diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index 26c56b0..5a8ee25 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -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)); @@ -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)); @@ -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); // Assess a fee (if fee switch enabled) and emit events. uint256 fee = 0; @@ -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; @@ -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. @@ -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); + if (amount != 0) { + // Get an additional bucket, because we still have options to exercise. exerciseIndex = (exerciseIndex + 1) % numUnexercisedBuckets; } } diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index 43d3d1b..77f079b 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -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. @@ -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 // @@ -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 diff --git a/test/OptionSettlementEngine.unit.t.sol b/test/OptionSettlementEngine.unit.t.sol index 00b8ad0..fc25fca 100644 --- a/test/OptionSettlementEngine.unit.t.sol +++ b/test/OptionSettlementEngine.unit.t.sol @@ -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); @@ -553,6 +558,7 @@ 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)); @@ -560,6 +566,9 @@ contract OptionSettlementUnitTest is BaseEngineTest { 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); @@ -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); @@ -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); @@ -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); @@ -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); diff --git a/test/utils/BaseEngineTest.sol b/test/utils/BaseEngineTest.sol index 52d5f81..f80aa0d 100644 --- a/test/utils/BaseEngineTest.sol +++ b/test/utils/BaseEngineTest.sol @@ -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);