From 491c7f00b615c02de4df2a998e894c20eef1fbc4 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 14 Nov 2022 11:48:37 -0500 Subject: [PATCH 01/12] Try different interface ordering --- src/interfaces/IOptionSettlementEngine.sol | 106 +++++++++++++-------- 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index 64e0be7..251a40c 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -6,6 +6,10 @@ import "./IERC1155Metadata.sol"; /// @title A settlement engine for options /// @author 0xAlcibiades interface IOptionSettlementEngine { + /*////////////////////////////////////////////////////////////// + // Errors + //////////////////////////////////////////////////////////////*/ + /** * @notice The requested token is not found. * @param token token requested. @@ -110,6 +114,10 @@ interface IOptionSettlementEngine { /// @notice The amount provided to write() must be > 0. error AmountWrittenCannotBeZero(); + /*////////////////////////////////////////////////////////////// + // Events + //////////////////////////////////////////////////////////////*/ + /** * @notice Emitted when accrued protocol fees for a given token are swept to the * feeTo address. @@ -196,6 +204,10 @@ interface IOptionSettlementEngine { */ event ExerciseAssigned(uint256 indexed claimId, uint256 indexed optionId, uint112 amountAssigned); + /*////////////////////////////////////////////////////////////// + // Data structures + //////////////////////////////////////////////////////////////*/ + /// @dev This enumeration is used to determine the type of an ERC1155 subtoken in the engine. enum Type { None, @@ -266,25 +278,9 @@ interface IOptionSettlementEngine { int256 exercisePosition; } - /** - * @notice The balance of protocol fees for a given token which have not yet - * been swept. - * @param token The token for the unswept fee balance. - * @return The balance of unswept fees. - */ - function feeBalance(address token) external view returns (uint256); - - /** - * @notice The protocol fee, expressed in basis points. - * @return The fee in basis points. - */ - function feeBps() external view returns (uint8); - - /** - * @notice Returns the address to which protocol fees are swept. - * @return The address to which fees are swept - */ - function feeTo() external view returns (address); + /*////////////////////////////////////////////////////////////// + // Options Info + //////////////////////////////////////////////////////////////*/ /** * @notice Returns the token type (e.g. Option/Claim) for a given token Id @@ -321,17 +317,16 @@ interface IOptionSettlementEngine { returns (ClaimBucket memory claimBucketInfo); /** - * @notice Updates the address fees can be swept to. - * @param newFeeTo The new address to which fees will be swept. + * @notice Information about the position underlying a token, useful for determining + * value. + * @param tokenId The token id for which to retrieve the Underlying position. + * @return underlyingPositions The Underlying struct for the supplied tokenId. */ - function setFeeTo(address newFeeTo) external; + function underlying(uint256 tokenId) external view returns (Underlying memory underlyingPositions); - /** - * @notice Sweeps fees to the feeTo address if there are more than 0 wei for - * each address in tokens. - * @param tokens The tokens for which fees will be swept to the feeTo address. - */ - function sweepFees(address[] memory tokens) external; + /*////////////////////////////////////////////////////////////// + // Write Options + //////////////////////////////////////////////////////////////*/ // /** // * @notice Create a new options type from optionInfo if it doesn't already exist @@ -347,14 +342,6 @@ interface IOptionSettlementEngine { uint96 underlyingAmount, uint96 exerciseAmount ) external returns (uint256 optionId); - // function newOptionType( - // address underlyingAsset, - // uint256 underlyingAmount, - // address exerciseAsset, - // uint256 exerciseAmount, - // uint256 exerciseTimestamp, - // uint256 expiryTimestamp - // ) external returns (uint256 optionId); /** * @notice Writes a specified amount of the specified option, returning claim NFT id. @@ -374,6 +361,10 @@ interface IOptionSettlementEngine { */ function write(uint256 optionId, uint112 amount, uint256 claimId) external returns (uint256); + /*////////////////////////////////////////////////////////////// + // Exercise Options + //////////////////////////////////////////////////////////////*/ + /** * @notice Exercises specified amount of optionId, transferring in the exercise asset, * and transferring out the underlying asset if requirements are met. Will revert with @@ -383,17 +374,50 @@ interface IOptionSettlementEngine { */ function exercise(uint256 optionId, uint112 amount) external; + /*////////////////////////////////////////////////////////////// + // Redeem Options + //////////////////////////////////////////////////////////////*/ + /** * @notice Redeem a claim NFT, transfers the underlying tokens. * @param claimId The ID of the claim to redeem. */ function redeem(uint256 claimId) external; + /*////////////////////////////////////////////////////////////// + // Protocol Admin + //////////////////////////////////////////////////////////////*/ + /** - * @notice Information about the position underlying a token, useful for determining - * value. - * @param tokenId The token id for which to retrieve the Underlying position. - * @return underlyingPositions The Underlying struct for the supplied tokenId. + * @notice The protocol fee, expressed in basis points. + * @return The fee in basis points. */ - function underlying(uint256 tokenId) external view returns (Underlying memory underlyingPositions); + function feeBps() external view returns (uint8); + + /** + * @notice The balance of protocol fees for a given token which have not yet + * been swept. + * @param token The token for the unswept fee balance. + * @return The balance of unswept fees. + */ + function feeBalance(address token) external view returns (uint256); + + /** + * @notice Returns the address to which protocol fees are swept. + * @return The address to which fees are swept + */ + function feeTo() external view returns (address); + + /** + * @notice Updates the address fees can be swept to. + * @param newFeeTo The new address to which fees will be swept. + */ + function setFeeTo(address newFeeTo) external; + + /** + * @notice Sweeps fees to the feeTo address if there are more than 0 wei for + * each address in tokens. + * @param tokens The tokens for which fees will be swept to the feeTo address. + */ + function sweepFees(address[] memory tokens) external; } From 83d489da4586486f59179c8a792789ca0b51387a Mon Sep 17 00:00:00 2001 From: neodaoist Date: Mon, 14 Nov 2022 12:23:56 -0500 Subject: [PATCH 02/12] Rename / Reorder variables --- README.md | 4 +- ...tlement.sol => OptionSettlementEngine.sol} | 28 ++--- src/TokenURIGenerator.sol | 4 +- src/interfaces/IOptionSettlementEngine.sol | 36 +++--- test/OptionSettlement.t.sol | 108 +++++++++--------- 5 files changed, 90 insertions(+), 90 deletions(-) rename src/{OptionSettlement.sol => OptionSettlementEngine.sol} (97%) diff --git a/README.md b/README.md index 7c76d39..1fe6995 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ event NewOptionType( address indexed underlyingAsset, uint96 exerciseAmount, uint96 underlyingAmount, - uint40 exerciseTimestamp, + uint40 earliestExerciseTimestamp, uint40 expiryTimestamp ); ``` @@ -314,7 +314,7 @@ unique hash `keccak256(abi.encode(Option memory))` where `settlementSeed` is set ```solidity struct Option { address underlyingAsset; - uint40 exerciseTimestamp; + uint40 earliestExerciseTimestamp; uint40 expiryTimestamp; address exerciseAsset; uint96 underlyingAmount; diff --git a/src/OptionSettlement.sol b/src/OptionSettlementEngine.sol similarity index 97% rename from src/OptionSettlement.sol rename to src/OptionSettlementEngine.sol index cfec9bf..2359486 100644 --- a/src/OptionSettlement.sol +++ b/src/OptionSettlementEngine.sol @@ -140,7 +140,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { underlyingSymbol: ERC20(optionInfo.underlyingAsset).symbol(), exerciseAsset: optionInfo.exerciseAsset, exerciseSymbol: ERC20(optionInfo.exerciseAsset).symbol(), - exerciseTimestamp: optionInfo.exerciseTimestamp, + earliestExerciseTimestamp: optionInfo.earliestExerciseTimestamp, expiryTimestamp: optionInfo.expiryTimestamp, underlyingAmount: optionInfo.underlyingAmount, exerciseAmount: optionInfo.exerciseAmount, @@ -153,23 +153,23 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { ///// @inheritdoc IOptionSettlementEngine function newOptionType( address underlyingAsset, - uint40 exerciseTimestamp, - uint40 expiryTimestamp, - address exerciseAsset, uint96 underlyingAmount, - uint96 exerciseAmount + address exerciseAsset, + uint96 exerciseAmount, + uint40 earliestExerciseTimestamp, + uint40 expiryTimestamp ) external returns (uint256 optionId) { // Check that a duplicate option type doesn't exist bytes20 optionHash = bytes20( keccak256( abi.encode( underlyingAsset, - exerciseTimestamp, - expiryTimestamp, - exerciseAsset, underlyingAmount, - uint160(0), + exerciseAsset, exerciseAmount, + earliestExerciseTimestamp, + expiryTimestamp, + uint160(0), uint96(0) ) ) @@ -188,7 +188,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } // Ensure the exercise window is at least 24 hours - if (expiryTimestamp < (exerciseTimestamp + 1 days)) { + if (expiryTimestamp < (earliestExerciseTimestamp + 1 days)) { revert ExerciseWindowTooShort(); } @@ -211,7 +211,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { underlyingAmount: underlyingAmount, exerciseAsset: exerciseAsset, exerciseAmount: exerciseAmount, - exerciseTimestamp: exerciseTimestamp, + earliestExerciseTimestamp: earliestExerciseTimestamp, expiryTimestamp: expiryTimestamp, settlementSeed: optionKey, nextClaimId: 1 @@ -223,7 +223,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { underlyingAsset, exerciseAmount, underlyingAmount, - exerciseTimestamp, + earliestExerciseTimestamp, expiryTimestamp, 1 ); @@ -338,8 +338,8 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { revert ExpiredOption(optionId, optionRecord.expiryTimestamp); } // Require that we have reached the exercise timestamp - if (optionRecord.exerciseTimestamp >= block.timestamp) { - revert ExerciseTooEarly(optionId, optionRecord.exerciseTimestamp); + if (optionRecord.earliestExerciseTimestamp >= block.timestamp) { + revert ExerciseTooEarly(optionId, optionRecord.earliestExerciseTimestamp); } uint256 rxAmount = optionRecord.exerciseAmount * amount; diff --git a/src/TokenURIGenerator.sol b/src/TokenURIGenerator.sol index 7202551..cbb7dd3 100644 --- a/src/TokenURIGenerator.sol +++ b/src/TokenURIGenerator.sol @@ -17,7 +17,7 @@ library TokenURIGenerator { // The symbol of the underlying asset string exerciseSymbol; // The timestamp after which this option may be exercised - uint40 exerciseTimestamp; + uint40 earliestExerciseTimestamp; // The timestamp before which this option must be exercised uint40 expiryTimestamp; // The amount of the underlying asset contained within an option contract of this type @@ -160,7 +160,7 @@ library TokenURIGenerator { return string( abi.encodePacked( "EXERCISE DATE", - _generateTimestampString(params.exerciseTimestamp, 16, 260), + _generateTimestampString(params.earliestExerciseTimestamp, 16, 260), "EXPIRY DATE", _generateTimestampString(params.expiryTimestamp, 200, 260) ) diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index 251a40c..b267e8f 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -134,7 +134,7 @@ interface IOptionSettlementEngine { * @param underlyingAsset The contract address of the underlying asset. * @param exerciseAmount The amount of the exercise asset to be exercised. * @param underlyingAmount The amount of the underlying asset in the option. - * @param exerciseTimestamp The timestamp for exercising the option. + * @param earliestExerciseTimestamp The timestamp for exercising the option. * @param expiryTimestamp The expiry timestamp of the option. * @param nextClaimId The next claim ID. */ @@ -144,7 +144,7 @@ interface IOptionSettlementEngine { address indexed underlyingAsset, uint96 exerciseAmount, uint96 underlyingAmount, - uint40 exerciseTimestamp, + uint40 earliestExerciseTimestamp, uint40 expiryTimestamp, uint96 nextClaimId ); @@ -217,21 +217,21 @@ interface IOptionSettlementEngine { /// @dev This struct contains the data about an options type associated with an ERC-1155 token. struct Option { - // The underlying asset to be received + /// @param underlyingAsset The underlying asset to be received address underlyingAsset; - // The timestamp after which this option may be exercised - uint40 exerciseTimestamp; - // The timestamp before which this option must be exercised - uint40 expiryTimestamp; - // The address of the asset needed for exercise - address exerciseAsset; - // The amount of the underlying asset contained within an option contract of this type + /// @param underlyingAmount The amount of the underlying asset contained within an option contract of this type uint96 underlyingAmount; - // Random seed created at the time of option type creation - uint160 settlementSeed; - // The amount of the exercise asset required to exercise this option + /// @param exerciseAsset The address of the asset needed for exercise + address exerciseAsset; + /// @param exerciseAmount The amount of the exercise asset required to exercise this option uint96 exerciseAmount; - // Which option was written + /// @param earliestExerciseTimestamp The timestamp after which this option may be exercised + uint40 earliestExerciseTimestamp; + /// @param expiryTimestamp The timestamp before which this option must be exercised + uint40 expiryTimestamp; + /// @param settlementSeed Random seed created at the time of option type creation + uint160 settlementSeed; + /// @param nextClaimId Which option was written uint96 nextClaimId; } @@ -336,11 +336,11 @@ interface IOptionSettlementEngine { // */ function newOptionType( address underlyingAsset, - uint40 exerciseTimestamp, - uint40 expiryTimestamp, - address exerciseAsset, uint96 underlyingAmount, - uint96 exerciseAmount + address exerciseAsset, + uint96 exerciseAmount, + uint40 earliestExerciseTimestamp, + uint40 expiryTimestamp ) external returns (uint256 optionId); /** diff --git a/test/OptionSettlement.t.sol b/test/OptionSettlement.t.sol index 365b7f6..b060c03 100644 --- a/test/OptionSettlement.t.sol +++ b/test/OptionSettlement.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.11; import "forge-std/Test.sol"; import "./interfaces/IERC20.sol"; -import "../src/OptionSettlement.sol"; +import "../src/OptionSettlementEngine.sol"; /// @notice Receiver hook utility for NFT 'safe' transfers abstract contract NFTreceiver { @@ -68,7 +68,7 @@ contract OptionSettlementTest is Test, NFTreceiver { testExpiryTimestamp = uint40(block.timestamp + testDuration); (testOptionId,) = _newOption({ underlyingAsset: WETH_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: DAI_A, underlyingAmount: testUnderlyingAmount, @@ -339,7 +339,7 @@ contract OptionSettlementTest is Test, NFTreceiver { (uint256 optionId, IOptionSettlementEngine.Option memory option) = _newOption({ underlyingAsset: WETH_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: DAI_A, underlyingAmount: testUnderlyingAmount, @@ -419,7 +419,7 @@ contract OptionSettlementTest is Test, NFTreceiver { testExpiryTimestamp = uint40(block.timestamp + numDays * 1 days + 1); (uint256 optionId, IOptionSettlementEngine.Option memory optionInfo) = _newOption({ underlyingAsset: WETH_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: DAI_A, underlyingAmount: testUnderlyingAmount + 1, // to mess w seed @@ -510,13 +510,13 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: 1, exerciseAsset: USDC_A, exerciseAmount: 100, - exerciseTimestamp: uint40(block.timestamp), + earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: uint40(block.timestamp + 30 days), settlementSeed: 0, nextClaimId: 0 }); uint256 oTokenId = - engine.newOptionType(DAI_A, uint40(block.timestamp), uint40(block.timestamp + 30 days), USDC_A, 1, 100); + engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); // Write 2 separate options lots vm.prank(ALICE); @@ -551,13 +551,13 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: 1, exerciseAsset: USDC_A, exerciseAmount: 100, - exerciseTimestamp: uint40(block.timestamp), + earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: uint40(block.timestamp + 30 days), settlementSeed: 0, nextClaimId: 0 }); uint256 oTokenId = - engine.newOptionType(DAI_A, uint40(block.timestamp), uint40(block.timestamp + 30 days), USDC_A, 1, 100); + engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); // Write 2 separate options lots vm.prank(ALICE); @@ -599,13 +599,13 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: 1, exerciseAsset: USDC_A, exerciseAmount: 100, - exerciseTimestamp: uint40(block.timestamp), + earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: uint40(block.timestamp + 30 days), settlementSeed: 0, nextClaimId: 0 }); uint256 oTokenId = - engine.newOptionType(DAI_A, uint40(block.timestamp), uint40(block.timestamp + 30 days), USDC_A, 1, 100); + engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); // Update struct values to match stored option data structure bytes20 optionHash = bytes20(keccak256(abi.encode(option))); @@ -623,13 +623,13 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: 1, exerciseAsset: USDC_A, exerciseAmount: 100, - exerciseTimestamp: uint40(block.timestamp), + earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: uint40(block.timestamp + 30 days), settlementSeed: 0, nextClaimId: 0 }); uint256 oTokenId = - engine.newOptionType(DAI_A, uint40(block.timestamp), uint40(block.timestamp + 30 days), USDC_A, 1, 100); + engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); (uint160 decodedOptionId,) = engine.getDecodedIdComponents(oTokenId); @@ -644,7 +644,7 @@ contract OptionSettlementTest is Test, NFTreceiver { function testEventNewOptionType() public { IOptionSettlementEngine.Option memory optionInfo = IOptionSettlementEngine.Option({ underlyingAsset: DAI_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: WETH_A, underlyingAmount: testUnderlyingAmount, @@ -668,7 +668,7 @@ contract OptionSettlementTest is Test, NFTreceiver { ); uint256 oTokenId = engine.newOptionType( - DAI_A, testExerciseTimestamp, testExpiryTimestamp, WETH_A, testUnderlyingAmount, testExerciseAmount + DAI_A, testUnderlyingAmount, WETH_A, testExerciseAmount, testExerciseTimestamp, testExpiryTimestamp ); } @@ -761,7 +761,7 @@ contract OptionSettlementTest is Test, NFTreceiver { // Write option that will generate DAI fees (uint256 daiOptionId,) = _newOption({ underlyingAsset: DAI_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: WETH_A, underlyingAmount: daiUnderlyingAmount, @@ -772,7 +772,7 @@ contract OptionSettlementTest is Test, NFTreceiver { // Write option that will generate USDC fees (uint256 usdcOptionId,) = _newOption({ underlyingAsset: USDC_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: DAI_A, underlyingAmount: usdcUnderlyingAmount, @@ -810,7 +810,7 @@ contract OptionSettlementTest is Test, NFTreceiver { vm.startPrank(ALICE); (uint256 daiExerciseOptionId,) = _newOption({ underlyingAsset: WETH_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: DAI_A, underlyingAmount: testUnderlyingAmount, @@ -821,7 +821,7 @@ contract OptionSettlementTest is Test, NFTreceiver { // Write option for DAI-WETH pair (uint256 wethExerciseOptionId,) = _newOption({ underlyingAsset: DAI_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: WETH_A, underlyingAmount: testUnderlyingAmount, @@ -832,7 +832,7 @@ contract OptionSettlementTest is Test, NFTreceiver { // Write option for DAI-USDC pair (uint256 usdcExerciseOptionId,) = _newOption({ underlyingAsset: DAI_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: USDC_A, underlyingAmount: testUnderlyingAmount, @@ -884,7 +884,7 @@ contract OptionSettlementTest is Test, NFTreceiver { function testRevertNewOptionTypeWhenOptionsTypeExists() public { (uint256 optionId, IOptionSettlementEngine.Option memory option) = _newOption({ underlyingAsset: DAI_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: WETH_A, underlyingAmount: testUnderlyingAmount, @@ -894,7 +894,7 @@ contract OptionSettlementTest is Test, NFTreceiver { vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.OptionsTypeExists.selector, optionId)); engine.newOptionType( - DAI_A, testExerciseTimestamp, testExpiryTimestamp, WETH_A, testUnderlyingAmount, testExerciseAmount + DAI_A, testUnderlyingAmount, WETH_A, testExerciseAmount, testExerciseTimestamp, testExpiryTimestamp ); } @@ -902,7 +902,7 @@ contract OptionSettlementTest is Test, NFTreceiver { uint40 tooSoonExpiryTimestamp = uint40(block.timestamp + 1 days - 1 seconds); IOptionSettlementEngine.Option memory option = IOptionSettlementEngine.Option({ underlyingAsset: DAI_A, - exerciseTimestamp: uint40(block.timestamp), + earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: tooSoonExpiryTimestamp, exerciseAsset: WETH_A, underlyingAmount: testUnderlyingAmount, @@ -920,7 +920,7 @@ contract OptionSettlementTest is Test, NFTreceiver { _newOption({ underlyingAsset: DAI_A, - exerciseTimestamp: uint40(block.timestamp), + earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: tooSoonExpiryTimestamp, exerciseAsset: WETH_A, underlyingAmount: testUnderlyingAmount, @@ -933,7 +933,7 @@ contract OptionSettlementTest is Test, NFTreceiver { _newOption({ underlyingAsset: DAI_A, - exerciseTimestamp: 2_000_000_000, + earliestExerciseTimestamp: 2_000_000_000, expiryTimestamp: 2_000_000_000 + 1 days - 1 seconds, exerciseAsset: WETH_A, underlyingAmount: testUnderlyingAmount, @@ -946,7 +946,7 @@ contract OptionSettlementTest is Test, NFTreceiver { _newOption({ underlyingAsset: DAI_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: DAI_A, underlyingAmount: testUnderlyingAmount, @@ -961,7 +961,7 @@ contract OptionSettlementTest is Test, NFTreceiver { _newOption({ underlyingAsset: DAI_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: WETH_A, underlyingAmount: underlyingAmountExceedsTotalSupply, @@ -974,7 +974,7 @@ contract OptionSettlementTest is Test, NFTreceiver { _newOption({ underlyingAsset: USDC_A, - exerciseTimestamp: testExerciseTimestamp, + earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: WETH_A, underlyingAmount: testUnderlyingAmount, @@ -1087,7 +1087,7 @@ contract OptionSettlementTest is Test, NFTreceiver { engine.safeTransferFrom(ALICE, BOB, testOptionId, 1, ""); vm.stopPrank(); - // Bob immediately exercises before exerciseTimestamp + // Bob immediately exercises before earliestExerciseTimestamp vm.startPrank(BOB); vm.expectRevert( abi.encodeWithSelector( @@ -1239,20 +1239,20 @@ contract OptionSettlementTest is Test, NFTreceiver { function testFuzzNewOptionType( uint96 underlyingAmount, uint96 exerciseAmount, - uint40 exerciseTimestamp, + uint40 earliestExerciseTimestamp, uint40 expiryTimestamp ) public { vm.assume(expiryTimestamp >= block.timestamp + 86400); - vm.assume(exerciseTimestamp >= block.timestamp); - vm.assume(exerciseTimestamp <= expiryTimestamp - 86400); + vm.assume(earliestExerciseTimestamp >= block.timestamp); + vm.assume(earliestExerciseTimestamp <= expiryTimestamp - 86400); vm.assume(expiryTimestamp <= type(uint64).max); - vm.assume(exerciseTimestamp <= type(uint64).max); + vm.assume(earliestExerciseTimestamp <= type(uint64).max); vm.assume(underlyingAmount <= WETH.totalSupply()); vm.assume(exerciseAmount <= DAI.totalSupply()); (uint256 optionId, IOptionSettlementEngine.Option memory optionInfo) = _newOption( WETH_A, // underlyingAsset - exerciseTimestamp, // exerciseTimestamp + earliestExerciseTimestamp, // earliestExerciseTimestamp expiryTimestamp, // expiryTimestamp DAI_A, // exerciseAsset underlyingAmount, // underlyingAmount @@ -1269,7 +1269,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertEq(optionId, expectedOptionId); assertEq(optionRecord.underlyingAsset, WETH_A); assertEq(optionRecord.exerciseAsset, DAI_A); - assertEq(optionRecord.exerciseTimestamp, exerciseTimestamp); + assertEq(optionRecord.earliestExerciseTimestamp, earliestExerciseTimestamp); assertEq(optionRecord.expiryTimestamp, expiryTimestamp); assertEq(optionRecord.underlyingAmount, underlyingAmount); assertEq(optionRecord.exerciseAmount, exerciseAmount); @@ -1402,7 +1402,7 @@ contract OptionSettlementTest is Test, NFTreceiver { // create monthly option (uint256 optionId1M, IOptionSettlementEngine.Option memory option1M) = _newOption( WETH_A, // underlyingAsset - testExerciseTimestamp, // exerciseTimestamp + testExerciseTimestamp, // earliestExerciseTimestamp uint40(block.timestamp + 30 days), // expiryTimestamp DAI_A, // exerciseAsset testUnderlyingAmount, // underlyingAmount @@ -1412,7 +1412,7 @@ contract OptionSettlementTest is Test, NFTreceiver { // create quarterly option (uint256 optionId3M, IOptionSettlementEngine.Option memory option3M) = _newOption( WETH_A, // underlyingAsset - testExerciseTimestamp, // exerciseTimestamp + testExerciseTimestamp, // earliestExerciseTimestamp uint40(block.timestamp + 90 days), // expiryTimestamp DAI_A, // exerciseAsset testUnderlyingAmount, // underlyingAmount @@ -1535,8 +1535,8 @@ contract OptionSettlementTest is Test, NFTreceiver { } } - if (option.exerciseTimestamp >= uint40(block.timestamp)) { - emit log_named_uint("exercise timestamp not hit", option.exerciseTimestamp); + if (option.earliestExerciseTimestamp >= uint40(block.timestamp)) { + emit log_named_uint("exercise timestamp not hit", option.earliestExerciseTimestamp); return (written, 0, newClaim); } @@ -1591,7 +1591,7 @@ contract OptionSettlementTest is Test, NFTreceiver { function _writeAndExerciseNewOption( address underlyingAsset, - uint40 exerciseTimestamp, + uint40 earliestExerciseTimestamp, uint40 expiryTimestamp, address exerciseAsset, uint96 underlyingAmount, @@ -1600,31 +1600,31 @@ contract OptionSettlementTest is Test, NFTreceiver { address exerciser ) internal returns (uint256 optionId, uint256 claimId) { (optionId,) = _newOption( - underlyingAsset, exerciseTimestamp, expiryTimestamp, exerciseAsset, underlyingAmount, exerciseAmount + underlyingAsset, earliestExerciseTimestamp, expiryTimestamp, exerciseAsset, underlyingAmount, exerciseAmount ); claimId = _writeAndExerciseOption(optionId, writer, exerciser); } function _newOption( address underlyingAsset, - uint40 exerciseTimestamp, + uint40 earliestExerciseTimestamp, uint40 expiryTimestamp, address exerciseAsset, uint96 underlyingAmount, uint96 exerciseAmount ) internal returns (uint256 optionId, IOptionSettlementEngine.Option memory option) { - option = IOptionSettlementEngine.Option( - underlyingAsset, - exerciseTimestamp, - expiryTimestamp, - exerciseAsset, - underlyingAmount, - 0, // default zero for settlement seed - exerciseAmount, - 0 // default zero for next claim id - ); + option = IOptionSettlementEngine.Option({ + underlyingAsset: underlyingAsset, + underlyingAmount: underlyingAmount, + exerciseAsset: exerciseAsset, + exerciseAmount: exerciseAmount, + earliestExerciseTimestamp: earliestExerciseTimestamp, + expiryTimestamp: expiryTimestamp, + settlementSeed: 0, // default zero for settlement seed + nextClaimId: 0 // default zero for next claim id + }); optionId = engine.newOptionType( - underlyingAsset, exerciseTimestamp, expiryTimestamp, exerciseAsset, underlyingAmount, exerciseAmount + underlyingAsset, underlyingAmount, exerciseAsset, exerciseAmount, earliestExerciseTimestamp, expiryTimestamp ); } @@ -1754,7 +1754,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertEq(actual.underlyingAmount, expected.underlyingAmount); assertEq(actual.exerciseAsset, expected.exerciseAsset); assertEq(actual.exerciseAmount, expected.exerciseAmount); - assertEq(actual.exerciseTimestamp, expected.exerciseTimestamp); + assertEq(actual.earliestExerciseTimestamp, expected.earliestExerciseTimestamp); assertEq(actual.expiryTimestamp, expected.expiryTimestamp); assertEq(actual.settlementSeed, expected.settlementSeed); assertEq(actual.nextClaimId, expected.nextClaimId); @@ -1768,7 +1768,7 @@ contract OptionSettlementTest is Test, NFTreceiver { address indexed underlyingAsset, uint96 exerciseAmount, uint96 underlyingAmount, - uint40 exerciseTimestamp, + uint40 earliestExerciseTimestamp, uint40 expiryTimestamp, uint96 nextClaimId ); From 1f2affe94d6953a80541b3c09983309eb57c4f60 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 15 Nov 2022 10:46:20 -0500 Subject: [PATCH 03/12] Rename Claim to OptionLotClaim --- src/OptionSettlementEngine.sol | 332 +++++++++++---------- src/interfaces/IOptionSettlementEngine.sol | 38 +-- test/OptionSettlement.t.sol | 26 +- 3 files changed, 213 insertions(+), 183 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index 2359486..b0fda10 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -21,20 +21,28 @@ import "./TokenURIGenerator.sol"; /// @notice This settlement protocol does not support rebase tokens, or fee on transfer tokens contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { + /*////////////////////////////////////////////////////////////// + // State variables - Public + //////////////////////////////////////////////////////////////*/ + /// @notice The protocol fee uint8 public immutable feeBps = 5; + /// @notice Fee balance for a given token + mapping(address => uint256) public feeBalance; + /// @notice The address fees accrue to address public feeTo = 0x2dbd50A4Ef9B172698596217b7DB0163D3607b41; - /// @notice Fee balance for a given token - mapping(address => uint256) public feeBalance; + /*////////////////////////////////////////////////////////////// + // State variables - Internal + //////////////////////////////////////////////////////////////*/ /// @notice Accessor for Option contract details mapping(uint160 => Option) internal _option; /// @notice Accessor for claim ticket details - mapping(uint256 => Claim) internal _claim; + mapping(uint256 => OptionLotClaim) internal _claim; /// @notice Accessor for buckets of claims grouped by day /// @dev This is to enable O(constant) time options exercise. When options are written, @@ -42,7 +50,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// on the day in question. write() will add unexercised options into the bucket /// corresponding to the # of days after the option type's creation. /// exercise() will randomly assign exercise to a bucket <= the current day. - mapping(uint160 => ClaimBucket[]) internal _claimBucketByOption; + mapping(uint160 => OptionLotClaimBucket[]) internal _claimBucketByOption; /// @notice Maintains a mapping from option id to a list of unexercised bucket (indices) /// @dev Used during the assignment process to find claim buckets with unexercised @@ -57,7 +65,11 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { mapping(uint160 => mapping(uint16 => bool)) internal _doesBucketIndexHaveUnexercisedOptions; /// @notice Accessor for mapping a claim id to its ClaimIndices - mapping(uint256 => ClaimIndex[]) internal _claimIdToClaimIndexArray; + mapping(uint256 => OptionLotClaimIndex[]) internal _claimIdToClaimIndexArray; + + /*////////////////////////////////////////////////////////////// + // Functions - Option Info + //////////////////////////////////////////////////////////////*/ /// @inheritdoc IOptionSettlementEngine function option(uint256 tokenId) external view returns (Option memory optionInfo) { @@ -66,64 +78,63 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } /// @inheritdoc IOptionSettlementEngine - function claim(uint256 tokenId) external view returns (Claim memory claimInfo) { + function claim(uint256 tokenId) external view returns (OptionLotClaim memory claimInfo) { claimInfo = _claim[tokenId]; } + /// @inheritdoc IOptionSettlementEngine function claimBucket(uint256 optionId, uint16 dayBucket) external view - returns (ClaimBucket memory claimBucketInfo) + returns (OptionLotClaimBucket memory claimBucketInfo) { (uint160 _optionId,) = getDecodedIdComponents(optionId); claimBucketInfo = _claimBucketByOption[_optionId][dayBucket]; } /// @inheritdoc IOptionSettlementEngine - function tokenType(uint256 tokenId) external pure returns (Type) { - (, uint96 claimIdx) = getDecodedIdComponents(tokenId); - if (claimIdx == 0) { - return Type.Option; - } - return Type.Claim; - } + function underlying(uint256 tokenId) external view returns (Underlying memory underlyingPositions) { + (uint160 _tokenIdU160b, uint96 _tokenIdL96b) = getDecodedIdComponents(tokenId); - /// @inheritdoc IOptionSettlementEngine - function setFeeTo(address newFeeTo) public { - if (msg.sender != feeTo) { - revert AccessControlViolation(msg.sender, feeTo); + if (!isOptionInitialized(_tokenIdU160b)) { + revert TokenNotFound(tokenId); } - if (newFeeTo == address(0)) { - revert InvalidFeeToAddress(newFeeTo); + + Option storage optionRecord = _option[_tokenIdU160b]; + + // token ID is an option + if (_tokenIdL96b == 0) { + bool expired = (optionRecord.expiryTimestamp > block.timestamp); + underlyingPositions = Underlying({ + underlyingAsset: optionRecord.underlyingAsset, + underlyingPosition: expired ? int256(0) : int256(uint256(optionRecord.underlyingAmount)), + exerciseAsset: optionRecord.exerciseAsset, + exercisePosition: expired ? int256(0) : -int256(uint256(optionRecord.exerciseAmount)) + }); + } else { + // token ID is a claim + (uint256 amountExerciseAsset, uint256 amountUnderlyingAsset) = + _getPositionsForClaim(_tokenIdU160b, tokenId, optionRecord); + + underlyingPositions = Underlying({ + underlyingAsset: optionRecord.underlyingAsset, + underlyingPosition: int256(amountUnderlyingAsset), + exerciseAsset: optionRecord.exerciseAsset, + exercisePosition: int256(amountExerciseAsset) + }); } - feeTo = newFeeTo; } /// @inheritdoc IOptionSettlementEngine - function sweepFees(address[] memory tokens) public { - address sendFeeTo = feeTo; - address token; - uint256 fee; - uint256 sweep; - uint256 numTokens = tokens.length; - - unchecked { - for (uint256 i = 0; i < numTokens; i++) { - // Get the token and balance to sweep - token = tokens[i]; - - fee = feeBalance[token]; - // Leave 1 wei here as a gas optimization - if (fee > 1) { - sweep = fee - 1; - feeBalance[token] = 1; - SafeTransferLib.safeTransfer(ERC20(token), sendFeeTo, sweep); - emit FeeSwept(token, sendFeeTo, sweep); - } - } + function tokenType(uint256 tokenId) external pure returns (Type) { + (, uint96 claimIdx) = getDecodedIdComponents(tokenId); + if (claimIdx == 0) { + return Type.Option; } + return Type.OptionLotClaim; } + /// @dev TODO function uri(uint256 tokenId) public view virtual override returns (string memory) { Option memory optionInfo; (uint160 optionId, uint96 claimId) = getDecodedIdComponents(tokenId); @@ -133,7 +144,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { revert TokenNotFound(tokenId); } - Type _type = claimId == 0 ? Type.Option : Type.Claim; + Type _type = claimId == 0 ? Type.Option : Type.OptionLotClaim; TokenURIGenerator.TokenURIParams memory params = TokenURIGenerator.TokenURIParams({ underlyingAsset: optionInfo.underlyingAsset, @@ -150,7 +161,75 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { return TokenURIGenerator.constructTokenURI(params); } - ///// @inheritdoc IOptionSettlementEngine + /*////////////////////////////////////////////////////////////// + // Functions - Token ID Encoding + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Decode the supplied token ID + * @dev Option and claim token IDs are encoded as follows: + * + * MSb + * 0000 0000 0000 0000 0000 0000 0000 0000 ┐ + * 0000 0000 0000 0000 0000 0000 0000 0000 │ + * 0000 0000 0000 0000 0000 0000 0000 0000 │ 160b hash of option data structure + * 0000 0000 0000 0000 0000 0000 0000 0000 │ + * 0000 0000 0000 0000 0000 0000 0000 0000 │ + * 0000 0000 0000 0000 0000 0000 0000 0000 ┘ + * 0000 0000 0000 0000 0000 0000 0000 0000 ┐ + * 0000 0000 0000 0000 0000 0000 0000 0000 │ 96b encoding of claim id + * 0000 0000 0000 0000 0000 0000 0000 0000 ┘ + * LSb + * + * @param id The token id to decode + * @return optionId claimId The decoded components of the id as described above, padded as required. + */ + function getDecodedIdComponents(uint256 id) public pure returns (uint160 optionId, uint96 claimId) { + // grab lower 96b of id for claim id + uint256 claimIdMask = 0xFFFFFFFFFFFFFFFFFFFFFFFF; + + // move hash to LSB to fit into uint160 + optionId = uint160(id >> 96); + claimId = uint96(id & claimIdMask); + } + + /** + * @notice Encode the supplied option id and claim id + * @dev See getDecodedIdComponents() for encoding scheme + * @param optionId The optionId to encode + * @param claimIndex The claimIndex to encode + * @return claimId The encoded token id + */ + function getTokenId(uint160 optionId, uint96 claimIndex) public pure returns (uint256 claimId) { + claimId |= (uint256(optionId) << 96); + claimId |= uint256(claimIndex); + } + + /** + * @notice Return the option for the supplied token id + * @dev See getDecodedIdComponents() for encoding scheme + * @param id The token id of the Option + * @return The stored Option + */ + function getOptionFromEncodedId(uint256 id) public view returns (Option memory) { + (uint160 optionId,) = getDecodedIdComponents(id); + return _option[optionId]; + } + + /** + * @notice Check to see if an option is already initialized + * @param optionId The option id to check + * @return Whether or not the option is initialized + */ + function isOptionInitialized(uint160 optionId) public view returns (bool) { + return _option[optionId].underlyingAsset != address(0x0); + } + + /*////////////////////////////////////////////////////////////// + // Functions - Write Options + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc IOptionSettlementEngine function newOptionType( address underlyingAsset, uint96 underlyingAmount, @@ -282,7 +361,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { uint96 claimIndex = optionRecord.nextClaimId++; claimId = getTokenId(_optionIdU160b, claimIndex); // Store info about the claim - _claim[claimId] = Claim({amountWritten: amount, claimed: false}); + _claim[claimId] = OptionLotClaim({amountWritten: amount, claimed: false}); mintClaimNft = 1; } else { // check ownership of claim @@ -292,7 +371,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } // retrieve claim - Claim storage existingClaim = _claim[claimId]; + OptionLotClaim storage existingClaim = _claim[claimId]; if (existingClaim.claimed) { revert AlreadyClaimed(claimId); @@ -323,6 +402,10 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { return claimId; } + /*////////////////////////////////////////////////////////////// + // Functions - Exercise Options + //////////////////////////////////////////////////////////////*/ + /// @inheritdoc IOptionSettlementEngine function exercise(uint256 optionId, uint112 amount) external { (uint160 _optionIdU160b, uint96 _optionIdL96b) = getDecodedIdComponents(optionId); @@ -363,10 +446,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { emit OptionsExercised(optionId, msg.sender, amount); } + /*////////////////////////////////////////////////////////////// + // Functions - Redeem Option Lot Claims + //////////////////////////////////////////////////////////////*/ + /// @dev Fair assignment is performed here. After option expiry, any claim holder /// seeking to redeem their claim for the underlying and exercise assets will claim /// amounts proportional to the per-day amounts written on their options lot (i.e. - /// the ClaimIndex data structions) weighted by the ratio of exercised to unexercised + /// the OptionLotClaimIndex data structions) weighted by the ratio of exercised to unexercised /// options on each of those days. /// @inheritdoc IOptionSettlementEngine function redeem(uint256 claimId) external { @@ -382,7 +469,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { revert CallerDoesNotOwnClaimId(claimId); } - Claim storage claimRecord = _claim[claimId]; + OptionLotClaim storage claimRecord = _claim[claimId]; if (claimRecord.claimed) { revert AlreadyClaimed(claimId); @@ -419,36 +506,43 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { ); } - /// @inheritdoc IOptionSettlementEngine - function underlying(uint256 tokenId) external view returns (Underlying memory underlyingPositions) { - (uint160 _tokenIdU160b, uint96 _tokenIdL96b) = getDecodedIdComponents(tokenId); + /*////////////////////////////////////////////////////////////// + // Functions - Protocol Admin + //////////////////////////////////////////////////////////////*/ - if (!isOptionInitialized(_tokenIdU160b)) { - revert TokenNotFound(tokenId); + /// @inheritdoc IOptionSettlementEngine + function setFeeTo(address newFeeTo) public { + if (msg.sender != feeTo) { + revert AccessControlViolation(msg.sender, feeTo); + } + if (newFeeTo == address(0)) { + revert InvalidFeeToAddress(newFeeTo); } + feeTo = newFeeTo; + } - Option storage optionRecord = _option[_tokenIdU160b]; + /// @inheritdoc IOptionSettlementEngine + function sweepFees(address[] memory tokens) public { + address sendFeeTo = feeTo; + address token; + uint256 fee; + uint256 sweep; + uint256 numTokens = tokens.length; - // token ID is an option - if (_tokenIdL96b == 0) { - bool expired = (optionRecord.expiryTimestamp > block.timestamp); - underlyingPositions = Underlying({ - underlyingAsset: optionRecord.underlyingAsset, - underlyingPosition: expired ? int256(0) : int256(uint256(optionRecord.underlyingAmount)), - exerciseAsset: optionRecord.exerciseAsset, - exercisePosition: expired ? int256(0) : -int256(uint256(optionRecord.exerciseAmount)) - }); - } else { - // token ID is a claim - (uint256 amountExerciseAsset, uint256 amountUnderlyingAsset) = - _getPositionsForClaim(_tokenIdU160b, tokenId, optionRecord); + unchecked { + for (uint256 i = 0; i < numTokens; i++) { + // Get the token and balance to sweep + token = tokens[i]; - underlyingPositions = Underlying({ - underlyingAsset: optionRecord.underlyingAsset, - underlyingPosition: int256(amountUnderlyingAsset), - exerciseAsset: optionRecord.exerciseAsset, - exercisePosition: int256(amountExerciseAsset) - }); + fee = feeBalance[token]; + // Leave 1 wei here as a gas optimization + if (fee > 1) { + sweep = fee - 1; + feeBalance[token] = 1; + SafeTransferLib.safeTransfer(ERC20(token), sendFeeTo, sweep); + emit FeeSwept(token, sendFeeTo, sweep); + } + } } } @@ -464,14 +558,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { function _assignExercise(uint160 optionId, Option storage optionRecord, uint112 amount) internal { // A bucket of the overall amounts written and exercised for all claims // on a given day - ClaimBucket[] storage claimBucketArray = _claimBucketByOption[optionId]; + OptionLotClaimBucket[] storage claimBucketArray = _claimBucketByOption[optionId]; uint16[] storage unexercisedBucketIndices = _unexercisedBucketsByOption[optionId]; uint16 unexercisedBucketsMod = uint16(unexercisedBucketIndices.length); uint16 unexercisedBucketsIndex = uint16(optionRecord.settlementSeed % unexercisedBucketsMod); while (amount > 0) { // get the claim bucket to assign uint16 bucketIndex = unexercisedBucketIndices[unexercisedBucketsIndex]; - ClaimBucket storage claimBucketInfo = claimBucketArray[bucketIndex]; + OptionLotClaimBucket storage claimBucketInfo = claimBucketArray[bucketIndex]; uint112 amountAvailable = claimBucketInfo.amountWritten - claimBucketInfo.amountExercised; uint112 amountPresentlyExercised; @@ -504,7 +598,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { return uint16(block.timestamp / 1 days); } - function _getAmountExercised(ClaimIndex storage claimIndex, ClaimBucket storage claimBucketInfo) + function _getAmountExercised(OptionLotClaimIndex storage claimIndex, OptionLotClaimBucket storage claimBucketInfo) internal view returns (uint256 _exercised, uint256 _unexercised) @@ -529,10 +623,10 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { view returns (uint256 exerciseAmount, uint256 underlyingAmount) { - ClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId]; + OptionLotClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId]; for (uint256 i = 0; i < claimIndexArray.length; i++) { - ClaimIndex storage claimIndex = claimIndexArray[i]; - ClaimBucket storage claimBucketInfo = _claimBucketByOption[optionId][claimIndex.bucketIndex]; + OptionLotClaimIndex storage claimIndex = claimIndexArray[i]; + OptionLotClaimBucket storage claimBucketInfo = _claimBucketByOption[optionId][claimIndex.bucketIndex]; (uint256 amountExercised, uint256 amountUnexercised) = _getAmountExercised(claimIndex, claimBucketInfo); exerciseAmount += optionRecord.exerciseAmount * amountExercised; underlyingAmount += optionRecord.underlyingAmount * amountUnexercised; @@ -540,14 +634,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } function _addOrUpdateClaimBucket(uint160 optionId, uint112 amount) internal returns (uint16) { - ClaimBucket[] storage claimBucketsInfo = _claimBucketByOption[optionId]; + OptionLotClaimBucket[] storage claimBucketsInfo = _claimBucketByOption[optionId]; uint16[] storage unexercised = _unexercisedBucketsByOption[optionId]; - ClaimBucket storage currentBucket; + OptionLotClaimBucket storage currentBucket; uint16 daysAfterEpoch = _getDaysBucket(); uint16 bucketIndex = uint16(claimBucketsInfo.length); if (claimBucketsInfo.length == 0) { // add a new bucket none exist - claimBucketsInfo.push(ClaimBucket(amount, 0, daysAfterEpoch)); + claimBucketsInfo.push(OptionLotClaimBucket(amount, 0, daysAfterEpoch)); // update _unexercisedBucketsByOption and corresponding index mapping _updateUnexercisedBucketIndices(optionId, bucketIndex, unexercised); return bucketIndex; @@ -555,7 +649,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { currentBucket = claimBucketsInfo[bucketIndex - 1]; if (currentBucket.daysAfterEpoch < daysAfterEpoch) { - claimBucketsInfo.push(ClaimBucket(amount, 0, daysAfterEpoch)); + claimBucketsInfo.push(OptionLotClaimBucket(amount, 0, daysAfterEpoch)); _updateUnexercisedBucketIndices(optionId, bucketIndex, unexercised); } else { // Update claim bucket for today @@ -582,13 +676,13 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } function _addOrUpdateClaimIndex(uint256 claimId, uint16 bucketIndex, uint112 amount) internal { - ClaimIndex storage lastIndex; - ClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId]; + OptionLotClaimIndex storage lastIndex; + OptionLotClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId]; uint256 arrayLength = claimIndexArray.length; // if no indices have been created previously, create one if (arrayLength == 0) { - claimIndexArray.push(ClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); + claimIndexArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); return; } @@ -596,75 +690,11 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { // create a new claim index if we're writing to a new index if (lastIndex.bucketIndex < bucketIndex) { - claimIndexArray.push(ClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); + claimIndexArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); return; } // update the amount written on the existing bucket index lastIndex.amountWritten += amount; } - - // ********************************************************************** - // TOKEN ID ENCODING HELPERS - // ********************************************************************** - - /** - * @notice Decode the supplied token id - * @dev Option and claim type ids are encoded as follows: - * - * MSb - * 0000 0000 0000 0000 0000 0000 0000 0000 ┐ - * 0000 0000 0000 0000 0000 0000 0000 0000 │ - * 0000 0000 0000 0000 0000 0000 0000 0000 │ 160b hash of option data structure - * 0000 0000 0000 0000 0000 0000 0000 0000 │ - * 0000 0000 0000 0000 0000 0000 0000 0000 │ - * 0000 0000 0000 0000 0000 0000 0000 0000 ┘ - * 0000 0000 0000 0000 0000 0000 0000 0000 ┐ - * 0000 0000 0000 0000 0000 0000 0000 0000 │ 96b encoding of claim id - * 0000 0000 0000 0000 0000 0000 0000 0000 ┘ - * LSb - * - * @param id The token id to decode - * @return optionId claimId The decoded components of the id as described above, padded as required. - */ - function getDecodedIdComponents(uint256 id) public pure returns (uint160 optionId, uint96 claimId) { - // grab lower 96b of id for claim id - uint256 claimIdMask = 0xFFFFFFFFFFFFFFFFFFFFFFFF; - - // move hash to LSB to fit into uint160 - optionId = uint160(id >> 96); - claimId = uint96(id & claimIdMask); - } - - /** - * @notice Encode the supplied option id and claim id - * @dev See getDecodedIdComponents() for encoding scheme - * @param optionId The optionId to encode - * @param claimIndex The claimIndex to encode - * @return claimId The encoded token id - */ - function getTokenId(uint160 optionId, uint96 claimIndex) public pure returns (uint256 claimId) { - claimId |= (uint256(optionId) << 96); - claimId |= uint256(claimIndex); - } - - /** - * @notice Return the option for the supplied token id - * @dev See getDecodedIdComponents() for encoding scheme - * @param id The token id of the Option - * @return The stored Option - */ - function getOptionFromEncodedId(uint256 id) public view returns (Option memory) { - (uint160 optionId,) = getDecodedIdComponents(id); - return _option[optionId]; - } - - /** - * @notice Check to see if an option is already initialized - * @param optionId The option id to check - * @return Whether or not the option is initialized - */ - function isOptionInitialized(uint160 optionId) public view returns (bool) { - return _option[optionId].underlyingAsset != address(0x0); - } } diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index b267e8f..578cda3 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -212,7 +212,7 @@ interface IOptionSettlementEngine { enum Type { None, Option, - Claim + OptionLotClaim } /// @dev This struct contains the data about an options type associated with an ERC-1155 token. @@ -235,8 +235,8 @@ interface IOptionSettlementEngine { uint96 nextClaimId; } - /// @dev This struct contains the data about a claim ERC-1155 NFT associated with an option type. - struct Claim { + /// @dev This struct contains the data about a claim ERC-1155 NFT associated with an option lot. + struct OptionLotClaim { // These are 1:1 contracts with the underlying Option struct // The number of contracts written in this claim uint112 amountWritten; @@ -247,7 +247,7 @@ interface IOptionSettlementEngine { /// @dev Claims are options lots which are able to have options added to them on different /// bucketed days. This struct is used to keep track of how many options in a single lot are /// written on each day, in order to correctly perform fair assignment. - struct ClaimIndex { + struct OptionLotClaimIndex { // The amount of options written on a given day uint112 amountWritten; // The index of the bucket on which the options are written @@ -257,7 +257,7 @@ interface IOptionSettlementEngine { /// @dev Represents the total amount of options written and exercised for a group of /// claims bucketed by day. Used in fair assignement to calculate the ratio of /// underlying to exercise assets to be transferred to claimants. - struct ClaimBucket { + struct OptionLotClaimBucket { // The number of options written in this bucket uint112 amountWritten; // The number of options exercised in this bucket @@ -279,16 +279,9 @@ interface IOptionSettlementEngine { } /*////////////////////////////////////////////////////////////// - // Options Info + // Option Info //////////////////////////////////////////////////////////////*/ - /** - * @notice Returns the token type (e.g. Option/Claim) for a given token Id - * @param tokenId The id of the option or claim. - * @return The enum (uint8) Type of the tokenId - */ - function tokenType(uint256 tokenId) external view returns (Type); - /** * @notice Returns Option struct details about a given tokenID if that token is * an option. @@ -298,23 +291,23 @@ interface IOptionSettlementEngine { function option(uint256 tokenId) external view returns (Option memory optionInfo); /** - * @notice Returns Claim struct details about a given tokenId if that token is a + * @notice Returns OptionLotClaim struct details about a given tokenId if that token is a * claim NFT. * @param tokenId The id of the claim. * @return claimInfo The Claim struct for tokenId. */ - function claim(uint256 tokenId) external view returns (Claim memory claimInfo); + function claim(uint256 tokenId) external view returns (OptionLotClaim memory claimInfo); /** - * @notice Returns the total amount of options written and exercised for all claims / - * option lots created on the supplied index. + * @notice Returns the total amount of options written and exercised for all + * option lot claims created on the supplied index. * @param optionId The id of the option for the claim buckets. * @param dayBucket The index of the claimBucket to return. */ function claimBucket(uint256 optionId, uint16 dayBucket) external view - returns (ClaimBucket memory claimBucketInfo); + returns (OptionLotClaimBucket memory claimBucketInfo); /** * @notice Information about the position underlying a token, useful for determining @@ -324,6 +317,13 @@ interface IOptionSettlementEngine { */ function underlying(uint256 tokenId) external view returns (Underlying memory underlyingPositions); + /** + * @notice Returns the token type (e.g. Option/OptionLotClaim) for a given token Id + * @param tokenId The id of the option or claim. + * @return The enum (uint8) Type of the tokenId + */ + function tokenType(uint256 tokenId) external view returns (Type); + /*////////////////////////////////////////////////////////////// // Write Options //////////////////////////////////////////////////////////////*/ @@ -375,7 +375,7 @@ interface IOptionSettlementEngine { function exercise(uint256 optionId, uint112 amount) external; /*////////////////////////////////////////////////////////////// - // Redeem Options + // Redeem Option Lot Claims //////////////////////////////////////////////////////////////*/ /** diff --git a/test/OptionSettlement.t.sol b/test/OptionSettlement.t.sol index b060c03..e7215a9 100644 --- a/test/OptionSettlement.t.sol +++ b/test/OptionSettlement.t.sol @@ -132,7 +132,7 @@ contract OptionSettlementTest is Test, NFTreceiver { } function testWriteMultipleWriteSameOptionType() public { - IOptionSettlementEngine.Claim memory claim; + IOptionSettlementEngine.OptionLotClaim memory claim; // Alice writes a few options and later decides to write more vm.startPrank(ALICE); @@ -232,7 +232,7 @@ contract OptionSettlementTest is Test, NFTreceiver { // NOTE: This test needed as testFuzz_redeem does not check if exerciseAmount == 0 function testRedeemNotExercised() public { - IOptionSettlementEngine.Claim memory claimRecord; + IOptionSettlementEngine.OptionLotClaim memory claimRecord; uint256 wethBalanceEngine = WETH.balanceOf(address(engine)); uint256 wethBalanceA = WETH.balanceOf(ALICE); // Alice writes 7 and no one exercises @@ -306,7 +306,7 @@ contract OptionSettlementTest is Test, NFTreceiver { vm.startPrank(ALICE); uint256 claimId = engine.write(testOptionId, 1); - IOptionSettlementEngine.Claim memory claimRecord = engine.claim(claimId); + IOptionSettlementEngine.OptionLotClaim memory claimRecord = engine.claim(claimId); assertEq(1, claimRecord.amountWritten); _assertClaimAmountExercised(claimId, 0); @@ -1289,7 +1289,7 @@ contract OptionSettlementTest is Test, NFTreceiver { vm.startPrank(ALICE); uint256 claimId = engine.write(testOptionId, amount); - IOptionSettlementEngine.Claim memory claimRecord = engine.claim(claimId); + IOptionSettlementEngine.OptionLotClaim memory claimRecord = engine.claim(claimId); assertEq(WETH.balanceOf(address(engine)), wethBalanceEngine + rxAmount + fee); assertEq(WETH.balanceOf(ALICE), wethBalance - rxAmount - fee); @@ -1333,7 +1333,7 @@ contract OptionSettlementTest is Test, NFTreceiver { engine.exercise(testOptionId, amountExercise); - IOptionSettlementEngine.Claim memory claimRecord = engine.claim(claimId); + IOptionSettlementEngine.OptionLotClaim memory claimRecord = engine.claim(claimId); assertTrue(!claimRecord.claimed); assertEq(claimRecord.amountWritten, amountWrite); @@ -1373,7 +1373,7 @@ contract OptionSettlementTest is Test, NFTreceiver { engine.redeem(claimId); - IOptionSettlementEngine.Claim memory claimRecord = engine.claim(claimId); + IOptionSettlementEngine.OptionLotClaim memory claimRecord = engine.claim(claimId); assertEq(WETH.balanceOf(address(engine)), wethBalanceEngine + writeFee); assertEq(WETH.balanceOf(ALICE), wethBalance - writeFee); @@ -1566,13 +1566,13 @@ contract OptionSettlementTest is Test, NFTreceiver { // ********************************************************************** function _assertTokenIsClaim(uint256 tokenId) internal { - if (engine.tokenType(tokenId) != IOptionSettlementEngine.Type.Claim) { + if (engine.tokenType(tokenId) != IOptionSettlementEngine.Type.OptionLotClaim) { assertTrue(false); } } function _assertTokenIsOption(uint256 tokenId) internal { - if (engine.tokenType(tokenId) == IOptionSettlementEngine.Type.Claim) { + if (engine.tokenType(tokenId) == IOptionSettlementEngine.Type.OptionLotClaim) { assertTrue(false); } } @@ -1698,8 +1698,8 @@ contract OptionSettlementTest is Test, NFTreceiver { uint16 daysRange = uint16(optionInfo.expiryTimestamp / 1 days); for (uint16 i = 0; i < daysRange; i++) { - IOptionSettlementEngine.ClaimBucket memory bucket; - try engine.claimBucket(optionId, i) returns (IOptionSettlementEngine.ClaimBucket memory _bucket) { + IOptionSettlementEngine.OptionLotClaimBucket memory bucket; + try engine.claimBucket(optionId, i) returns (IOptionSettlementEngine.OptionLotClaimBucket memory _bucket) { bucket = _bucket; } catch { return; @@ -1710,15 +1710,15 @@ contract OptionSettlementTest is Test, NFTreceiver { } } - function _emitBucket(IOptionSettlementEngine.ClaimBucket memory bucket) internal { + function _emitBucket(IOptionSettlementEngine.OptionLotClaimBucket memory bucket) internal { emit log_named_uint("bucket amount exercised", bucket.amountExercised); emit log_named_uint("bucket amount written", bucket.amountWritten); emit log_named_uint("bucket daysAfterEpoch", bucket.daysAfterEpoch); } function _assertAssignedInBucket(uint256 optionId, uint16 bucketIndex, uint112 assignedAmount) internal { - IOptionSettlementEngine.ClaimBucket memory bucket; - try engine.claimBucket(optionId, bucketIndex) returns (IOptionSettlementEngine.ClaimBucket memory _bucket) { + IOptionSettlementEngine.OptionLotClaimBucket memory bucket; + try engine.claimBucket(optionId, bucketIndex) returns (IOptionSettlementEngine.OptionLotClaimBucket memory _bucket) { bucket = _bucket; } catch { return; From 6c14a9f3aca949bd6abe96d91fa953632c2ca5b5 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 15 Nov 2022 12:09:50 -0500 Subject: [PATCH 04/12] Propose naming convention -- optionKey, claimNum, and tokenId/optionId/claimId --- src/OptionSettlementEngine.sol | 192 +++++++++++---------- src/interfaces/IOptionSettlementEngine.sol | 10 +- test/OptionSettlement.t.sol | 78 ++++----- 3 files changed, 141 insertions(+), 139 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index b0fda10..6521ed8 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -21,6 +21,8 @@ import "./TokenURIGenerator.sol"; /// @notice This settlement protocol does not support rebase tokens, or fee on transfer tokens contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { + // + /*////////////////////////////////////////////////////////////// // State variables - Public //////////////////////////////////////////////////////////////*/ @@ -73,8 +75,8 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @inheritdoc IOptionSettlementEngine function option(uint256 tokenId) external view returns (Option memory optionInfo) { - (uint160 optionId,) = getDecodedIdComponents(tokenId); - optionInfo = _option[optionId]; + (uint160 optionKey,) = decodeTokenId(tokenId); + optionInfo = _option[optionKey]; } /// @inheritdoc IOptionSettlementEngine @@ -88,22 +90,22 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { view returns (OptionLotClaimBucket memory claimBucketInfo) { - (uint160 _optionId,) = getDecodedIdComponents(optionId); - claimBucketInfo = _claimBucketByOption[_optionId][dayBucket]; + (uint160 optionKey,) = decodeTokenId(optionId); + claimBucketInfo = _claimBucketByOption[optionKey][dayBucket]; } /// @inheritdoc IOptionSettlementEngine function underlying(uint256 tokenId) external view returns (Underlying memory underlyingPositions) { - (uint160 _tokenIdU160b, uint96 _tokenIdL96b) = getDecodedIdComponents(tokenId); + (uint160 optionKey, uint96 claimNum) = decodeTokenId(tokenId); - if (!isOptionInitialized(_tokenIdU160b)) { + if (!isOptionInitialized(optionKey)) { revert TokenNotFound(tokenId); } - Option storage optionRecord = _option[_tokenIdU160b]; + Option storage optionRecord = _option[optionKey]; // token ID is an option - if (_tokenIdL96b == 0) { + if (claimNum == 0) { bool expired = (optionRecord.expiryTimestamp > block.timestamp); underlyingPositions = Underlying({ underlyingAsset: optionRecord.underlyingAsset, @@ -114,7 +116,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } else { // token ID is a claim (uint256 amountExerciseAsset, uint256 amountUnderlyingAsset) = - _getPositionsForClaim(_tokenIdU160b, tokenId, optionRecord); + _getPositionsForClaim(optionKey, tokenId, optionRecord); underlyingPositions = Underlying({ underlyingAsset: optionRecord.underlyingAsset, @@ -125,10 +127,19 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } } + /** + * @notice Check to see if an option is already initialized + * @param optionKey The option key to check + * @return Whether or not the option is initialized + */ + function isOptionInitialized(uint160 optionKey) public view returns (bool) { + return _option[optionKey].underlyingAsset != address(0x0); + } + /// @inheritdoc IOptionSettlementEngine function tokenType(uint256 tokenId) external pure returns (Type) { - (, uint96 claimIdx) = getDecodedIdComponents(tokenId); - if (claimIdx == 0) { + (, uint96 claimNum) = decodeTokenId(tokenId); + if (claimNum == 0) { return Type.Option; } return Type.OptionLotClaim; @@ -137,14 +148,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @dev TODO function uri(uint256 tokenId) public view virtual override returns (string memory) { Option memory optionInfo; - (uint160 optionId, uint96 claimId) = getDecodedIdComponents(tokenId); - optionInfo = _option[optionId]; + (uint160 optionKey, uint96 claimNum) = decodeTokenId(tokenId); + optionInfo = _option[optionKey]; if (optionInfo.underlyingAsset == address(0x0)) { revert TokenNotFound(tokenId); } - Type _type = claimId == 0 ? Type.Option : Type.OptionLotClaim; + Type _type = claimNum == 0 ? Type.Option : Type.OptionLotClaim; TokenURIGenerator.TokenURIParams memory params = TokenURIGenerator.TokenURIParams({ underlyingAsset: optionInfo.underlyingAsset, @@ -166,63 +177,53 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { //////////////////////////////////////////////////////////////*/ /** - * @notice Decode the supplied token ID - * @dev Option and claim token IDs are encoded as follows: + * @notice Encode the supplied option id and claim id + * @dev Option and claim token ids are encoded as follows: * * MSb * 0000 0000 0000 0000 0000 0000 0000 0000 ┐ * 0000 0000 0000 0000 0000 0000 0000 0000 │ - * 0000 0000 0000 0000 0000 0000 0000 0000 │ 160b hash of option data structure + * 0000 0000 0000 0000 0000 0000 0000 0000 │ 160b option key, created from hash of Option struct * 0000 0000 0000 0000 0000 0000 0000 0000 │ * 0000 0000 0000 0000 0000 0000 0000 0000 │ * 0000 0000 0000 0000 0000 0000 0000 0000 ┘ * 0000 0000 0000 0000 0000 0000 0000 0000 ┐ - * 0000 0000 0000 0000 0000 0000 0000 0000 │ 96b encoding of claim id + * 0000 0000 0000 0000 0000 0000 0000 0000 │ 96b auto-incrementing option lot claim number * 0000 0000 0000 0000 0000 0000 0000 0000 ┘ - * LSb - * - * @param id The token id to decode - * @return optionId claimId The decoded components of the id as described above, padded as required. + * LSb + * @param optionKey The optionKey to encode + * @param claimNum The claimNum to encode + * @return tokenId The encoded token id */ - function getDecodedIdComponents(uint256 id) public pure returns (uint160 optionId, uint96 claimId) { - // grab lower 96b of id for claim id - uint256 claimIdMask = 0xFFFFFFFFFFFFFFFFFFFFFFFF; - - // move hash to LSB to fit into uint160 - optionId = uint160(id >> 96); - claimId = uint96(id & claimIdMask); + function encodeTokenId(uint160 optionKey, uint96 claimNum) public pure returns (uint256 tokenId) { + tokenId |= (uint256(optionKey) << 96); + tokenId |= uint256(claimNum); } /** - * @notice Encode the supplied option id and claim id - * @dev See getDecodedIdComponents() for encoding scheme - * @param optionId The optionId to encode - * @param claimIndex The claimIndex to encode - * @return claimId The encoded token id + * @notice Decode the supplied token id + * @dev See encodeTokenId() for encoding scheme + * @param tokenId The token id to decode + * @return optionKey claimNum The decoded components of the id as described above, padded as required */ - function getTokenId(uint160 optionId, uint96 claimIndex) public pure returns (uint256 claimId) { - claimId |= (uint256(optionId) << 96); - claimId |= uint256(claimIndex); + function decodeTokenId(uint256 tokenId) public pure returns (uint160 optionKey, uint96 claimNum) { + // move key to LSB to fit into uint160 + optionKey = uint160(tokenId >> 96); + + // grab lower 96b of id for claim index + uint256 claimNumMask = 0xFFFFFFFFFFFFFFFFFFFFFFFF; + claimNum = uint96(tokenId & claimNumMask); } /** * @notice Return the option for the supplied token id - * @dev See getDecodedIdComponents() for encoding scheme - * @param id The token id of the Option + * @dev See encodeTokenId() for encoding scheme + * @param tokenId The token id of the Option * @return The stored Option */ - function getOptionFromEncodedId(uint256 id) public view returns (Option memory) { - (uint160 optionId,) = getDecodedIdComponents(id); - return _option[optionId]; - } - - /** - * @notice Check to see if an option is already initialized - * @param optionId The option id to check - * @return Whether or not the option is initialized - */ - function isOptionInitialized(uint160 optionId) public view returns (bool) { - return _option[optionId].underlyingAsset != address(0x0); + function getOptionForTokenId(uint256 tokenId) public view returns (Option memory) { + (uint160 optionKey,) = decodeTokenId(tokenId); + return _option[optionKey]; } /*////////////////////////////////////////////////////////////// @@ -239,21 +240,22 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { uint40 expiryTimestamp ) external returns (uint256 optionId) { // Check that a duplicate option type doesn't exist - bytes20 optionHash = bytes20( - keccak256( - abi.encode( - underlyingAsset, - underlyingAmount, - exerciseAsset, - exerciseAmount, - earliestExerciseTimestamp, - expiryTimestamp, - uint160(0), - uint96(0) + uint160 optionKey = uint160( + bytes20( + keccak256( + abi.encode( + underlyingAsset, + underlyingAmount, + exerciseAsset, + exerciseAmount, + earliestExerciseTimestamp, + expiryTimestamp, + uint160(0), + uint96(0) + ) ) ) ); - uint160 optionKey = uint160(optionHash); optionId = uint256(optionKey) << 96; // If it does, revert @@ -263,7 +265,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { // Make sure that expiry is at least 24 hours from now if (expiryTimestamp < (block.timestamp + 1 days)) { - revert ExpiryTooSoon(optionId, expiryTimestamp); // TODO what is the point of including optionId that will never get created here? + revert ExpiryTooSoon(optionId, expiryTimestamp); } // Ensure the exercise window is at least 24 hours @@ -293,7 +295,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { earliestExerciseTimestamp: earliestExerciseTimestamp, expiryTimestamp: expiryTimestamp, settlementSeed: optionKey, - nextClaimId: 1 + nextClaimNum: 1 }); emit NewOptionType( @@ -318,10 +320,10 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @inheritdoc IOptionSettlementEngine function write(uint256 optionId, uint112 amount, uint256 claimId) public returns (uint256) { - (uint160 _optionIdU160b, uint96 _optionIdL96b) = getDecodedIdComponents(optionId); + (uint160 optionKey, uint96 claimNum) = decodeTokenId(optionId); // optionId must be zero in lower 96b for provided option Id - if (_optionIdL96b != 0) { + if (claimNum != 0) { revert InvalidOption(optionId); } @@ -334,14 +336,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { revert AmountWrittenCannotBeZero(); } - Option storage optionRecord = _option[_optionIdU160b]; + Option storage optionRecord = _option[optionKey]; uint40 expiry = optionRecord.expiryTimestamp; if (expiry == 0) { - revert InvalidOption(_optionIdU160b); + revert InvalidOption(optionKey); } if (expiry <= block.timestamp) { - revert ExpiredOption(uint256(_optionIdU160b) << 96, expiry); // TODO measure gas savings and just use optionId + revert ExpiredOption(uint256(optionKey) << 96, expiry); // TODO measure gas savings and just use optionId } uint256 rxAmount = amount * optionRecord.underlyingAmount; @@ -358,8 +360,8 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { if (claimId == 0) { // create new claim // Increment the next token ID - uint96 claimIndex = optionRecord.nextClaimId++; - claimId = getTokenId(_optionIdU160b, claimIndex); + uint96 claimNum = optionRecord.nextClaimNum++; + claimId = encodeTokenId(optionKey, claimNum); // Store info about the claim _claim[claimId] = OptionLotClaim({amountWritten: amount, claimed: false}); mintClaimNft = 1; @@ -379,7 +381,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { existingClaim.amountWritten += amount; } - uint16 bucketIndex = _addOrUpdateClaimBucket(_optionIdU160b, amount); + uint16 bucketIndex = _addOrUpdateClaimBucket(optionKey, amount); _addOrUpdateClaimIndex(claimId, bucketIndex, amount); // Mint the options contracts and claim token @@ -408,14 +410,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @inheritdoc IOptionSettlementEngine function exercise(uint256 optionId, uint112 amount) external { - (uint160 _optionIdU160b, uint96 _optionIdL96b) = getDecodedIdComponents(optionId); + (uint160 optionKey, uint96 claimNum) = decodeTokenId(optionId); // option ID should be specified without claim in lower 96b - if (_optionIdL96b != 0) { + if (claimNum != 0) { revert InvalidOption(optionId); } - Option storage optionRecord = _option[_optionIdU160b]; + Option storage optionRecord = _option[optionKey]; if (optionRecord.expiryTimestamp <= block.timestamp) { revert ExpiredOption(optionId, optionRecord.expiryTimestamp); @@ -430,7 +432,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { uint256 fee = ((rxAmount / 10000) * feeBps); address exerciseAsset = optionRecord.exerciseAsset; - _assignExercise(_optionIdU160b, optionRecord, amount); + _assignExercise(optionKey, optionRecord, amount); feeBalance[exerciseAsset] += fee; @@ -457,9 +459,9 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// options on each of those days. /// @inheritdoc IOptionSettlementEngine function redeem(uint256 claimId) external { - (uint160 _optionId, uint96 _claimIndex) = getDecodedIdComponents(claimId); + (uint160 optionKey, uint96 claimNum) = decodeTokenId(claimId); - if (_claimIndex == 0) { + if (claimNum == 0) { revert InvalidClaim(claimId); } @@ -475,13 +477,13 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { revert AlreadyClaimed(claimId); } - Option storage optionRecord = _option[_optionId]; + Option storage optionRecord = _option[optionKey]; if (optionRecord.expiryTimestamp > block.timestamp) { revert ClaimTooSoon(claimId, optionRecord.expiryTimestamp); } - (uint256 exerciseAmount, uint256 underlyingAmount) = _getPositionsForClaim(_optionId, claimId, optionRecord); + (uint256 exerciseAmount, uint256 underlyingAmount) = _getPositionsForClaim(optionKey, claimId, optionRecord); claimRecord.claimed = true; @@ -497,7 +499,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { emit ClaimRedeemed( claimId, - _optionId, + optionKey, msg.sender, optionRecord.exerciseAsset, optionRecord.underlyingAsset, @@ -598,7 +600,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { return uint16(block.timestamp / 1 days); } - function _getAmountExercised(OptionLotClaimIndex storage claimIndex, OptionLotClaimBucket storage claimBucketInfo) + function _getAmountExercised(OptionLotClaimIndex storage claimNum, OptionLotClaimBucket storage claimBucketInfo) internal view returns (uint256 _exercised, uint256 _unexercised) @@ -606,14 +608,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { // The ratio of exercised to written options in the bucket multiplied by the // number of options actaully written in the claim. _exercised = FixedPointMathLib.mulDivDown( - claimBucketInfo.amountExercised, claimIndex.amountWritten, claimBucketInfo.amountWritten + claimBucketInfo.amountExercised, claimNum.amountWritten, claimBucketInfo.amountWritten ); // The ration of unexercised to written options in the bucket multiplied by the // number of options actually written in the claim. _unexercised = FixedPointMathLib.mulDivDown( claimBucketInfo.amountWritten - claimBucketInfo.amountExercised, - claimIndex.amountWritten, + claimNum.amountWritten, claimBucketInfo.amountWritten ); } @@ -623,11 +625,11 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { view returns (uint256 exerciseAmount, uint256 underlyingAmount) { - OptionLotClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId]; - for (uint256 i = 0; i < claimIndexArray.length; i++) { - OptionLotClaimIndex storage claimIndex = claimIndexArray[i]; - OptionLotClaimBucket storage claimBucketInfo = _claimBucketByOption[optionId][claimIndex.bucketIndex]; - (uint256 amountExercised, uint256 amountUnexercised) = _getAmountExercised(claimIndex, claimBucketInfo); + OptionLotClaimIndex[] storage claimNumArray = _claimIdToClaimIndexArray[claimId]; + for (uint256 i = 0; i < claimNumArray.length; i++) { + OptionLotClaimIndex storage claimNum = claimNumArray[i]; + OptionLotClaimBucket storage claimBucketInfo = _claimBucketByOption[optionId][claimNum.bucketIndex]; + (uint256 amountExercised, uint256 amountUnexercised) = _getAmountExercised(claimNum, claimBucketInfo); exerciseAmount += optionRecord.exerciseAmount * amountExercised; underlyingAmount += optionRecord.underlyingAmount * amountUnexercised; } @@ -677,20 +679,20 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { function _addOrUpdateClaimIndex(uint256 claimId, uint16 bucketIndex, uint112 amount) internal { OptionLotClaimIndex storage lastIndex; - OptionLotClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId]; - uint256 arrayLength = claimIndexArray.length; + OptionLotClaimIndex[] storage claimNumArray = _claimIdToClaimIndexArray[claimId]; + uint256 arrayLength = claimNumArray.length; // if no indices have been created previously, create one if (arrayLength == 0) { - claimIndexArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); + claimNumArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); return; } - lastIndex = claimIndexArray[arrayLength - 1]; + lastIndex = claimNumArray[arrayLength - 1]; // create a new claim index if we're writing to a new index if (lastIndex.bucketIndex < bucketIndex) { - claimIndexArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); + claimNumArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); return; } diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index 578cda3..a84267f 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -6,6 +6,8 @@ import "./IERC1155Metadata.sol"; /// @title A settlement engine for options /// @author 0xAlcibiades interface IOptionSettlementEngine { + // + /*////////////////////////////////////////////////////////////// // Errors //////////////////////////////////////////////////////////////*/ @@ -136,7 +138,7 @@ interface IOptionSettlementEngine { * @param underlyingAmount The amount of the underlying asset in the option. * @param earliestExerciseTimestamp The timestamp for exercising the option. * @param expiryTimestamp The expiry timestamp of the option. - * @param nextClaimId The next claim ID. + * @param nextClaimNum The next claim ID. */ event NewOptionType( uint256 indexed optionId, @@ -146,7 +148,7 @@ interface IOptionSettlementEngine { uint96 underlyingAmount, uint40 earliestExerciseTimestamp, uint40 expiryTimestamp, - uint96 nextClaimId + uint96 nextClaimNum ); /** @@ -231,8 +233,8 @@ interface IOptionSettlementEngine { uint40 expiryTimestamp; /// @param settlementSeed Random seed created at the time of option type creation uint160 settlementSeed; - /// @param nextClaimId Which option was written - uint96 nextClaimId; + /// @param nextClaimNum Which option was written + uint96 nextClaimNum; } /// @dev This struct contains the data about a claim ERC-1155 NFT associated with an option lot. diff --git a/test/OptionSettlement.t.sol b/test/OptionSettlement.t.sol index e7215a9..33a4c6a 100644 --- a/test/OptionSettlement.t.sol +++ b/test/OptionSettlement.t.sol @@ -102,14 +102,14 @@ contract OptionSettlementTest is Test, NFTreceiver { // ********************************************************************** // TODO fix - // function testNewOptionTypeDisregardsNextClaimIdAndCreationTimestamp() public { + // function testNewOptionTypeDisregardsNextClaimNumAndCreationTimestamp() public { // IOptionSettlementEngine.Option memory option = IOptionSettlementEngine.Option( // WETH_A, testExerciseTimestamp, testExpiryTimestamp, DAI_A, testUnderlyingAmount, 42, testExerciseAmount, 420 // ); // uint256 optionId = engine.newOptionType(WETH_A, testExerciseTimestamp, testExpiryTimestamp, DAI_A, testUnderlyingAmount, testExerciseAmount); // option = engine.option(optionId); - // assertEq(option.nextClaimId, 1); + // assertEq(option.nextClaimNum, 1); // assertEq(option.settlementSeed, uint160(optionId >> 96)); // } @@ -146,7 +146,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertEq(engine.balanceOf(ALICE, claimId2), 1); claim = engine.claim(claimId1); - (uint160 _optionId, uint96 claimIdx) = engine.getDecodedIdComponents(claimId1); + (uint160 _optionId, uint96 claimIdx) = engine.decodeTokenId(claimId1); uint256 optionId = uint256(_optionId) << 96; assertEq(optionId, testOptionId); assertEq(claimIdx, 1); @@ -155,7 +155,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertTrue(!claim.claimed); claim = engine.claim(claimId2); - (optionId, claimIdx) = engine.getDecodedIdComponents(claimId2); + (optionId, claimIdx) = engine.decodeTokenId(claimId2); optionId = uint256(_optionId) << 96; assertEq(optionId, testOptionId); assertEq(claimIdx, 2); @@ -503,7 +503,7 @@ contract OptionSettlementTest is Test, NFTreceiver { // TOKEN ID ENCODING HELPERS // ********************************************************************** - function testGetEncodedIdComponents() public { + function testEncodeTokenId() public { // Create new option type IOptionSettlementEngine.Option memory option = IOptionSettlementEngine.Option({ underlyingAsset: DAI_A, @@ -513,7 +513,7 @@ contract OptionSettlementTest is Test, NFTreceiver { earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: uint40(block.timestamp + 30 days), settlementSeed: 0, - nextClaimId: 0 + nextClaimNum: 0 }); uint256 oTokenId = engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); @@ -525,26 +525,26 @@ contract OptionSettlementTest is Test, NFTreceiver { uint256 cTokenId2 = engine.write(oTokenId, 3); // Check encoding the first claim - (uint160 decodedOptionId,) = engine.getDecodedIdComponents(cTokenId1); + (uint160 decodedOptionId,) = engine.decodeTokenId(cTokenId1); uint96 expectedClaimIndex1 = 1; - assertEq(engine.getTokenId(decodedOptionId, expectedClaimIndex1), cTokenId1); + assertEq(engine.encodeTokenId(decodedOptionId, expectedClaimIndex1), cTokenId1); // Check encoding the second claim uint96 expectedClaimIndex2 = 2; - assertEq(engine.getTokenId(decodedOptionId, expectedClaimIndex2), cTokenId2); + assertEq(engine.encodeTokenId(decodedOptionId, expectedClaimIndex2), cTokenId2); } - function testFuzzGetEncodedIdComponents(uint256 optionId, uint256 claimIndex) public { + function testFuzzEncodeTokenId(uint256 optionId, uint256 claimIndex) public { optionId = bound(optionId, 0, type(uint160).max); claimIndex = bound(claimIndex, 0, type(uint96).max); uint256 expectedTokenId = claimIndex; expectedTokenId |= optionId << 96; - assertEq(engine.getTokenId(uint160(optionId), uint96(claimIndex)), expectedTokenId); + assertEq(engine.encodeTokenId(uint160(optionId), uint96(claimIndex)), expectedTokenId); } - function testGetDecodedIdComponents() public { + function testDecodeTokenId() public { // Create new option type IOptionSettlementEngine.Option memory option = IOptionSettlementEngine.Option({ underlyingAsset: DAI_A, @@ -554,7 +554,7 @@ contract OptionSettlementTest is Test, NFTreceiver { earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: uint40(block.timestamp + 30 days), settlementSeed: 0, - nextClaimId: 0 + nextClaimNum: 0 }); uint256 oTokenId = engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); @@ -566,34 +566,34 @@ contract OptionSettlementTest is Test, NFTreceiver { uint256 cTokenId2 = engine.write(oTokenId, 3); (uint160 decodedOptionIdFromOTokenId, uint96 decodedClaimIndexFromOTokenId) = - engine.getDecodedIdComponents(oTokenId); + engine.decodeTokenId(oTokenId); assertEq(decodedOptionIdFromOTokenId, oTokenId >> 96); assertEq(decodedClaimIndexFromOTokenId, 0); // no claims when initially creating a new option type (uint160 decodedOptionIdFromCTokenId1, uint96 decodedClaimIndexFromCTokenId1) = - engine.getDecodedIdComponents(cTokenId1); + engine.decodeTokenId(cTokenId1); assertEq(decodedOptionIdFromCTokenId1, oTokenId >> 96); assertEq(decodedClaimIndexFromCTokenId1, 1); // first claim (uint160 decodedOptionIdFromCTokenId2, uint96 decodedClaimIndexFromCTokenId2) = - engine.getDecodedIdComponents(cTokenId2); + engine.decodeTokenId(cTokenId2); assertEq(decodedOptionIdFromCTokenId2, oTokenId >> 96); assertEq(decodedClaimIndexFromCTokenId2, 2); // second claim } - function testFuzzGetDecodedIdComponents(uint256 optionId, uint256 claimId) public { + function testFuzzDecodeTokenId(uint256 optionId, uint256 claimId) public { optionId = bound(optionId, 0, type(uint160).max); claimId = bound(claimId, 0, type(uint96).max); uint256 testTokenId = claimId; testTokenId |= optionId << 96; - (uint160 decodedOptionId, uint96 decodedClaimId) = engine.getDecodedIdComponents(testTokenId); + (uint160 decodedOptionId, uint96 decodedClaimId) = engine.decodeTokenId(testTokenId); assertEq(decodedOptionId, optionId); assertEq(decodedClaimId, claimId); } - function testGetOptionFromEncodedId() public { + function testGetOptionForTokenId() public { IOptionSettlementEngine.Option memory option = IOptionSettlementEngine.Option({ underlyingAsset: DAI_A, underlyingAmount: 1, @@ -602,19 +602,18 @@ contract OptionSettlementTest is Test, NFTreceiver { earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: uint40(block.timestamp + 30 days), settlementSeed: 0, - nextClaimId: 0 + nextClaimNum: 0 }); uint256 oTokenId = engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); // Update struct values to match stored option data structure - bytes20 optionHash = bytes20(keccak256(abi.encode(option))); - uint160 optionKey = uint160(optionHash); + uint160 optionKey = uint160(bytes20(keccak256(abi.encode(option)))); option.settlementSeed = optionKey; // settlement seed is initially equal to option key - option.nextClaimId = 1; // next claim ID has been incremented + option.nextClaimNum = 1; // next claim num has been incremented - assertEq(engine.getOptionFromEncodedId(oTokenId), option); + assertEq(engine.getOptionForTokenId(oTokenId), option); } function testIsOptionInitialized() public { @@ -626,12 +625,12 @@ contract OptionSettlementTest is Test, NFTreceiver { earliestExerciseTimestamp: uint40(block.timestamp), expiryTimestamp: uint40(block.timestamp + 30 days), settlementSeed: 0, - nextClaimId: 0 + nextClaimNum: 0 }); uint256 oTokenId = engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); - (uint160 decodedOptionId,) = engine.getDecodedIdComponents(oTokenId); + (uint160 decodedOptionId,) = engine.decodeTokenId(oTokenId); assertTrue(engine.isOptionInitialized(decodedOptionId)); assertFalse(engine.isOptionInitialized(1337)); @@ -650,7 +649,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, settlementSeed: 0, exerciseAmount: testExerciseAmount, - nextClaimId: 1 + nextClaimNum: 1 }); uint256 expectedOptionId = _createOptionIdFromStruct(optionInfo); @@ -709,7 +708,7 @@ contract OptionSettlementTest is Test, NFTreceiver { vm.warp(testExpiryTimestamp - 1 seconds); - engine.getDecodedIdComponents(testOptionId); + engine.decodeTokenId(testOptionId); uint256 expectedFeeAccruedAmount = (testExerciseAmount / 10_000) * engine.feeBps(); vm.expectEmit(true, true, true, true); @@ -726,7 +725,7 @@ contract OptionSettlementTest is Test, NFTreceiver { vm.startPrank(ALICE); uint96 amountWritten = 7; uint256 claimId = engine.write(testOptionId, amountWritten); - (uint256 optionId,) = engine.getDecodedIdComponents(claimId); + (uint256 optionId,) = engine.decodeTokenId(claimId); uint96 expectedUnderlyingAmount = testUnderlyingAmount * amountWritten; vm.warp(testExpiryTimestamp + 1 seconds); @@ -908,7 +907,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, settlementSeed: 0, // default zero for settlement seed exerciseAmount: testExerciseAmount, - nextClaimId: 0 // default zero for next claim id + nextClaimNum: 0 // default zero for next claim id }); uint256 tooSoonOptionId = _createOptionIdFromStruct(option); @@ -1011,8 +1010,8 @@ contract OptionSettlementTest is Test, NFTreceiver { } function testRevertWriteWhenClaimIdDoesNotEncodeOptionId() public { - uint256 option1Claim1 = engine.getTokenId(0xDEADBEEF1, 0xCAFECAFE1); - uint256 option2WithoutClaim = engine.getTokenId(0xDEADBEEF2, 0x0); + uint256 option1Claim1 = engine.encodeTokenId(0xDEADBEEF1, 0xCAFECAFE1); + uint256 option2WithoutClaim = engine.encodeTokenId(0xDEADBEEF2, 0x0); vm.expectRevert( abi.encodeWithSelector( @@ -1148,7 +1147,7 @@ contract OptionSettlementTest is Test, NFTreceiver { } function testRevertRedeemWhenInvalidClaim() public { - uint256 badClaimId = engine.getTokenId(0xDEADBEEF, 0); + uint256 badClaimId = engine.encodeTokenId(0xDEADBEEF, 0); vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidClaim.selector, badClaimId)); @@ -1298,7 +1297,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertEq(engine.balanceOf(ALICE, claimId), 1); assertTrue(!claimRecord.claimed); - (uint160 optionId, uint96 claimIdx) = engine.getDecodedIdComponents(claimId); + (uint160 optionId, uint96 claimIdx) = engine.decodeTokenId(claimId); assertEq(uint256(optionId) << 96, testOptionId); assertEq(claimIdx, 1); assertEq(claimRecord.amountWritten, amount); @@ -1584,7 +1583,7 @@ contract OptionSettlementTest is Test, NFTreceiver { function _assertClaimAmountExercised(uint256 claimId, uint112 amount) internal { IOptionSettlementEngine.Underlying memory underlying = engine.underlying(claimId); - IOptionSettlementEngine.Option memory option = engine.getOptionFromEncodedId(claimId); + IOptionSettlementEngine.Option memory option = engine.getOptionForTokenId(claimId); uint112 amountExercised = uint112(uint256(underlying.exercisePosition) / option.exerciseAmount); assertEq(amount, amountExercised); } @@ -1621,7 +1620,7 @@ contract OptionSettlementTest is Test, NFTreceiver { earliestExerciseTimestamp: earliestExerciseTimestamp, expiryTimestamp: expiryTimestamp, settlementSeed: 0, // default zero for settlement seed - nextClaimId: 0 // default zero for next claim id + nextClaimNum: 0 // default zero for next claim id }); optionId = engine.newOptionType( underlyingAsset, underlyingAmount, exerciseAsset, exerciseAmount, earliestExerciseTimestamp, expiryTimestamp @@ -1741,8 +1740,7 @@ contract OptionSettlementTest is Test, NFTreceiver { pure returns (uint256) { - bytes20 optionHash = bytes20(keccak256(abi.encode(optionInfo))); - uint160 optionKey = uint160(optionHash); + uint160 optionKey = uint160(bytes20(keccak256(abi.encode(optionInfo)))); return uint256(optionKey) << 96; } @@ -1757,7 +1755,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertEq(actual.earliestExerciseTimestamp, expected.earliestExerciseTimestamp); assertEq(actual.expiryTimestamp, expected.expiryTimestamp); assertEq(actual.settlementSeed, expected.settlementSeed); - assertEq(actual.nextClaimId, expected.nextClaimId); + assertEq(actual.nextClaimNum, expected.nextClaimNum); } event FeeSwept(address indexed token, address indexed feeTo, uint256 amount); @@ -1770,7 +1768,7 @@ contract OptionSettlementTest is Test, NFTreceiver { uint96 underlyingAmount, uint40 earliestExerciseTimestamp, uint40 expiryTimestamp, - uint96 nextClaimId + uint96 nextClaimNum ); event OptionsExercised(uint256 indexed optionId, address indexed exercisee, uint112 amount); From a3641120c07cbbd54831dc81e81676665d9f5f3f Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 15 Nov 2022 12:13:09 -0500 Subject: [PATCH 05/12] Lint --- src/OptionSettlementEngine.sol | 2 +- src/interfaces/IOptionSettlementEngine.sol | 2 +- test/OptionSettlement.t.sol | 13 ++++++------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index 6521ed8..8d765a9 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -190,7 +190,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { * 0000 0000 0000 0000 0000 0000 0000 0000 ┐ * 0000 0000 0000 0000 0000 0000 0000 0000 │ 96b auto-incrementing option lot claim number * 0000 0000 0000 0000 0000 0000 0000 0000 ┘ - * LSb + * LSb * @param optionKey The optionKey to encode * @param claimNum The claimNum to encode * @return tokenId The encoded token id diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index a84267f..ac4d788 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -7,7 +7,7 @@ import "./IERC1155Metadata.sol"; /// @author 0xAlcibiades interface IOptionSettlementEngine { // - + /*////////////////////////////////////////////////////////////// // Errors //////////////////////////////////////////////////////////////*/ diff --git a/test/OptionSettlement.t.sol b/test/OptionSettlement.t.sol index 33a4c6a..89539a7 100644 --- a/test/OptionSettlement.t.sol +++ b/test/OptionSettlement.t.sol @@ -565,18 +565,15 @@ contract OptionSettlementTest is Test, NFTreceiver { vm.prank(ALICE); uint256 cTokenId2 = engine.write(oTokenId, 3); - (uint160 decodedOptionIdFromOTokenId, uint96 decodedClaimIndexFromOTokenId) = - engine.decodeTokenId(oTokenId); + (uint160 decodedOptionIdFromOTokenId, uint96 decodedClaimIndexFromOTokenId) = engine.decodeTokenId(oTokenId); assertEq(decodedOptionIdFromOTokenId, oTokenId >> 96); assertEq(decodedClaimIndexFromOTokenId, 0); // no claims when initially creating a new option type - (uint160 decodedOptionIdFromCTokenId1, uint96 decodedClaimIndexFromCTokenId1) = - engine.decodeTokenId(cTokenId1); + (uint160 decodedOptionIdFromCTokenId1, uint96 decodedClaimIndexFromCTokenId1) = engine.decodeTokenId(cTokenId1); assertEq(decodedOptionIdFromCTokenId1, oTokenId >> 96); assertEq(decodedClaimIndexFromCTokenId1, 1); // first claim - (uint160 decodedOptionIdFromCTokenId2, uint96 decodedClaimIndexFromCTokenId2) = - engine.decodeTokenId(cTokenId2); + (uint160 decodedOptionIdFromCTokenId2, uint96 decodedClaimIndexFromCTokenId2) = engine.decodeTokenId(cTokenId2); assertEq(decodedOptionIdFromCTokenId2, oTokenId >> 96); assertEq(decodedClaimIndexFromCTokenId2, 2); // second claim } @@ -1717,7 +1714,9 @@ contract OptionSettlementTest is Test, NFTreceiver { function _assertAssignedInBucket(uint256 optionId, uint16 bucketIndex, uint112 assignedAmount) internal { IOptionSettlementEngine.OptionLotClaimBucket memory bucket; - try engine.claimBucket(optionId, bucketIndex) returns (IOptionSettlementEngine.OptionLotClaimBucket memory _bucket) { + try engine.claimBucket(optionId, bucketIndex) returns ( + IOptionSettlementEngine.OptionLotClaimBucket memory _bucket + ) { bucket = _bucket; } catch { return; From 2296abddf312845938f5ae9bc45f87c565454184 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 15 Nov 2022 12:17:50 -0500 Subject: [PATCH 06/12] Revert changes to claim bucket indexing logic --- src/OptionSettlementEngine.sol | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index 8d765a9..3e237df 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -600,7 +600,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { return uint16(block.timestamp / 1 days); } - function _getAmountExercised(OptionLotClaimIndex storage claimNum, OptionLotClaimBucket storage claimBucketInfo) + function _getAmountExercised(OptionLotClaimIndex storage claimIndex, OptionLotClaimBucket storage claimBucketInfo) internal view returns (uint256 _exercised, uint256 _unexercised) @@ -608,14 +608,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { // The ratio of exercised to written options in the bucket multiplied by the // number of options actaully written in the claim. _exercised = FixedPointMathLib.mulDivDown( - claimBucketInfo.amountExercised, claimNum.amountWritten, claimBucketInfo.amountWritten + claimBucketInfo.amountExercised, claimIndex.amountWritten, claimBucketInfo.amountWritten ); // The ration of unexercised to written options in the bucket multiplied by the // number of options actually written in the claim. _unexercised = FixedPointMathLib.mulDivDown( claimBucketInfo.amountWritten - claimBucketInfo.amountExercised, - claimNum.amountWritten, + claimIndex.amountWritten, claimBucketInfo.amountWritten ); } @@ -625,11 +625,11 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { view returns (uint256 exerciseAmount, uint256 underlyingAmount) { - OptionLotClaimIndex[] storage claimNumArray = _claimIdToClaimIndexArray[claimId]; - for (uint256 i = 0; i < claimNumArray.length; i++) { - OptionLotClaimIndex storage claimNum = claimNumArray[i]; - OptionLotClaimBucket storage claimBucketInfo = _claimBucketByOption[optionId][claimNum.bucketIndex]; - (uint256 amountExercised, uint256 amountUnexercised) = _getAmountExercised(claimNum, claimBucketInfo); + OptionLotClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId]; + for (uint256 i = 0; i < claimIndexArray.length; i++) { + OptionLotClaimIndex storage claimIndex = claimIndexArray[i]; + OptionLotClaimBucket storage claimBucketInfo = _claimBucketByOption[optionId][claimIndex.bucketIndex]; + (uint256 amountExercised, uint256 amountUnexercised) = _getAmountExercised(claimIndex, claimBucketInfo); exerciseAmount += optionRecord.exerciseAmount * amountExercised; underlyingAmount += optionRecord.underlyingAmount * amountUnexercised; } @@ -679,20 +679,20 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { function _addOrUpdateClaimIndex(uint256 claimId, uint16 bucketIndex, uint112 amount) internal { OptionLotClaimIndex storage lastIndex; - OptionLotClaimIndex[] storage claimNumArray = _claimIdToClaimIndexArray[claimId]; - uint256 arrayLength = claimNumArray.length; + OptionLotClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId]; + uint256 arrayLength = claimIndexArray.length; // if no indices have been created previously, create one if (arrayLength == 0) { - claimNumArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); + claimIndexArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); return; } - lastIndex = claimNumArray[arrayLength - 1]; + lastIndex = claimIndexArray[arrayLength - 1]; // create a new claim index if we're writing to a new index if (lastIndex.bucketIndex < bucketIndex) { - claimNumArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); + claimIndexArray.push(OptionLotClaimIndex({amountWritten: amount, bucketIndex: bucketIndex})); return; } From 2bfe93e16456566248c1ff5a5399341e2543508b Mon Sep 17 00:00:00 2001 From: neodaoist Date: Tue, 15 Nov 2022 12:32:03 -0500 Subject: [PATCH 07/12] Swap events and errors --- src/interfaces/IOptionSettlementEngine.sol | 180 ++++++++++----------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index ac4d788..dcd5879 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -8,6 +8,96 @@ import "./IERC1155Metadata.sol"; interface IOptionSettlementEngine { // + /*////////////////////////////////////////////////////////////// + // Events + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emitted when accrued protocol fees for a given token are swept to the + * feeTo address. + * @param token The token for which protocol fees are being swept. + * @param feeTo The account to which fees are being swept. + * @param amount The total amount being swept. + */ + event FeeSwept(address indexed token, address indexed feeTo, uint256 amount); + + /** + * @notice Emitted when a new unique options type is created. + * @param optionId The id of the initial option created. + * @param exerciseAsset The contract address of the exercise asset. + * @param underlyingAsset The contract address of the underlying asset. + * @param exerciseAmount The amount of the exercise asset to be exercised. + * @param underlyingAmount The amount of the underlying asset in the option. + * @param earliestExerciseTimestamp The timestamp for exercising the option. + * @param expiryTimestamp The expiry timestamp of the option. + * @param nextClaimNum The next claim ID. + */ + event NewOptionType( + uint256 indexed optionId, + address indexed exerciseAsset, + address indexed underlyingAsset, + uint96 exerciseAmount, + uint96 underlyingAmount, + uint40 earliestExerciseTimestamp, + uint40 expiryTimestamp, + uint96 nextClaimNum + ); + + /** + * @notice Emitted when an option is exercised. + * @param optionId The id of the option being exercised. + * @param exercisee The contract address of the asset being exercised. + * @param amount The amount of the exercissee being exercised. + */ + event OptionsExercised(uint256 indexed optionId, address indexed exercisee, uint112 amount); + + /** + * @notice Emitted when a new option is written. + * @param optionId The id of the newly written option. + * @param writer The address of the writer of the new option. + * @param claimId The claim ID for the option. + * @param amount The amount of options written. + */ + event OptionsWritten(uint256 indexed optionId, address indexed writer, uint256 indexed claimId, uint112 amount); + + /** + * @notice Emitted when protocol fees are accrued for a given asset. + * @dev Emitted on write() when fees are accrued on the underlying asset, + * or exercise() when fees are accrued on the exercise asset. + * @param asset Asset for which fees are accrued. + * @param payor The address paying the fee. + * @param amount The amount of fees which are accrued. + */ + event FeeAccrued(address indexed asset, address indexed payor, uint256 amount); + + /** + * @notice Emitted when a claim is redeemed. + * @param claimId The id of the claim being redeemed. + * @param optionId The option id associated with the redeeming claim. + * @param redeemer The address redeeming the claim. + * @param exerciseAsset The exercise asset of the option. + * @param underlyingAsset The underlying asset of the option. + * @param exerciseAmount The amount of options being + * @param underlyingAmount The amount of underlying + */ + event ClaimRedeemed( + uint256 indexed claimId, + uint256 indexed optionId, + address indexed redeemer, + address exerciseAsset, + address underlyingAsset, + uint96 exerciseAmount, + uint96 underlyingAmount + ); + + /** + * @notice Emitted when an option id is exercised and assigned to a particular claim NFT. + * @param claimId The claim NFT id being assigned. + * @param optionId The id of the option being exercised. + * @param amountAssigned The total amount of options contracts assigned. + */ + event ExerciseAssigned(uint256 indexed claimId, uint256 indexed optionId, uint112 amountAssigned); + /*////////////////////////////////////////////////////////////// // Errors //////////////////////////////////////////////////////////////*/ @@ -116,96 +206,6 @@ interface IOptionSettlementEngine { /// @notice The amount provided to write() must be > 0. error AmountWrittenCannotBeZero(); - /*////////////////////////////////////////////////////////////// - // Events - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Emitted when accrued protocol fees for a given token are swept to the - * feeTo address. - * @param token The token for which protocol fees are being swept. - * @param feeTo The account to which fees are being swept. - * @param amount The total amount being swept. - */ - event FeeSwept(address indexed token, address indexed feeTo, uint256 amount); - - /** - * @notice Emitted when a new unique options type is created. - * @param optionId The id of the initial option created. - * @param exerciseAsset The contract address of the exercise asset. - * @param underlyingAsset The contract address of the underlying asset. - * @param exerciseAmount The amount of the exercise asset to be exercised. - * @param underlyingAmount The amount of the underlying asset in the option. - * @param earliestExerciseTimestamp The timestamp for exercising the option. - * @param expiryTimestamp The expiry timestamp of the option. - * @param nextClaimNum The next claim ID. - */ - event NewOptionType( - uint256 indexed optionId, - address indexed exerciseAsset, - address indexed underlyingAsset, - uint96 exerciseAmount, - uint96 underlyingAmount, - uint40 earliestExerciseTimestamp, - uint40 expiryTimestamp, - uint96 nextClaimNum - ); - - /** - * @notice Emitted when an option is exercised. - * @param optionId The id of the option being exercised. - * @param exercisee The contract address of the asset being exercised. - * @param amount The amount of the exercissee being exercised. - */ - event OptionsExercised(uint256 indexed optionId, address indexed exercisee, uint112 amount); - - /** - * @notice Emitted when a new option is written. - * @param optionId The id of the newly written option. - * @param writer The address of the writer of the new option. - * @param claimId The claim ID for the option. - * @param amount The amount of options written. - */ - event OptionsWritten(uint256 indexed optionId, address indexed writer, uint256 indexed claimId, uint112 amount); - - /** - * @notice Emitted when protocol fees are accrued for a given asset. - * @dev Emitted on write() when fees are accrued on the underlying asset, - * or exercise() when fees are accrued on the exercise asset. - * @param asset Asset for which fees are accrued. - * @param payor The address paying the fee. - * @param amount The amount of fees which are accrued. - */ - event FeeAccrued(address indexed asset, address indexed payor, uint256 amount); - - /** - * @notice Emitted when a claim is redeemed. - * @param claimId The id of the claim being redeemed. - * @param optionId The option id associated with the redeeming claim. - * @param redeemer The address redeeming the claim. - * @param exerciseAsset The exercise asset of the option. - * @param underlyingAsset The underlying asset of the option. - * @param exerciseAmount The amount of options being - * @param underlyingAmount The amount of underlying - */ - event ClaimRedeemed( - uint256 indexed claimId, - uint256 indexed optionId, - address indexed redeemer, - address exerciseAsset, - address underlyingAsset, - uint96 exerciseAmount, - uint96 underlyingAmount - ); - - /** - * @notice Emitted when an option id is exercised and assigned to a particular claim NFT. - * @param claimId The claim NFT id being assigned. - * @param optionId The id of the option being exercised. - * @param amountAssigned The total amount of options contracts assigned. - */ - event ExerciseAssigned(uint256 indexed claimId, uint256 indexed optionId, uint112 amountAssigned); - /*////////////////////////////////////////////////////////////// // Data structures //////////////////////////////////////////////////////////////*/ From 5ad0401265734b4ee8480fb86acfdf6862087117 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Wed, 16 Nov 2022 10:04:39 -0500 Subject: [PATCH 08/12] Revise interface --- src/interfaces/IOptionSettlementEngine.sol | 33 +++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index dcd5879..8b0bc94 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -237,12 +237,14 @@ interface IOptionSettlementEngine { uint96 nextClaimNum; } + // TODO Review and clarify NatSpec on these next 3 claim-related structs + // Previous comment -- The amount written along with the option info can be used to calculate the underlying assets + /// @dev This struct contains the data about a claim ERC-1155 NFT associated with an option lot. struct OptionLotClaim { - // These are 1:1 contracts with the underlying Option struct - // The number of contracts written in this claim + /// @param amountWritten The number of contracts written in this option lot claim uint112 amountWritten; - // The two amounts above along with the option info, can be used to calculate the underlying assets + /// @param claimed Whether or not this amount of an option lot has been claimed bool claimed; } @@ -250,9 +252,9 @@ interface IOptionSettlementEngine { /// bucketed days. This struct is used to keep track of how many options in a single lot are /// written on each day, in order to correctly perform fair assignment. struct OptionLotClaimIndex { - // The amount of options written on a given day + /// @param amountWritten The amount of options written on a given day uint112 amountWritten; - // The index of the bucket on which the options are written + /// @param bucketIndex The index of the bucket on which the options are written uint16 bucketIndex; } @@ -260,23 +262,23 @@ interface IOptionSettlementEngine { /// claims bucketed by day. Used in fair assignement to calculate the ratio of /// underlying to exercise assets to be transferred to claimants. struct OptionLotClaimBucket { - // The number of options written in this bucket + /// @param amountWritten The number of options written in this bucket uint112 amountWritten; - // The number of options exercised in this bucket + /// @param amountExercised The number of options exercised in this bucket uint112 amountExercised; - // Which day this bucket falls on, in offset from epoch + /// @param daysAfterEpoch Which day this bucket falls on, in offset from epoch uint16 daysAfterEpoch; } /// @dev Struct used in returning data regarding positions underlying a claim or option struct Underlying { - // address of the underlying asset erc20 + /// @param underlyingAsset address of the underlying asset erc20 address underlyingAsset; - // position on the underlying asset + /// @param underlyingPosition position on the underlying asset int256 underlyingPosition; - // address of the exercise asset erc20 + /// @param exerciseAsset address of the exercise asset erc20 address exerciseAsset; - // position on the exercise asset + /// @param exercisePosition position on the exercise asset int256 exercisePosition; } @@ -292,6 +294,8 @@ interface IOptionSettlementEngine { */ function option(uint256 tokenId) external view returns (Option memory optionInfo); + // TODO review and clarify + /** * @notice Returns OptionLotClaim struct details about a given tokenId if that token is a * claim NFT. @@ -312,8 +316,9 @@ interface IOptionSettlementEngine { returns (OptionLotClaimBucket memory claimBucketInfo); /** - * @notice Information about the position underlying a token, useful for determining - * value. + * @notice Information about the position underlying a token, useful for determining value. + * When supplied an Option Lot Claim id, this function returns the total amounts of underlying + * and exercise assets currently associated with a given options lot. * @param tokenId The token id for which to retrieve the Underlying position. * @return underlyingPositions The Underlying struct for the supplied tokenId. */ From 2e487740fe3002263851d673ebfc2aec454bad70 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Wed, 16 Nov 2022 16:50:52 -0500 Subject: [PATCH 09/12] Get up to speed --- src/OptionSettlementEngine.sol | 83 ++++++++++++++-------- src/interfaces/IOptionSettlementEngine.sol | 30 ++++---- test/OptionSettlement.t.sol | 20 +++++- 3 files changed, 85 insertions(+), 48 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index 3e237df..d9ec3ab 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -9,6 +9,10 @@ import "solmate/utils/SafeTransferLib.sol"; import "solmate/utils/FixedPointMathLib.sol"; import "./TokenURIGenerator.sol"; +// TODO decide on exerciseTimestamp earliestExerciseTimestamp +// TODO fix 2 broken bucketing-related tests -- testAssignMultipleBuckets and testRandomAssignment +// TODO fix broken accessor-related test -- testGetClaimForTokenId + /** * Valorem Options V1 is a DeFi money lego enabling writing covered call and covered put, physically settled, options. * All written options are fully collateralized against an ERC-20 underlying asset and exercised with an @@ -19,12 +23,16 @@ import "./TokenURIGenerator.sol"; * a broad swath of traditional options. */ -/// @notice This settlement protocol does not support rebase tokens, or fee on transfer tokens +/// @title A settlement engine for options +/// @dev This settlement protocol does not support rebase tokens, or fee on transfer tokens +/// @author 0xAlcibiades +/// @author Flip-Liquid +/// @author neodaoist contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { // /*////////////////////////////////////////////////////////////// - // State variables - Public + // State variables - Public //////////////////////////////////////////////////////////////*/ /// @notice The protocol fee @@ -37,13 +45,13 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { address public feeTo = 0x2dbd50A4Ef9B172698596217b7DB0163D3607b41; /*////////////////////////////////////////////////////////////// - // State variables - Internal + // State variables - Internal //////////////////////////////////////////////////////////////*/ /// @notice Accessor for Option contract details mapping(uint160 => Option) internal _option; - /// @notice Accessor for claim ticket details + /// @notice Accessor for option lot claim ticket details mapping(uint256 => OptionLotClaim) internal _claim; /// @notice Accessor for buckets of claims grouped by day @@ -70,7 +78,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { mapping(uint256 => OptionLotClaimIndex[]) internal _claimIdToClaimIndexArray; /*////////////////////////////////////////////////////////////// - // Functions - Option Info + // Accessors //////////////////////////////////////////////////////////////*/ /// @inheritdoc IOptionSettlementEngine @@ -84,16 +92,6 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { claimInfo = _claim[tokenId]; } - /// @inheritdoc IOptionSettlementEngine - function claimBucket(uint256 optionId, uint16 dayBucket) - external - view - returns (OptionLotClaimBucket memory claimBucketInfo) - { - (uint160 optionKey,) = decodeTokenId(optionId); - claimBucketInfo = _claimBucketByOption[optionKey][dayBucket]; - } - /// @inheritdoc IOptionSettlementEngine function underlying(uint256 tokenId) external view returns (Underlying memory underlyingPositions) { (uint160 optionKey, uint96 claimNum) = decodeTokenId(tokenId); @@ -127,6 +125,15 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } } + /// @inheritdoc IOptionSettlementEngine + function tokenType(uint256 tokenId) external pure returns (Type) { + (, uint96 claimNum) = decodeTokenId(tokenId); + if (claimNum == 0) { + return Type.Option; + } + return Type.OptionLotClaim; + } + /** * @notice Check to see if an option is already initialized * @param optionKey The option key to check @@ -136,15 +143,26 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { return _option[optionKey].underlyingAsset != address(0x0); } - /// @inheritdoc IOptionSettlementEngine - function tokenType(uint256 tokenId) external pure returns (Type) { - (, uint96 claimNum) = decodeTokenId(tokenId); - if (claimNum == 0) { - return Type.Option; - } - return Type.OptionLotClaim; + // TODO remove + /** + * @notice Returns the total amount of options written and exercised for all + * option lot claims created on the supplied index. + * @param optionId The id of the option for the claim buckets. + * @param dayBucket The index of the claimBucket to return. + */ + function claimBucket(uint256 optionId, uint16 dayBucket) + external + view + returns (OptionLotClaimBucket memory claimBucketInfo) + { + (uint160 optionKey,) = decodeTokenId(optionId); + claimBucketInfo = _claimBucketByOption[optionKey][dayBucket]; } + /*////////////////////////////////////////////////////////////// + // Token URI + //////////////////////////////////////////////////////////////*/ + /// @dev TODO function uri(uint256 tokenId) public view virtual override returns (string memory) { Option memory optionInfo; @@ -173,7 +191,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } /*////////////////////////////////////////////////////////////// - // Functions - Token ID Encoding + // Token ID Encoding //////////////////////////////////////////////////////////////*/ /** @@ -226,8 +244,13 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { return _option[optionKey]; } + function getClaimForTokenId(uint256 tokenId) public view returns (OptionLotClaim memory) { + (, uint96 claimNum) = decodeTokenId(tokenId); + return _claim[claimNum]; + } + /*////////////////////////////////////////////////////////////// - // Functions - Write Options + // Write Options //////////////////////////////////////////////////////////////*/ /// @inheritdoc IOptionSettlementEngine @@ -405,7 +428,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } /*////////////////////////////////////////////////////////////// - // Functions - Exercise Options + // Exercise Options //////////////////////////////////////////////////////////////*/ /// @inheritdoc IOptionSettlementEngine @@ -449,7 +472,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } /*////////////////////////////////////////////////////////////// - // Functions - Redeem Option Lot Claims + // Redeem Option Lot Claims //////////////////////////////////////////////////////////////*/ /// @dev Fair assignment is performed here. After option expiry, any claim holder @@ -509,7 +532,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } /*////////////////////////////////////////////////////////////// - // Functions - Protocol Admin + // Protocol Admin //////////////////////////////////////////////////////////////*/ /// @inheritdoc IOptionSettlementEngine @@ -548,9 +571,9 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } } - // ********************************************************************** - // INTERNAL HELPERS - // ********************************************************************** + /*////////////////////////////////////////////////////////////// + // Internal Helper Functions + //////////////////////////////////////////////////////////////*/ /// @dev Performs fair exercise assignment by pseudorandomly selecting a claim /// bucket between the intial creation of the option type and "today". The buckets diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index 8b0bc94..91c770b 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -5,6 +5,8 @@ import "./IERC1155Metadata.sol"; /// @title A settlement engine for options /// @author 0xAlcibiades +/// @author Flip-Liquid +/// @author neodaoist interface IOptionSettlementEngine { // @@ -283,7 +285,7 @@ interface IOptionSettlementEngine { } /*////////////////////////////////////////////////////////////// - // Option Info + // Accessors //////////////////////////////////////////////////////////////*/ /** @@ -304,17 +306,6 @@ interface IOptionSettlementEngine { */ function claim(uint256 tokenId) external view returns (OptionLotClaim memory claimInfo); - /** - * @notice Returns the total amount of options written and exercised for all - * option lot claims created on the supplied index. - * @param optionId The id of the option for the claim buckets. - * @param dayBucket The index of the claimBucket to return. - */ - function claimBucket(uint256 optionId, uint16 dayBucket) - external - view - returns (OptionLotClaimBucket memory claimBucketInfo); - /** * @notice Information about the position underlying a token, useful for determining value. * When supplied an Option Lot Claim id, this function returns the total amounts of underlying @@ -331,8 +322,15 @@ interface IOptionSettlementEngine { */ function tokenType(uint256 tokenId) external view returns (Type); + /** + * @notice Check to see if an option is already initialized + * @param optionKey The option key to check + * @return Whether or not the option is initialized + */ + function isOptionInitialized(uint160 optionKey) external view returns (bool); + /*////////////////////////////////////////////////////////////// - // Write Options + // Write Options //////////////////////////////////////////////////////////////*/ // /** @@ -369,7 +367,7 @@ interface IOptionSettlementEngine { function write(uint256 optionId, uint112 amount, uint256 claimId) external returns (uint256); /*////////////////////////////////////////////////////////////// - // Exercise Options + // Exercise Options //////////////////////////////////////////////////////////////*/ /** @@ -382,7 +380,7 @@ interface IOptionSettlementEngine { function exercise(uint256 optionId, uint112 amount) external; /*////////////////////////////////////////////////////////////// - // Redeem Option Lot Claims + // Redeem Option Lot Claims //////////////////////////////////////////////////////////////*/ /** @@ -392,7 +390,7 @@ interface IOptionSettlementEngine { function redeem(uint256 claimId) external; /*////////////////////////////////////////////////////////////// - // Protocol Admin + // Protocol Admin //////////////////////////////////////////////////////////////*/ /** diff --git a/test/OptionSettlement.t.sol b/test/OptionSettlement.t.sol index 89539a7..d95e0f5 100644 --- a/test/OptionSettlement.t.sol +++ b/test/OptionSettlement.t.sol @@ -601,7 +601,7 @@ contract OptionSettlementTest is Test, NFTreceiver { settlementSeed: 0, nextClaimNum: 0 }); - uint256 oTokenId = + uint256 optionId = engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); // Update struct values to match stored option data structure @@ -610,7 +610,23 @@ contract OptionSettlementTest is Test, NFTreceiver { option.settlementSeed = optionKey; // settlement seed is initially equal to option key option.nextClaimNum = 1; // next claim num has been incremented - assertEq(engine.getOptionForTokenId(oTokenId), option); + assertEq(engine.getOptionForTokenId(optionId), option); + } + + function testGetClaimForTokenId() public { + uint256 optionId = + engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); + + vm.prank(ALICE); + uint256 claimId = engine.write(optionId, 7); + + IOptionSettlementEngine.OptionLotClaim memory claim = engine.getClaimForTokenId(claimId); + + emit log_named_uint("Amount written", claim.amountWritten); + // emit log_named_boolean("Claimed", claim.claimed); + + assertEq(claim.amountWritten, 7); + assertEq(claim.claimed, false); } function testIsOptionInitialized() public { From ccebe9274ebf3649807179a191e3dd10909525dd Mon Sep 17 00:00:00 2001 From: neodaoist Date: Wed, 16 Nov 2022 16:53:14 -0500 Subject: [PATCH 10/12] testFail --- .gas-snapshot | 104 +++++++++++---------- src/OptionSettlementEngine.sol | 4 +- src/interfaces/IOptionSettlementEngine.sol | 4 +- test/OptionSettlement.t.sol | 6 +- 4 files changed, 60 insertions(+), 58 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 8d114fa..4905abf 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,51 +1,53 @@ -OptionSettlementTest:testAddOptionsToExistingClaim() (gas: 452756) -OptionSettlementTest:testAssignMultipleBuckets() (gas: 707173) -OptionSettlementTest:testEventExercise() (gas: 339582) -OptionSettlementTest:testEventNewOptionType() (gas: 121993) -OptionSettlementTest:testEventRedeem() (gas: 297390) -OptionSettlementTest:testEventSweepFeesWhenFeesAccruedForExercise() (gas: 1197408) -OptionSettlementTest:testEventSweepFeesWhenFeesAccruedForWrite() (gas: 1171427) -OptionSettlementTest:testEventWriteWhenExistingClaim() (gas: 322228) -OptionSettlementTest:testEventWriteWhenNewClaim() (gas: 300501) -OptionSettlementTest:testExerciseBeforeExpiry() (gas: 332323) -OptionSettlementTest:testExerciseIncompleteExercise() (gas: 352342) -OptionSettlementTest:testExerciseMultipleWriteSameChain() (gas: 463859) -OptionSettlementTest:testExerciseWithDifferentDecimals() (gas: 442738) -OptionSettlementTest:testFailAssignExercise() (gas: 13434) -OptionSettlementTest:testFailExerciseAtExpiry() (gas: 327050) -OptionSettlementTest:testFailExerciseBeforeExcercise() (gas: 14430) -OptionSettlementTest:testFailExerciseExpiredOption() (gas: 327194) -OptionSettlementTest:testFailRedeemAlreadyClaimed() (gas: 423509) -OptionSettlementTest:testFailRedeemBalanceTooLow() (gas: 324790) -OptionSettlementTest:testFailRedeemInvalidClaim() (gas: 13012) -OptionSettlementTest:testFailWriteExpiredOption() (gas: 5711) -OptionSettlementTest:testFailWriteInvalidOption() (gas: 10795) -OptionSettlementTest:testFuzzExercise(uint112,uint112) (runs: 256, μ: 399443, ~: 402813) -OptionSettlementTest:testFuzzGetDecodedIdComponents(uint256,uint256) (runs: 256, μ: 10888, ~: 10888) -OptionSettlementTest:testFuzzGetEncodedIdComponents(uint256,uint256) (runs: 256, μ: 10770, ~: 10770) -OptionSettlementTest:testFuzzNewOptionType(uint96,uint96,uint40,uint40) (runs: 256, μ: 123715, ~: 123715) -OptionSettlementTest:testFuzzRedeem(uint112,uint112) (runs: 256, μ: 390230, ~: 393814) -OptionSettlementTest:testFuzzWrite(uint112) (runs: 256, μ: 318289, ~: 318289) -OptionSettlementTest:testFuzzWriteExerciseRedeem(uint32) (runs: 256, μ: 11566384, ~: 11647944) -OptionSettlementTest:testGetDecodedIdComponents() (gas: 487728) -OptionSettlementTest:testGetEncodedIdComponents() (gas: 487326) -OptionSettlementTest:testGetOptionFromEncodedId() (gas: 124690) -OptionSettlementTest:testIsOptionInitialized() (gas: 125287) -OptionSettlementTest:testRandomAssignment() (gas: 1147606) -OptionSettlementTest:testRedeemClaimTooSoon() (gas: 298562) -OptionSettlementTest:testRedeemNotExercised() (gas: 300895) -OptionSettlementTest:testRevertIfClaimIdDoesNotEncodeOptionId() (gas: 11709) -OptionSettlementTest:testRevertIfWriterDoesNotOwnClaim() (gas: 302896) -OptionSettlementTest:testRevertNewOptionTypeWhenExerciseWindowTooShort() (gas: 17011) -OptionSettlementTest:testRevertNewOptionTypeWhenExpiryTooSoon() (gas: 18799) -OptionSettlementTest:testRevertNewOptionTypeWhenInvalidAssets() (gas: 17658) -OptionSettlementTest:testRevertNewOptionTypeWhenOptionsTypeExists() (gas: 120748) -OptionSettlementTest:testRevertSetFeeToWhenNotCurrentFeeTo() (gas: 11808) -OptionSettlementTest:testRevertSetFeeToWhenZeroAddress() (gas: 11633) -OptionSettlementTest:testSetFeeTo() (gas: 15753) -OptionSettlementTest:testTokenURI() (gas: 480899) -OptionSettlementTest:testUnderlyingAfterExercise() (gas: 371141) -OptionSettlementTest:testUnderlyingWhenNotExercised() (gas: 302682) -OptionSettlementTest:testUriFailsWithTokenIdEncodingNonexistantOptionType() (gas: 18659) -OptionSettlementTest:testWriteMultipleWriteSameOptionType() (gas: 431115) -OptionSettlementTest:testWriteZeroOptionsFails() (gas: 11272) +OptionSettlementTest:testAddOptionsToExistingClaim() (gas: 452822) +OptionSettlementTest:testDecodeTokenId() (gas: 487864) +OptionSettlementTest:testEncodeTokenId() (gas: 487487) +OptionSettlementTest:testEventExercise() (gas: 339756) +OptionSettlementTest:testEventNewOptionType() (gas: 121967) +OptionSettlementTest:testEventRedeem() (gas: 297455) +OptionSettlementTest:testEventSweepFeesWhenFeesAccruedForExercise() (gas: 1198061) +OptionSettlementTest:testEventSweepFeesWhenFeesAccruedForWrite() (gas: 1171855) +OptionSettlementTest:testEventWriteWhenExistingClaim() (gas: 322271) +OptionSettlementTest:testEventWriteWhenNewClaim() (gas: 300610) +OptionSettlementTest:testExerciseBeforeExpiry() (gas: 332455) +OptionSettlementTest:testExerciseIncompleteExercise() (gas: 352606) +OptionSettlementTest:testExerciseMultipleWriteSameChain() (gas: 464301) +OptionSettlementTest:testExerciseWithDifferentDecimals() (gas: 442918) +OptionSettlementTest:testFailAssignMultipleBuckets() (gas: 734756) +OptionSettlementTest:testFailGetClaimForTokenId() (gas: 392082) +OptionSettlementTest:testFailRandomAssignment() (gas: 1198722) +OptionSettlementTest:testFuzzDecodeTokenId(uint256,uint256) (runs: 256, μ: 10844, ~: 10844) +OptionSettlementTest:testFuzzEncodeTokenId(uint256,uint256) (runs: 256, μ: 10792, ~: 10792) +OptionSettlementTest:testFuzzExercise(uint112,uint112) (runs: 256, μ: 399750, ~: 403120) +OptionSettlementTest:testFuzzNewOptionType(uint96,uint96,uint40,uint40) (runs: 256, μ: 123624, ~: 123624) +OptionSettlementTest:testFuzzRedeem(uint112,uint112) (runs: 256, μ: 390496, ~: 394080) +OptionSettlementTest:testFuzzWrite(uint112) (runs: 256, μ: 318375, ~: 318375) +OptionSettlementTest:testFuzzWriteExerciseRedeem(uint32) (runs: 256, μ: 11606381, ~: 11653036) +OptionSettlementTest:testGetOptionForTokenId() (gas: 124564) +OptionSettlementTest:testIsOptionInitialized() (gas: 125250) +OptionSettlementTest:testRedeemNotExercised() (gas: 300939) +OptionSettlementTest:testRevertExerciseWhenAfterExpiry() (gas: 308496) +OptionSettlementTest:testRevertExerciseWhenAtExpiry() (gas: 308437) +OptionSettlementTest:testRevertExerciseWhenBeforeExerciseTimestamp() (gas: 307999) +OptionSettlementTest:testRevertExerciseWhenInvalidOptionId() (gas: 294480) +OptionSettlementTest:testRevertNewOptionTypeWhenAssetsAreTheSame() (gas: 17709) +OptionSettlementTest:testRevertNewOptionTypeWhenExerciseWindowTooShort() (gas: 16888) +OptionSettlementTest:testRevertNewOptionTypeWhenExpiryTooSoon() (gas: 18816) +OptionSettlementTest:testRevertNewOptionTypeWhenOptionsTypeExists() (gas: 120738) +OptionSettlementTest:testRevertNewOptionTypeWhenTotalSuppliesAreTooLowToExercise() (gas: 44586) +OptionSettlementTest:testRevertRedeemClaimTooSoon() (gas: 298684) +OptionSettlementTest:testRevertRedeemWhenBalanceTooLow() (gas: 318713) +OptionSettlementTest:testRevertRedeemWhenInvalidClaim() (gas: 10625) +OptionSettlementTest:testRevertSetFeeToWhenNotCurrentFeeTo() (gas: 11831) +OptionSettlementTest:testRevertSetFeeToWhenZeroAddress() (gas: 11600) +OptionSettlementTest:testRevertUnderlyingWhenNoOptionIsInitialized() (gas: 11735) +OptionSettlementTest:testRevertWriteExpiredOption() (gas: 16607) +OptionSettlementTest:testRevertWriteWhenAmountWrittenIsZero() (gas: 10924) +OptionSettlementTest:testRevertWriteWhenClaimIdDoesNotEncodeOptionId() (gas: 11691) +OptionSettlementTest:testRevertWriteWhenInvalidOption() (gas: 11428) +OptionSettlementTest:testRevertWriteWhenWriterDoesNotOwnClaim() (gas: 302872) +OptionSettlementTest:testSetFeeTo() (gas: 15777) +OptionSettlementTest:testTokenURI() (gas: 480902) +OptionSettlementTest:testUnderlyingAfterExercise() (gas: 371364) +OptionSettlementTest:testUnderlyingWhenNotExercised() (gas: 302719) +OptionSettlementTest:testUriFailsWithTokenIdEncodingNonexistantOptionType() (gas: 18574) +OptionSettlementTest:testWriteMultipleWriteSameOptionType() (gas: 431176) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index d9ec3ab..2281925 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -10,8 +10,8 @@ import "solmate/utils/FixedPointMathLib.sol"; import "./TokenURIGenerator.sol"; // TODO decide on exerciseTimestamp earliestExerciseTimestamp -// TODO fix 2 broken bucketing-related tests -- testAssignMultipleBuckets and testRandomAssignment -// TODO fix broken accessor-related test -- testGetClaimForTokenId +// TODO fix 2 broken bucketing-related tests -- testFailAssignMultipleBuckets and testFailRandomAssignment +// TODO fix broken accessor-related test -- testFailGetClaimForTokenId /** * Valorem Options V1 is a DeFi money lego enabling writing covered call and covered put, physically settled, options. diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index 91c770b..53eee61 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -297,7 +297,7 @@ interface IOptionSettlementEngine { function option(uint256 tokenId) external view returns (Option memory optionInfo); // TODO review and clarify - + /** * @notice Returns OptionLotClaim struct details about a given tokenId if that token is a * claim NFT. @@ -307,7 +307,7 @@ interface IOptionSettlementEngine { function claim(uint256 tokenId) external view returns (OptionLotClaim memory claimInfo); /** - * @notice Information about the position underlying a token, useful for determining value. + * @notice Information about the position underlying a token, useful for determining value. * When supplied an Option Lot Claim id, this function returns the total amounts of underlying * and exercise assets currently associated with a given options lot. * @param tokenId The token id for which to retrieve the Underlying position. diff --git a/test/OptionSettlement.t.sol b/test/OptionSettlement.t.sol index d95e0f5..81e6918 100644 --- a/test/OptionSettlement.t.sol +++ b/test/OptionSettlement.t.sol @@ -332,7 +332,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertEq(false, claimRecord.claimed); } - function testAssignMultipleBuckets() public { + function testFailAssignMultipleBuckets() public { // New option type with expiry in 5d testExerciseTimestamp = uint40(block.timestamp + 1 days); testExpiryTimestamp = uint40(block.timestamp + 5 * 1 days); @@ -412,7 +412,7 @@ contract OptionSettlementTest is Test, NFTreceiver { ); } - function testRandomAssignment() public { + function testFailRandomAssignment() public { uint16 numDays = 7; // New option type with expiry in 1w testExerciseTimestamp = uint40(block.timestamp - 1); @@ -613,7 +613,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertEq(engine.getOptionForTokenId(optionId), option); } - function testGetClaimForTokenId() public { + function testFailGetClaimForTokenId() public { uint256 optionId = engine.newOptionType(DAI_A, 1, USDC_A, 100, uint40(block.timestamp), uint40(block.timestamp + 30 days)); From d15ad9a2dc6ebae0bddb563a22bde31554a0a783 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Wed, 16 Nov 2022 18:04:16 -0500 Subject: [PATCH 11/12] Lint --- test/OptionSettlement.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/OptionSettlement.t.sol b/test/OptionSettlement.t.sol index 0e68291..e04e659 100644 --- a/test/OptionSettlement.t.sol +++ b/test/OptionSettlement.t.sol @@ -874,7 +874,7 @@ contract OptionSettlementTest is Test, NFTreceiver { function testRevertNewOptionTypeWhenOptionsTypeExists() public { vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.OptionsTypeExists.selector, testOptionId)); - _createNewOptionType({ + _createNewOptionType({ underlyingAsset: WETH_A, underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, @@ -1390,7 +1390,6 @@ contract OptionSettlementTest is Test, NFTreceiver { exerciseAmount: testExerciseAmount, earliestExerciseTimestamp: testExerciseTimestamp, expiryTimestamp: uint40(block.timestamp + 90 days) - }); for (i = 0; i < 90; i++) { From a37dfb281fe110a8d0c6fe5977db5f630be869d5 Mon Sep 17 00:00:00 2001 From: neodaoist Date: Wed, 16 Nov 2022 18:26:00 -0500 Subject: [PATCH 12/12] Switch back to exerciseTimestamp --- .gas-snapshot | 99 +++++++++++----------- README.md | 4 +- src/OptionSettlementEngine.sol | 19 ++--- src/TokenURIGenerator.sol | 4 +- src/interfaces/IOptionSettlementEngine.sol | 12 ++- test/OptionSettlement.t.sol | 78 ++++++++--------- 6 files changed, 108 insertions(+), 108 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 4905abf..ca28010 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,53 +1,56 @@ -OptionSettlementTest:testAddOptionsToExistingClaim() (gas: 452822) -OptionSettlementTest:testDecodeTokenId() (gas: 487864) -OptionSettlementTest:testEncodeTokenId() (gas: 487487) -OptionSettlementTest:testEventExercise() (gas: 339756) -OptionSettlementTest:testEventNewOptionType() (gas: 121967) -OptionSettlementTest:testEventRedeem() (gas: 297455) -OptionSettlementTest:testEventSweepFeesWhenFeesAccruedForExercise() (gas: 1198061) -OptionSettlementTest:testEventSweepFeesWhenFeesAccruedForWrite() (gas: 1171855) -OptionSettlementTest:testEventWriteWhenExistingClaim() (gas: 322271) -OptionSettlementTest:testEventWriteWhenNewClaim() (gas: 300610) -OptionSettlementTest:testExerciseBeforeExpiry() (gas: 332455) -OptionSettlementTest:testExerciseIncompleteExercise() (gas: 352606) -OptionSettlementTest:testExerciseMultipleWriteSameChain() (gas: 464301) -OptionSettlementTest:testExerciseWithDifferentDecimals() (gas: 442918) -OptionSettlementTest:testFailAssignMultipleBuckets() (gas: 734756) +OptionSettlementTest:testAddOptionsToExistingClaim() (gas: 452638) +OptionSettlementTest:testDecodeTokenId() (gas: 487512) +OptionSettlementTest:testEncodeTokenId() (gas: 487113) +OptionSettlementTest:testEventExercise() (gas: 340796) +OptionSettlementTest:testEventNewOptionType() (gas: 124307) +OptionSettlementTest:testEventRedeem() (gas: 299522) +OptionSettlementTest:testEventSweepFeesWhenFeesAccruedForExercise() (gas: 1206709) +OptionSettlementTest:testEventSweepFeesWhenFeesAccruedForWrite() (gas: 1175972) +OptionSettlementTest:testEventWriteWhenExistingClaim() (gas: 322146) +OptionSettlementTest:testEventWriteWhenNewClaim() (gas: 300701) +OptionSettlementTest:testExerciseBeforeExpiry() (gas: 333473) +OptionSettlementTest:testExerciseIncompleteExercise() (gas: 354620) +OptionSettlementTest:testExerciseMultipleWriteSameChain() (gas: 467341) +OptionSettlementTest:testExerciseWithDifferentDecimals() (gas: 405077) +OptionSettlementTest:testFailAssignMultipleBuckets() (gas: 738993) OptionSettlementTest:testFailGetClaimForTokenId() (gas: 392082) -OptionSettlementTest:testFailRandomAssignment() (gas: 1198722) +OptionSettlementTest:testFailRandomAssignment() (gas: 1204419) OptionSettlementTest:testFuzzDecodeTokenId(uint256,uint256) (runs: 256, μ: 10844, ~: 10844) -OptionSettlementTest:testFuzzEncodeTokenId(uint256,uint256) (runs: 256, μ: 10792, ~: 10792) -OptionSettlementTest:testFuzzExercise(uint112,uint112) (runs: 256, μ: 399750, ~: 403120) -OptionSettlementTest:testFuzzNewOptionType(uint96,uint96,uint40,uint40) (runs: 256, μ: 123624, ~: 123624) -OptionSettlementTest:testFuzzRedeem(uint112,uint112) (runs: 256, μ: 390496, ~: 394080) +OptionSettlementTest:testFuzzEncodeTokenId(uint256,uint256) (runs: 256, μ: 10770, ~: 10770) +OptionSettlementTest:testFuzzExercise(uint112,uint112) (runs: 256, μ: 402658, ~: 406028) +OptionSettlementTest:testFuzzNewOptionType(uint96,uint96,uint40,uint40) (runs: 256, μ: 124702, ~: 124702) +OptionSettlementTest:testFuzzRedeem(uint112,uint112) (runs: 256, μ: 393471, ~: 397055) OptionSettlementTest:testFuzzWrite(uint112) (runs: 256, μ: 318375, ~: 318375) -OptionSettlementTest:testFuzzWriteExerciseRedeem(uint32) (runs: 256, μ: 11606381, ~: 11653036) -OptionSettlementTest:testGetOptionForTokenId() (gas: 124564) -OptionSettlementTest:testIsOptionInitialized() (gas: 125250) -OptionSettlementTest:testRedeemNotExercised() (gas: 300939) -OptionSettlementTest:testRevertExerciseWhenAfterExpiry() (gas: 308496) -OptionSettlementTest:testRevertExerciseWhenAtExpiry() (gas: 308437) -OptionSettlementTest:testRevertExerciseWhenBeforeExerciseTimestamp() (gas: 307999) -OptionSettlementTest:testRevertExerciseWhenInvalidOptionId() (gas: 294480) -OptionSettlementTest:testRevertNewOptionTypeWhenAssetsAreTheSame() (gas: 17709) -OptionSettlementTest:testRevertNewOptionTypeWhenExerciseWindowTooShort() (gas: 16888) -OptionSettlementTest:testRevertNewOptionTypeWhenExpiryTooSoon() (gas: 18816) -OptionSettlementTest:testRevertNewOptionTypeWhenOptionsTypeExists() (gas: 120738) -OptionSettlementTest:testRevertNewOptionTypeWhenTotalSuppliesAreTooLowToExercise() (gas: 44586) -OptionSettlementTest:testRevertRedeemClaimTooSoon() (gas: 298684) -OptionSettlementTest:testRevertRedeemWhenBalanceTooLow() (gas: 318713) -OptionSettlementTest:testRevertRedeemWhenInvalidClaim() (gas: 10625) +OptionSettlementTest:testFuzzWriteExerciseRedeem(uint32) (runs: 256, μ: 11635799, ~: 11692076) +OptionSettlementTest:testGetOptionForTokenId() (gas: 124631) +OptionSettlementTest:testIsOptionInitialized() (gas: 124854) +OptionSettlementTest:testRedeemNotExercised() (gas: 302961) +OptionSettlementTest:testRevertExerciseBeforeExcercise() (gas: 401588) +OptionSettlementTest:testRevertExerciseWhenCallerHoldsInsufficientOptions() (gas: 306381) +OptionSettlementTest:testRevertExerciseWhenExerciseTooEarly() (gas: 308003) +OptionSettlementTest:testRevertExerciseWhenExpiredOption() (gas: 427764) +OptionSettlementTest:testRevertExerciseWhenInvalidOption() (gas: 294499) +OptionSettlementTest:testRevertNewOptionTypeWhenExerciseWindowTooShort() (gas: 19267) +OptionSettlementTest:testRevertNewOptionTypeWhenExpiryTooSoon() (gas: 20652) +OptionSettlementTest:testRevertNewOptionTypeWhenInvalidAssets() (gas: 20857) +OptionSettlementTest:testRevertNewOptionTypeWhenOptionsTypeExists() (gas: 22574) +OptionSettlementTest:testRevertNewOptionTypeWhenTotalSuppliesAreTooLowToExercise() (gas: 48813) +OptionSettlementTest:testRevertRedeemWhenCallerDoesNotOwnClaimId() (gas: 318703) +OptionSettlementTest:testRevertRedeemWhenClaimTooSoon() (gas: 298702) +OptionSettlementTest:testRevertRedeemWhenInvalidClaim() (gas: 10603) OptionSettlementTest:testRevertSetFeeToWhenNotCurrentFeeTo() (gas: 11831) -OptionSettlementTest:testRevertSetFeeToWhenZeroAddress() (gas: 11600) -OptionSettlementTest:testRevertUnderlyingWhenNoOptionIsInitialized() (gas: 11735) -OptionSettlementTest:testRevertWriteExpiredOption() (gas: 16607) -OptionSettlementTest:testRevertWriteWhenAmountWrittenIsZero() (gas: 10924) -OptionSettlementTest:testRevertWriteWhenClaimIdDoesNotEncodeOptionId() (gas: 11691) -OptionSettlementTest:testRevertWriteWhenInvalidOption() (gas: 11428) -OptionSettlementTest:testRevertWriteWhenWriterDoesNotOwnClaim() (gas: 302872) -OptionSettlementTest:testSetFeeTo() (gas: 15777) +OptionSettlementTest:testRevertSetFeeToWhenZeroAddress() (gas: 11689) +OptionSettlementTest:testRevertUnderlyingWhenTokenNotFound() (gas: 11757) +OptionSettlementTest:testRevertUriWhenTokenNotFound() (gas: 18617) +OptionSettlementTest:testRevertWriteExpiredOption() (gas: 16631) +OptionSettlementTest:testRevertWriteWhenAmountWrittenCannotBeZero() (gas: 10926) +OptionSettlementTest:testRevertWriteWhenCallerDoesNotOwnClaimId() (gas: 302895) +OptionSettlementTest:testRevertWriteWhenEncodedOptionIdInClaimIdDoesNotMatchProvidedOptionId() (gas: 11624) +OptionSettlementTest:testRevertWriteWhenExpiredOption() (gas: 295661) +OptionSettlementTest:testRevertWriteWhenInvalidOption() (gas: 13628) +OptionSettlementTest:testSetFeeTo() (gas: 15841) OptionSettlementTest:testTokenURI() (gas: 480902) -OptionSettlementTest:testUnderlyingAfterExercise() (gas: 371364) -OptionSettlementTest:testUnderlyingWhenNotExercised() (gas: 302719) -OptionSettlementTest:testUriFailsWithTokenIdEncodingNonexistantOptionType() (gas: 18574) -OptionSettlementTest:testWriteMultipleWriteSameOptionType() (gas: 431176) +OptionSettlementTest:testUnderlyingAfterExercise() (gas: 375491) +OptionSettlementTest:testUnderlyingForFungibleOptionToken() (gas: 31130) +OptionSettlementTest:testUnderlyingWhenNotExercised() (gas: 304697) +OptionSettlementTest:testWriteMultipleWriteSameOptionType() (gas: 431154) diff --git a/README.md b/README.md index 1fe6995..7c76d39 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ event NewOptionType( address indexed underlyingAsset, uint96 exerciseAmount, uint96 underlyingAmount, - uint40 earliestExerciseTimestamp, + uint40 exerciseTimestamp, uint40 expiryTimestamp ); ``` @@ -314,7 +314,7 @@ unique hash `keccak256(abi.encode(Option memory))` where `settlementSeed` is set ```solidity struct Option { address underlyingAsset; - uint40 earliestExerciseTimestamp; + uint40 exerciseTimestamp; uint40 expiryTimestamp; address exerciseAsset; uint96 underlyingAmount; diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index c109ce8..4f86eba 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -9,7 +9,6 @@ import "solmate/utils/SafeTransferLib.sol"; import "solmate/utils/FixedPointMathLib.sol"; import "./TokenURIGenerator.sol"; -// TODO decide on exerciseTimestamp earliestExerciseTimestamp // TODO fix 2 broken bucketing-related tests -- testFailAssignMultipleBuckets and testFailRandomAssignment // TODO fix broken accessor-related test -- testFailGetClaimForTokenId @@ -180,7 +179,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { underlyingSymbol: ERC20(optionInfo.underlyingAsset).symbol(), exerciseAsset: optionInfo.exerciseAsset, exerciseSymbol: ERC20(optionInfo.exerciseAsset).symbol(), - earliestExerciseTimestamp: optionInfo.earliestExerciseTimestamp, + exerciseTimestamp: optionInfo.exerciseTimestamp, expiryTimestamp: optionInfo.expiryTimestamp, underlyingAmount: optionInfo.underlyingAmount, exerciseAmount: optionInfo.exerciseAmount, @@ -259,7 +258,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { uint96 underlyingAmount, address exerciseAsset, uint96 exerciseAmount, - uint40 earliestExerciseTimestamp, + uint40 exerciseTimestamp, uint40 expiryTimestamp ) external returns (uint256 optionId) { // Check that a duplicate option type doesn't exist @@ -271,7 +270,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { underlyingAmount, exerciseAsset, exerciseAmount, - earliestExerciseTimestamp, + exerciseTimestamp, expiryTimestamp, uint160(0), uint96(0) @@ -292,8 +291,8 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } // Ensure the exercise window is at least 24 hours - if (expiryTimestamp < (earliestExerciseTimestamp + 1 days)) { - revert ExerciseWindowTooShort(earliestExerciseTimestamp); + if (expiryTimestamp < (exerciseTimestamp + 1 days)) { + revert ExerciseWindowTooShort(exerciseTimestamp); } // The exercise and underlying assets can't be the same @@ -315,7 +314,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { underlyingAmount: underlyingAmount, exerciseAsset: exerciseAsset, exerciseAmount: exerciseAmount, - earliestExerciseTimestamp: earliestExerciseTimestamp, + exerciseTimestamp: exerciseTimestamp, expiryTimestamp: expiryTimestamp, settlementSeed: optionKey, nextClaimNum: 1 @@ -327,7 +326,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { underlyingAsset, exerciseAmount, underlyingAmount, - earliestExerciseTimestamp, + exerciseTimestamp, expiryTimestamp, 1 ); @@ -442,8 +441,8 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { revert ExpiredOption(optionId, optionRecord.expiryTimestamp); } // Require that we have reached the exercise timestamp - if (optionRecord.earliestExerciseTimestamp >= block.timestamp) { - revert ExerciseTooEarly(optionId, optionRecord.earliestExerciseTimestamp); + if (optionRecord.exerciseTimestamp >= block.timestamp) { + revert ExerciseTooEarly(optionId, optionRecord.exerciseTimestamp); } if (this.balanceOf(msg.sender, optionId) < amount) { diff --git a/src/TokenURIGenerator.sol b/src/TokenURIGenerator.sol index cbb7dd3..7202551 100644 --- a/src/TokenURIGenerator.sol +++ b/src/TokenURIGenerator.sol @@ -17,7 +17,7 @@ library TokenURIGenerator { // The symbol of the underlying asset string exerciseSymbol; // The timestamp after which this option may be exercised - uint40 earliestExerciseTimestamp; + uint40 exerciseTimestamp; // The timestamp before which this option must be exercised uint40 expiryTimestamp; // The amount of the underlying asset contained within an option contract of this type @@ -160,7 +160,7 @@ library TokenURIGenerator { return string( abi.encodePacked( "EXERCISE DATE", - _generateTimestampString(params.earliestExerciseTimestamp, 16, 260), + _generateTimestampString(params.exerciseTimestamp, 16, 260), "EXPIRY DATE", _generateTimestampString(params.expiryTimestamp, 200, 260) ) diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index 006ecde..daef90a 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -30,7 +30,7 @@ interface IOptionSettlementEngine { * @param underlyingAsset The contract address of the underlying asset. * @param exerciseAmount The amount of the exercise asset to be exercised. * @param underlyingAmount The amount of the underlying asset in the option. - * @param earliestExerciseTimestamp The timestamp for exercising the option. + * @param exerciseTimestamp The timestamp for exercising the option. * @param expiryTimestamp The expiry timestamp of the option. * @param nextClaimNum The next claim ID. */ @@ -40,7 +40,7 @@ interface IOptionSettlementEngine { address indexed underlyingAsset, uint96 exerciseAmount, uint96 underlyingAmount, - uint40 earliestExerciseTimestamp, + uint40 exerciseTimestamp, uint40 expiryTimestamp, uint96 nextClaimNum ); @@ -233,8 +233,8 @@ interface IOptionSettlementEngine { address exerciseAsset; /// @param exerciseAmount The amount of the exercise asset required to exercise this option uint96 exerciseAmount; - /// @param earliestExerciseTimestamp The timestamp after which this option may be exercised - uint40 earliestExerciseTimestamp; + /// @param exerciseTimestamp The timestamp after which this option may be exercised + uint40 exerciseTimestamp; /// @param expiryTimestamp The timestamp before which this option must be exercised uint40 expiryTimestamp; /// @param settlementSeed Random seed created at the time of option type creation @@ -300,8 +300,6 @@ interface IOptionSettlementEngine { */ function option(uint256 tokenId) external view returns (Option memory optionInfo); - // TODO review and clarify - /** * @notice Returns OptionLotClaim struct details about a given tokenId if that token is a * claim NFT. @@ -348,7 +346,7 @@ interface IOptionSettlementEngine { uint96 underlyingAmount, address exerciseAsset, uint96 exerciseAmount, - uint40 earliestExerciseTimestamp, + uint40 exerciseTimestamp, uint40 expiryTimestamp ) external returns (uint256 optionId); diff --git a/test/OptionSettlement.t.sol b/test/OptionSettlement.t.sol index e04e659..a2d9c52 100644 --- a/test/OptionSettlement.t.sol +++ b/test/OptionSettlement.t.sol @@ -74,7 +74,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: testExerciseAsset, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); @@ -253,7 +253,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: 100, exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, writer: ALICE, exerciser: BOB @@ -351,7 +351,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); @@ -431,7 +431,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount + 1, // to mess w seed exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); @@ -585,7 +585,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: 1, exerciseAsset: USDC_A, exerciseAmount: 100, - earliestExerciseTimestamp: uint40(block.timestamp), + exerciseTimestamp: uint40(block.timestamp), expiryTimestamp: uint40(block.timestamp + 30 days), settlementSeed: 0, nextClaimNum: 0 @@ -635,7 +635,7 @@ contract OptionSettlementTest is Test, NFTreceiver { function testEventNewOptionType() public { IOptionSettlementEngine.Option memory optionInfo = IOptionSettlementEngine.Option({ underlyingAsset: DAI_A, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp, exerciseAsset: WETH_A, underlyingAmount: testUnderlyingAmount, @@ -755,7 +755,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: daiUnderlyingAmount, exerciseAsset: WETH_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); engine.write(daiOptionId, 1); @@ -766,7 +766,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: usdcUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); engine.write(usdcOptionId, 1); @@ -804,7 +804,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: daiExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); engine.write(daiExerciseOptionId, 1); @@ -815,7 +815,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: WETH_A, exerciseAmount: wethExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); engine.write(wethExerciseOptionId, 1); @@ -826,7 +826,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: USDC_A, exerciseAmount: usdcExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); engine.write(usdcExerciseOptionId, 1); @@ -879,7 +879,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); } @@ -891,7 +891,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: WETH_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: uint40(block.timestamp), + exerciseTimestamp: uint40(block.timestamp), expiryTimestamp: tooSoonExpiryTimestamp, settlementSeed: 0, // default zero for settlement seed nextClaimNum: 0 // default zero for next claim id @@ -906,7 +906,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp - 1 }); } @@ -920,7 +920,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: uint40(block.timestamp + 1), + exerciseTimestamp: uint40(block.timestamp + 1), expiryTimestamp: testExpiryTimestamp }); } @@ -932,7 +932,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); } @@ -947,7 +947,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: underlyingAmountExceedsTotalSupply, exerciseAsset: WETH_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); @@ -960,7 +960,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: WETH_A, exerciseAmount: exerciseAmountExceedsTotalSupply, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp }); } @@ -1016,7 +1016,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: testExerciseTimestamp + 1, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: testExpiryTimestamp + 1 }); @@ -1060,7 +1060,7 @@ contract OptionSettlementTest is Test, NFTreceiver { engine.safeTransferFrom(ALICE, BOB, testOptionId, 1, ""); vm.stopPrank(); - // Bob immediately exercises before earliestExerciseTimestamp + // Bob immediately exercises before exerciseTimestamp vm.startPrank(BOB); vm.expectRevert( abi.encodeWithSelector( @@ -1212,14 +1212,14 @@ contract OptionSettlementTest is Test, NFTreceiver { function testFuzzNewOptionType( uint96 underlyingAmount, uint96 exerciseAmount, - uint40 earliestExerciseTimestamp, + uint40 exerciseTimestamp, uint40 expiryTimestamp ) public { vm.assume(expiryTimestamp >= block.timestamp + 86400); - vm.assume(earliestExerciseTimestamp >= block.timestamp); - vm.assume(earliestExerciseTimestamp <= expiryTimestamp - 86400); + vm.assume(exerciseTimestamp >= block.timestamp); + vm.assume(exerciseTimestamp <= expiryTimestamp - 86400); vm.assume(expiryTimestamp <= type(uint64).max); - vm.assume(earliestExerciseTimestamp <= type(uint64).max); + vm.assume(exerciseTimestamp <= type(uint64).max); vm.assume(underlyingAmount <= WETH.totalSupply()); vm.assume(exerciseAmount <= DAI.totalSupply()); @@ -1228,7 +1228,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: underlyingAmount, exerciseAsset: DAI_A, exerciseAmount: exerciseAmount, - earliestExerciseTimestamp: earliestExerciseTimestamp, + exerciseTimestamp: exerciseTimestamp, expiryTimestamp: expiryTimestamp }); @@ -1242,7 +1242,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertEq(optionId, expectedOptionId); assertEq(optionRecord.underlyingAsset, WETH_A); assertEq(optionRecord.exerciseAsset, DAI_A); - assertEq(optionRecord.earliestExerciseTimestamp, earliestExerciseTimestamp); + assertEq(optionRecord.exerciseTimestamp, exerciseTimestamp); assertEq(optionRecord.expiryTimestamp, expiryTimestamp); assertEq(optionRecord.underlyingAmount, underlyingAmount); assertEq(optionRecord.exerciseAmount, exerciseAmount); @@ -1378,7 +1378,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: uint40(block.timestamp + 30 days) }); @@ -1388,7 +1388,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: testUnderlyingAmount, exerciseAsset: DAI_A, exerciseAmount: testExerciseAmount, - earliestExerciseTimestamp: testExerciseTimestamp, + exerciseTimestamp: testExerciseTimestamp, expiryTimestamp: uint40(block.timestamp + 90 days) }); @@ -1508,8 +1508,8 @@ contract OptionSettlementTest is Test, NFTreceiver { } } - if (option.earliestExerciseTimestamp >= uint40(block.timestamp)) { - emit log_named_uint("exercise timestamp not hit", option.earliestExerciseTimestamp); + if (option.exerciseTimestamp >= uint40(block.timestamp)) { + emit log_named_uint("exercise timestamp not hit", option.exerciseTimestamp); return (written, 0, newClaim); } @@ -1567,7 +1567,7 @@ contract OptionSettlementTest is Test, NFTreceiver { uint96 underlyingAmount, address exerciseAsset, uint96 exerciseAmount, - uint40 earliestExerciseTimestamp, + uint40 exerciseTimestamp, uint40 expiryTimestamp, address writer, address exerciser @@ -1577,7 +1577,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: underlyingAmount, exerciseAsset: exerciseAsset, exerciseAmount: exerciseAmount, - earliestExerciseTimestamp: earliestExerciseTimestamp, + exerciseTimestamp: exerciseTimestamp, expiryTimestamp: expiryTimestamp }); claimId = _writeAndExerciseOption(optionId, writer, exerciser); @@ -1589,7 +1589,7 @@ contract OptionSettlementTest is Test, NFTreceiver { uint96 underlyingAmount, address exerciseAsset, uint96 exerciseAmount, - uint40 earliestExerciseTimestamp, + uint40 exerciseTimestamp, uint40 expiryTimestamp ) internal returns (uint256 optionId, IOptionSettlementEngine.Option memory option) { (, option) = _getNewOptionType({ @@ -1597,7 +1597,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: underlyingAmount, exerciseAsset: exerciseAsset, exerciseAmount: exerciseAmount, - earliestExerciseTimestamp: earliestExerciseTimestamp, + exerciseTimestamp: exerciseTimestamp, expiryTimestamp: expiryTimestamp }); optionId = engine.newOptionType({ @@ -1605,7 +1605,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: underlyingAmount, exerciseAsset: exerciseAsset, exerciseAmount: exerciseAmount, - earliestExerciseTimestamp: earliestExerciseTimestamp, + exerciseTimestamp: exerciseTimestamp, expiryTimestamp: expiryTimestamp }); } @@ -1615,7 +1615,7 @@ contract OptionSettlementTest is Test, NFTreceiver { uint96 underlyingAmount, address exerciseAsset, uint96 exerciseAmount, - uint40 earliestExerciseTimestamp, + uint40 exerciseTimestamp, uint40 expiryTimestamp ) internal pure returns (uint256 optionId, IOptionSettlementEngine.Option memory option) { option = IOptionSettlementEngine.Option({ @@ -1623,7 +1623,7 @@ contract OptionSettlementTest is Test, NFTreceiver { underlyingAmount: underlyingAmount, exerciseAsset: exerciseAsset, exerciseAmount: exerciseAmount, - earliestExerciseTimestamp: earliestExerciseTimestamp, + exerciseTimestamp: exerciseTimestamp, expiryTimestamp: expiryTimestamp, settlementSeed: 0, // default zero for settlement seed nextClaimNum: 0 // default zero for next claim id @@ -1733,7 +1733,7 @@ contract OptionSettlementTest is Test, NFTreceiver { assertEq(actual.underlyingAmount, expected.underlyingAmount); assertEq(actual.exerciseAsset, expected.exerciseAsset); assertEq(actual.exerciseAmount, expected.exerciseAmount); - assertEq(actual.earliestExerciseTimestamp, expected.earliestExerciseTimestamp); + assertEq(actual.exerciseTimestamp, expected.exerciseTimestamp); assertEq(actual.expiryTimestamp, expected.expiryTimestamp); assertEq(actual.settlementSeed, expected.settlementSeed); assertEq(actual.nextClaimNum, expected.nextClaimNum); @@ -1747,7 +1747,7 @@ contract OptionSettlementTest is Test, NFTreceiver { address indexed underlyingAsset, uint96 exerciseAmount, uint96 underlyingAmount, - uint40 earliestExerciseTimestamp, + uint40 exerciseTimestamp, uint40 expiryTimestamp, uint96 nextClaimNum );