Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support off-chain claim accounting via bucket events #185

Merged
merged 7 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions src/OptionSettlementEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,9 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
// Add claim bucket indices.
_addOrUpdateClaimIndex(optionTypeStates[optionKey], nextClaimKey, bucketIndex, amount);

// Emit event about options written on a new claim.
// Emit events about options written on a new claim.
emit OptionsWritten(encodedOptionId, msg.sender, tokenId, amount);
emit BucketWrittenInto(encodedOptionId, tokenId, bucketIndex, amount);

// Transfer in the requisite underlying asset amount.
SafeTransferLib.safeTransferFrom(ERC20(underlyingAsset), msg.sender, address(this), (rxAmount + fee));
Expand All @@ -478,8 +479,9 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
// Add claim bucket indices.
_addOrUpdateClaimIndex(optionTypeStates[optionKey], claimKey, bucketIndex, amount);

// Emit event about options written on existing claim.
// Emit events about options written on existing claim.
emit OptionsWritten(encodedOptionId, msg.sender, tokenId, amount);
emit BucketWrittenInto(encodedOptionId, tokenId, bucketIndex, amount);

// Transfer in the requisite underlying asset amount.
SafeTransferLib.safeTransferFrom(ERC20(underlyingAsset), msg.sender, address(this), (rxAmount + fee));
Expand Down Expand Up @@ -598,7 +600,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
address underlyingAsset = optionRecord.underlyingAsset;

// Assign exercise to writers.
_assignExercise(optionTypeState, optionRecord, amount);
_assignExercise(optionId, optionTypeState, optionRecord, amount);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, with the entropy + weighting changes, it's likely that we can drop optionRecord from the function params.


// Assess a fee (if fee switch enabled) and emit events.
uint256 fee = 0;
Expand Down Expand Up @@ -774,9 +776,12 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
* another bucket, the buckets are iterated from oldest to newest. The pseudorandom
* index seed is updated accordingly on the option type.
*/
function _assignExercise(OptionTypeState storage optionTypeState, Option storage optionRecord, uint112 amount)
private
{
function _assignExercise(
uint256 optionId,
OptionTypeState storage optionTypeState,
Option storage optionRecord,
uint112 amount
) private {
// Setup pointers to buckets and buckets with collateral available for exercise.
Bucket[] storage buckets = optionTypeState.bucketInfo.buckets;
uint96[] storage unexercisedBucketIndices = optionTypeState.bucketInfo.unexercisedBucketIndices;
Expand All @@ -791,7 +796,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
uint112 amountAvailable = bucketInfo.amountWritten - bucketInfo.amountExercised;
uint112 amountPresentlyExercised = 0;
if (amountAvailable <= amount) {
// Bucket is fully exercised/assigned
// Bucket is fully exercised/assigned.
amount -= amountAvailable;
amountPresentlyExercised = amountAvailable;
// Perform "swap and pop" index management.
Expand All @@ -800,13 +805,16 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine {
unexercisedBucketIndices[exerciseIndex] = overwrite;
unexercisedBucketIndices.pop();
} else {
// Bucket is partially exercised/assigned
// Bucket is partially exercised/assigned.
amountPresentlyExercised = amount;
amount = 0;
}
bucketInfo.amountExercised += amountPresentlyExercised;

emit BucketAssignedExercise(optionId, bucketIndex, amountPresentlyExercised);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe amountPresentlyExercised is the correct argument to include here — the amount of options that were just assigned exercised from this bucket.


if (amount != 0) {
// Get an additional bucket, because we still have options to exercise.
exerciseIndex = (exerciseIndex + 1) % numUnexercisedBuckets;
}
}
Expand Down
69 changes: 46 additions & 23 deletions src/interfaces/IOptionSettlementEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,9 @@ interface IOptionSettlementEngine {
//////////////////////////////////////////////////////////////*/

//
// Write/Redeem events
// Write events
//

/**
* @notice Emitted when a claim is redeemed.
* @param optionId The token id of the option type of the claim being redeemed.
* @param claimId The token id of the claim being redeemed.
* @param redeemer The address redeeming the claim.
* @param exerciseAmountRedeemed The amount of the option.exerciseAsset redeemed.
* @param underlyingAmountRedeemed The amount of option.underlyingAsset redeemed.
*/
event ClaimRedeemed(
uint256 indexed claimId,
uint256 indexed optionId,
address indexed redeemer,
uint256 exerciseAmountRedeemed,
uint256 underlyingAmountRedeemed
);

/**
* @notice Emitted when a new option type is created.
* @param optionId The token id of the new option type created.
Expand All @@ -49,6 +33,46 @@ interface IOptionSettlementEngine {
uint40 indexed expiryTimestamp
);

/**
* @notice Emitted when new options contracts are written.
* @param optionId The token id of the option type written.
* @param writer The address of the writer.
* @param claimId The claim token id of the new or existing short position written against.
* @param amount The amount of options contracts written.
*/
event OptionsWritten(uint256 indexed optionId, address indexed writer, uint256 indexed claimId, uint112 amount);

/**
* @notice Emitted when options contracts are written into a bucket.
* @param optionId The token id of the option type written.
* @param claimId The claim token id of the new or existing short position written against.
* @param bucketIndex The index of the bucket to which the options were written.
* @param amount The amount of options contracts written.
*/
event BucketWrittenInto(
uint256 indexed optionId, uint256 indexed claimId, uint96 indexed bucketIndex, uint112 amount
);

//
// Redeem events
//

/**
* @notice Emitted when a claim is redeemed.
* @param optionId The token id of the option type of the claim being redeemed.
* @param claimId The token id of the claim being redeemed.
* @param redeemer The address redeeming the claim.
* @param exerciseAmountRedeemed The amount of the option.exerciseAsset redeemed.
* @param underlyingAmountRedeemed The amount of option.underlyingAsset redeemed.
*/
event ClaimRedeemed(
uint256 indexed claimId,
uint256 indexed optionId,
address indexed redeemer,
uint256 exerciseAmountRedeemed,
uint256 underlyingAmountRedeemed
);

//
// Exercise events
//
Expand All @@ -62,13 +86,12 @@ interface IOptionSettlementEngine {
event OptionsExercised(uint256 indexed optionId, address indexed exerciser, uint112 amount);

/**
* @notice Emitted when new options contracts are written.
* @param optionId The token id of the option type written.
* @param writer The address of the writer.
* @param claimId The claim token id of the new or existing short position written against.
* @param amount The amount of options contracts written.
* @notice Emitted when a bucket is assigned exercise.
* @param optionId The token id of the option type exercised.
* @param bucketIndex The index of the bucket which is being assigned exercise.
* @param amountAssigned The amount of options contracts assigned exercise in the given bucket.
*/
event OptionsWritten(uint256 indexed optionId, address indexed writer, uint256 indexed claimId, uint112 amount);
event BucketAssignedExercise(uint256 indexed optionId, uint96 indexed bucketIndex, uint112 amountAssigned);

//
// Fee events
Expand Down
31 changes: 29 additions & 2 deletions test/OptionSettlementEngine.unit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -526,12 +526,17 @@ contract OptionSettlementUnitTest is BaseEngineTest {
function test_write_whenNewClaim() public {
uint112 amountWritten = 5;
uint256 expectedFee = _calculateFee(testUnderlyingAmount * amountWritten);
uint256 expectedClaimId = testOptionId + 1;
uint96 expectedBucketIndex = 0;

vm.expectEmit(true, true, true, true);
emit FeeAccrued(testOptionId, testUnderlyingAsset, ALICE, expectedFee);

vm.expectEmit(true, true, true, true);
emit OptionsWritten(testOptionId, ALICE, testOptionId + 1, amountWritten);
emit OptionsWritten(testOptionId, ALICE, expectedClaimId, amountWritten);

vm.expectEmit(true, true, true, true);
emit BucketWrittenInto(testOptionId, expectedClaimId, expectedBucketIndex, 5);

vm.prank(ALICE);
uint256 claimId = engine.write(testOptionId, amountWritten);
Expand All @@ -553,13 +558,17 @@ contract OptionSettlementUnitTest is BaseEngineTest {
// Alice writes 1 option
vm.prank(ALICE);
uint256 claimId = engine.write(testOptionId, 1);
uint96 expectedBucketIndex = 0;

vm.expectEmit(true, true, true, true);
emit FeeAccrued(testOptionId, testUnderlyingAsset, ALICE, _calculateFee(testUnderlyingAmount * 5));

vm.expectEmit(true, true, true, true);
emit OptionsWritten(testOptionId, ALICE, claimId, 5);

vm.expectEmit(true, true, true, true);
emit BucketWrittenInto(testOptionId, claimId, expectedBucketIndex, 5);

// Alice writes 5 more options on existing claim
vm.prank(ALICE);
uint256 existingClaimId = engine.write(claimId, 5);
Expand All @@ -579,11 +588,17 @@ contract OptionSettlementUnitTest is BaseEngineTest {
}

function test_write_whenFeeOff() public {
uint256 expectedClaimId = testOptionId + 1;
uint96 expectedBucketIndex = 0;

vm.prank(FEE_TO);
engine.setFeesEnabled(false);

vm.expectEmit(true, true, true, true);
emit OptionsWritten(testOptionId, ALICE, testOptionId + 1, 5);
emit OptionsWritten(testOptionId, ALICE, expectedClaimId, 5);

vm.expectEmit(true, true, true, true);
emit BucketWrittenInto(testOptionId, expectedClaimId, expectedBucketIndex, 5);

vm.prank(ALICE);
uint256 claimId = engine.write(testOptionId, 5);
Expand Down Expand Up @@ -1005,6 +1020,10 @@ contract OptionSettlementUnitTest is BaseEngineTest {
// Warp to exercise
vm.warp(testExerciseTimestamp);

uint96 expectedBucketIndex = 0;
vm.expectEmit(true, true, true, true);
emit BucketAssignedExercise(testOptionId, expectedBucketIndex, 2);

uint256 expectedExerciseFee = _calculateFee(testExerciseAmount * 2);
vm.expectEmit(true, true, true, true);
emit FeeAccrued(testOptionId, testExerciseAsset, BOB, expectedExerciseFee);
Expand Down Expand Up @@ -1053,6 +1072,10 @@ contract OptionSettlementUnitTest is BaseEngineTest {
uint256 expectedExercise2Fee = _calculateFee(testExerciseAmount * 2);
uint256 expectedExercise3Fee = _calculateFee(testExerciseAmount * 3);

uint96 expectedBucketIndex = 0;
vm.expectEmit(true, true, true, true);
emit BucketAssignedExercise(testOptionId, expectedBucketIndex, 2);

vm.expectEmit(true, true, true, true);
emit FeeAccrued(testOptionId, testExerciseAsset, BOB, expectedExercise2Fee);

Expand All @@ -1078,6 +1101,10 @@ contract OptionSettlementUnitTest is BaseEngineTest {
assertEq(engine.feeBalance(testUnderlyingAsset), expectedWriteFee, "Fee balance underlying after exercising 2");
assertEq(engine.feeBalance(testExerciseAsset), expectedExercise2Fee, "Fee balance exercise after exercising 2");

expectedBucketIndex = 0;
vm.expectEmit(true, true, true, true);
emit BucketAssignedExercise(testOptionId, expectedBucketIndex, 3);

vm.expectEmit(true, true, true, true);
emit FeeAccrued(testOptionId, testExerciseAsset, BOB, expectedExercise3Fee);

Expand Down
6 changes: 6 additions & 0 deletions test/utils/BaseEngineTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,12 @@ abstract contract BaseEngineTest is Test {
uint256 underlyingAmountRedeemed
);

event BucketWrittenInto(
uint256 indexed optionId, uint256 indexed claimId, uint96 indexed bucketIndex, uint112 amount
);

event BucketAssignedExercise(uint256 indexed optionId, uint96 indexed bucketIndex, uint112 amountAssigned);

event OptionsExercised(uint256 indexed optionId, address indexed exerciser, uint112 amount);

event FeeAccrued(uint256 indexed optionId, address indexed asset, address indexed payer, uint256 amount);
Expand Down