From 6105e363ef254f117fdaf229186e3e3f7ae331aa Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 14:48:44 -0500 Subject: [PATCH 01/14] token uri generator to contract, add interface --- src/TokenURIGenerator.sol | 2 +- src/interfaces/ITokenURIGenerator.sol | 39 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/interfaces/ITokenURIGenerator.sol diff --git a/src/TokenURIGenerator.sol b/src/TokenURIGenerator.sol index 7202551..d6df86e 100644 --- a/src/TokenURIGenerator.sol +++ b/src/TokenURIGenerator.sol @@ -6,7 +6,7 @@ import "solmate/tokens/ERC20.sol"; import "./interfaces/IOptionSettlementEngine.sol"; -library TokenURIGenerator { +contract TokenURIGenerator { struct TokenURIParams { // The underlying asset to be received address underlyingAsset; diff --git a/src/interfaces/ITokenURIGenerator.sol b/src/interfaces/ITokenURIGenerator.sol new file mode 100644 index 0000000..d42621a --- /dev/null +++ b/src/interfaces/ITokenURIGenerator.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BUSL 1.1 +pragma solidity 0.8.11; + +import "./IERC1155Metadata.sol"; +import "./IOptionSettlementEngine.sol"; + +/// @title A token URI geneartor for Claim NFTs +/// @author 0xAlcibiades +/// @author Flip-Liquid +/// @author neodaoist +interface ITokenURIGenerator { + struct TokenURIParams { + /// @param underlyingAsset The underlying asset to be received + address underlyingAsset; + /// @param underlyingSymbol The symbol of the underlying asset + string underlyingSymbol; + /// @param exerciseAsset The address of the asset needed for exercise + address exerciseAsset; + /// @param exerciseSymbol The symbol of the underlying asset + string exerciseSymbol; + /// @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 underlyingAmount The amount of the underlying asset contained within an option contract of this type + uint96 underlyingAmount; + /// @param exerciseAmount The amount of the exercise asset required to exercise this option + uint96 exerciseAmount; + /// @param tokenType Option or Claim + IOptionSettlementEngine.Type tokenType; + } + function constructTokenURI(TokenURIParams memory params) external view returns (string memory); + + function generateName(TokenURIParams memory params) external pure returns (string memory); + + function generateDescription(TokenURIParams memory params) external pure returns (string memory); + + function generateNFT(TokenURIParams memory params) external view returns (string memory); +} \ No newline at end of file From 0dcf505fe9fe2bbf8dd0430c35e9f84bf869d384 Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 15:22:40 -0500 Subject: [PATCH 02/14] integrate token uri geneartor as contract --- src/OptionSettlementEngine.sol | 18 +++++++++++++- src/TokenURIGenerator.sol | 28 ++++------------------ src/interfaces/IOptionSettlementEngine.sol | 14 ++++++++++- src/interfaces/ITokenURIGenerator.sol | 1 + 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index c0a294c..7c0ae49 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -39,6 +39,9 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @notice The address fees accrue to address public feeTo; + /// @notice The contract for token uri generation + ITokenURIGenerator public tokenUriGenerator; + /*////////////////////////////////////////////////////////////// // State variables - Internal //////////////////////////////////////////////////////////////*/ @@ -80,6 +83,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @param _feeTo The address fees accrue to constructor(address _feeTo) { feeTo = _feeTo; + tokenUriGenerator = new TokenURIGenerator(); } /*////////////////////////////////////////////////////////////// @@ -159,7 +163,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { Type _type = claimNum == 0 ? Type.Option : Type.OptionLotClaim; - TokenURIGenerator.TokenURIParams memory params = TokenURIGenerator.TokenURIParams({ + TokenURIGenerator.TokenURIParams memory params = tokenURIGenerator.TokenURIParams({ underlyingAsset: optionInfo.underlyingAsset, underlyingSymbol: ERC20(optionInfo.underlyingAsset).symbol(), exerciseAsset: optionInfo.exerciseAsset, @@ -515,6 +519,18 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } } + /// @inheritdoc IOptionSettlementEngine + function setTokenURIGenerator(address newTokenURIGenerator) public { + if (msg.sender != feeTo) { + revert AccessControlViolation(msg.sender, feeTo); + } + if (tokenUriGenerator == address(0)) { + revert InvalidTokenURIGeneratorAddress(0); + } + + tokenUriGenerator = ITokenURIGenerator(newTokenURIGenerator); + } + /*////////////////////////////////////////////////////////////// // Internal Helper Functions //////////////////////////////////////////////////////////////*/ diff --git a/src/TokenURIGenerator.sol b/src/TokenURIGenerator.sol index fcb0e94..a4d6a2f 100644 --- a/src/TokenURIGenerator.sol +++ b/src/TokenURIGenerator.sol @@ -5,31 +5,13 @@ import "base64/Base64.sol"; import "solmate/tokens/ERC20.sol"; import "./interfaces/IOptionSettlementEngine.sol"; +import "./interfaces/ITokenURIGenerator.sol"; /// @title Library to dynamically generate Valorem token URIs -contract TokenURIGenerator { - /// @notice This struct contains params to generate the token URI - struct TokenURIParams { - /// @param underlyingAsset The underlying asset to be received - address underlyingAsset; - /// @param underlyingSymbol The symbol of the underlying asset - string underlyingSymbol; - /// @param exerciseAsset The address of the asset needed for exercise - address exerciseAsset; - /// @param exerciseSymbol The symbol of the underlying asset - string exerciseSymbol; - /// @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 underlyingAmount The amount of the underlying asset contained within an option contract of this type - uint96 underlyingAmount; - /// @param exerciseAmount The amount of the exercise asset required to exercise this option - uint96 exerciseAmount; - /// @param tokenType Option or Claim - IOptionSettlementEngine.Type tokenType; - } - +/// @author 0xAlcibiades +/// @author Flip-Liquid +/// @author neodaoist +contract TokenURIGenerator is ITokenURIGenerator { /// @dev Construct the token URI function constructTokenURI(TokenURIParams memory params) public view returns (string memory) { string memory svg = generateNFT(params); diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index be6de50..e0b8eca 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -117,10 +117,16 @@ interface IOptionSettlementEngine { /** * @notice Invalid fee to address. - * @param feeTo the feeTo address. + * @param feeTo The feeTo address. */ error InvalidFeeToAddress(address feeTo); + /** + * @notice Invalid TokenURIGenerator address. + * @param tokenURIGenerator The tokenURIGenerator address. + */ + error InvalidTokenURIGeneratorAddress(address tokenURIGenerator); + /** * @notice This options chain already exists and thus cannot be created. * @param optionId The id and hash of the options chain. @@ -472,4 +478,10 @@ interface IOptionSettlementEngine { * @param tokens The tokens for which fees will be swept to the feeTo address. */ function sweepFees(address[] memory tokens) external; + + /** + * @notice Updates the contract address for generating the token URI for claim NFTs. + * @param newTokenURIGenerator The address of the new ITokenURIGenerator contract. + */ + function setTokenURIGenerator(address newTokenURIGenerator) external; } diff --git a/src/interfaces/ITokenURIGenerator.sol b/src/interfaces/ITokenURIGenerator.sol index d42621a..5956dde 100644 --- a/src/interfaces/ITokenURIGenerator.sol +++ b/src/interfaces/ITokenURIGenerator.sol @@ -29,6 +29,7 @@ interface ITokenURIGenerator { /// @param tokenType Option or Claim IOptionSettlementEngine.Type tokenType; } + function constructTokenURI(TokenURIParams memory params) external view returns (string memory); function generateName(TokenURIParams memory params) external pure returns (string memory); From 492ebdd6070a2439902a2e14c17fc1da60d70b8c Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 15:28:07 -0500 Subject: [PATCH 03/14] casing --- src/OptionSettlementEngine.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index 7c0ae49..be62710 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -40,7 +40,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { address public feeTo; /// @notice The contract for token uri generation - ITokenURIGenerator public tokenUriGenerator; + ITokenURIGenerator public tokenURIGenerator; /*////////////////////////////////////////////////////////////// // State variables - Internal @@ -83,7 +83,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @param _feeTo The address fees accrue to constructor(address _feeTo) { feeTo = _feeTo; - tokenUriGenerator = new TokenURIGenerator(); + tokenURIGenerator = new TokenURIGenerator(); } /*////////////////////////////////////////////////////////////// @@ -524,11 +524,11 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { if (msg.sender != feeTo) { revert AccessControlViolation(msg.sender, feeTo); } - if (tokenUriGenerator == address(0)) { + if (tokenURIGenerator == address(0)) { revert InvalidTokenURIGeneratorAddress(0); } - tokenUriGenerator = ITokenURIGenerator(newTokenURIGenerator); + tokenURIGenerator = ITokenURIGenerator(newTokenURIGenerator); } /*////////////////////////////////////////////////////////////// From 4cfdd07d2674d089fb49e380441a4c312c0f1df7 Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 15:39:26 -0500 Subject: [PATCH 04/14] add tests --- src/OptionSettlementEngine.sol | 8 ++++---- test/OptionSettlementEngine.t.sol | 20 ++++++++++++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index be62710..aa33ca0 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -163,7 +163,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { Type _type = claimNum == 0 ? Type.Option : Type.OptionLotClaim; - TokenURIGenerator.TokenURIParams memory params = tokenURIGenerator.TokenURIParams({ + ITokenURIGenerator.TokenURIParams memory params = ITokenURIGenerator.TokenURIParams({ underlyingAsset: optionInfo.underlyingAsset, underlyingSymbol: ERC20(optionInfo.underlyingAsset).symbol(), exerciseAsset: optionInfo.exerciseAsset, @@ -175,7 +175,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { tokenType: _type }); - return TokenURIGenerator.constructTokenURI(params); + return tokenURIGenerator.constructTokenURI(params); } /*////////////////////////////////////////////////////////////// @@ -524,8 +524,8 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { if (msg.sender != feeTo) { revert AccessControlViolation(msg.sender, feeTo); } - if (tokenURIGenerator == address(0)) { - revert InvalidTokenURIGeneratorAddress(0); + if (newTokenURIGenerator == address(0)) { + revert InvalidTokenURIGeneratorAddress(address(0)); } tokenURIGenerator = ITokenURIGenerator(newTokenURIGenerator); diff --git a/test/OptionSettlementEngine.t.sol b/test/OptionSettlementEngine.t.sol index 63d2bd7..c3e4851 100644 --- a/test/OptionSettlementEngine.t.sol +++ b/test/OptionSettlementEngine.t.sol @@ -520,18 +520,34 @@ contract OptionSettlementTest is Test, NFTreceiver { function testRevertSetFeeToWhenNotCurrentFeeTo() public { vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.AccessControlViolation.selector, ALICE, FEE_TO)); - vm.prank(ALICE); engine.setFeeTo(address(0xCAFE)); } function testRevertSetFeeToWhenZeroAddress() public { vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidFeeToAddress.selector, address(0))); - vm.prank(FEE_TO); engine.setFeeTo(address(0)); } + function testSetTokenURIGenerator() public { + vm.prank(FEE_TO); + engine.setTokenURIGenerator(address(0xCAFE)); + assertEq(address(engine.tokenURIGenerator()), address(0xCAFE)); + } + + function testRevertSetTokenURIGeneratorWhenNotCurrentFeeTo() public { + vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.AccessControlViolation.selector, ALICE, FEE_TO)); + vm.prank(ALICE); + engine.setTokenURIGenerator(address(0xCAFE)); + } + + function testRevertSetTokenURIGeneratorWhenZeroAddress() public { + vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidTokenURIGeneratorAddress.selector, address(0))); + vm.prank(FEE_TO); + engine.setTokenURIGenerator(address(0)); + } + // ********************************************************************** // TOKEN ID ENCODING HELPERS // ********************************************************************** From 01a0291790a9a2bc3802bd1e884874aaa4e32857 Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 15:39:48 -0500 Subject: [PATCH 05/14] lint --- src/interfaces/ITokenURIGenerator.sol | 2 +- test/OptionSettlementEngine.t.sol | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/interfaces/ITokenURIGenerator.sol b/src/interfaces/ITokenURIGenerator.sol index 5956dde..aeabf56 100644 --- a/src/interfaces/ITokenURIGenerator.sol +++ b/src/interfaces/ITokenURIGenerator.sol @@ -37,4 +37,4 @@ interface ITokenURIGenerator { function generateDescription(TokenURIParams memory params) external pure returns (string memory); function generateNFT(TokenURIParams memory params) external view returns (string memory); -} \ No newline at end of file +} diff --git a/test/OptionSettlementEngine.t.sol b/test/OptionSettlementEngine.t.sol index c3e4851..5d2ca70 100644 --- a/test/OptionSettlementEngine.t.sol +++ b/test/OptionSettlementEngine.t.sol @@ -543,7 +543,9 @@ contract OptionSettlementTest is Test, NFTreceiver { } function testRevertSetTokenURIGeneratorWhenZeroAddress() public { - vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidTokenURIGeneratorAddress.selector, address(0))); + vm.expectRevert( + abi.encodeWithSelector(IOptionSettlementEngine.InvalidTokenURIGeneratorAddress.selector, address(0)) + ); vm.prank(FEE_TO); engine.setTokenURIGenerator(address(0)); } From 41dc98c60eb6dbeb1d177d5acd4320ae844f36ef Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 17:26:48 -0500 Subject: [PATCH 06/14] token uri generator comments --- src/TokenURIGenerator.sol | 8 ++++---- src/interfaces/ITokenURIGenerator.sol | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/TokenURIGenerator.sol b/src/TokenURIGenerator.sol index a4d6a2f..95216b3 100644 --- a/src/TokenURIGenerator.sol +++ b/src/TokenURIGenerator.sol @@ -12,7 +12,7 @@ import "./interfaces/ITokenURIGenerator.sol"; /// @author Flip-Liquid /// @author neodaoist contract TokenURIGenerator is ITokenURIGenerator { - /// @dev Construct the token URI + /// @inheritdoc ITokenURIGenerator function constructTokenURI(TokenURIParams memory params) public view returns (string memory) { string memory svg = generateNFT(params); @@ -36,7 +36,7 @@ contract TokenURIGenerator is ITokenURIGenerator { /* solhint-enable quotes */ } - /// @dev Generate the name field + /// @inheritdoc ITokenURIGenerator function generateName(TokenURIParams memory params) public pure returns (string memory) { (uint256 month, uint256 day, uint256 year) = _getDateUnits(params.expiryTimestamp); @@ -59,7 +59,7 @@ contract TokenURIGenerator is ITokenURIGenerator { ); } - /// @dev Generate the description field + /// @inheritdoc ITokenURIGenerator function generateDescription(TokenURIParams memory params) public pure returns (string memory) { return string( abi.encodePacked( @@ -76,7 +76,7 @@ contract TokenURIGenerator is ITokenURIGenerator { ); } - /// @dev Generate the image field + /// @inheritdoc ITokenURIGenerator function generateNFT(TokenURIParams memory params) public view returns (string memory) { uint8 underlyingDecimals = ERC20(params.underlyingAsset).decimals(); uint8 exerciseDecimals = ERC20(params.exerciseAsset).decimals(); diff --git a/src/interfaces/ITokenURIGenerator.sol b/src/interfaces/ITokenURIGenerator.sol index aeabf56..74985c3 100644 --- a/src/interfaces/ITokenURIGenerator.sol +++ b/src/interfaces/ITokenURIGenerator.sol @@ -30,11 +30,31 @@ interface ITokenURIGenerator { IOptionSettlementEngine.Type tokenType; } + /** + * @notice Constructs a URI for a claim NFT, encoding an SVG based on parameters of the claims lot. + * @param params Parameters for the token URI. + * @return A string with the SVG encoded in Base64. + */ function constructTokenURI(TokenURIParams memory params) external view returns (string memory); + /** + * @notice Generates a name for the NFT based on the supplied params. + * @param params Parameters for the token URI. + * @return A generated name for the NFT. + */ function generateName(TokenURIParams memory params) external pure returns (string memory); + /** + * @notice Generates a description for the NFT based on the supplied params. + * @param params Parameters for the token URI. + * @return A generated description for the NFT. + */ function generateDescription(TokenURIParams memory params) external pure returns (string memory); + /** + * @notice Generates a svg for the NFT based on the supplied params. + * @param params Parameters for the token URI. + * @return A generated svg for the NFT. + */ function generateNFT(TokenURIParams memory params) external view returns (string memory); } From fc9088c36e7fda634b8209905cb90061b0c67e4e Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 17:27:26 -0500 Subject: [PATCH 07/14] accept geneartor address as constructor arg --- src/OptionSettlementEngine.sol | 4 ++-- test/OptionSettlementEngine.t.sol | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index aa33ca0..5d745cc 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -81,9 +81,9 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @notice OptionSettlementEngine constructor /// @param _feeTo The address fees accrue to - constructor(address _feeTo) { + constructor(address _feeTo, address _tokenURIGenerator) { feeTo = _feeTo; - tokenURIGenerator = new TokenURIGenerator(); + tokenURIGenerator = ITokenURIGenerator(_tokenURIGenerator); } /*////////////////////////////////////////////////////////////// diff --git a/test/OptionSettlementEngine.t.sol b/test/OptionSettlementEngine.t.sol index 5d2ca70..1bc43c4 100644 --- a/test/OptionSettlementEngine.t.sol +++ b/test/OptionSettlementEngine.t.sol @@ -59,13 +59,15 @@ contract OptionSettlementTest is Test, NFTreceiver { uint256 private testDuration = 1 days; IOptionSettlementEngine.Option private testOption; + ITokenURIGenerator private generator; function setUp() public { // Fork mainnet vm.createSelectFork(vm.envString("RPC_URL"), 15_000_000); // specify block number to cache for future test runs // Deploy OptionSettlementEngine - engine = new OptionSettlementEngine(FEE_TO); + generator = new TokenURIGenerator(); + engine = new OptionSettlementEngine(FEE_TO, address(generator)); // Setup test option contract testExerciseTimestamp = uint40(block.timestamp); From 09b1c05708b7f7306ac142cb63aa940ecd89e668 Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 17:32:23 -0500 Subject: [PATCH 08/14] idiomatic zero address --- src/OptionSettlementEngine.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index 5d745cc..3561a75 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -145,7 +145,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @inheritdoc IOptionSettlementEngine function isOptionInitialized(uint160 optionKey) public view returns (bool) { - return _option[optionKey].underlyingAsset != address(0x0); + return _option[optionKey].underlyingAsset != address(0); } /*////////////////////////////////////////////////////////////// From b446e72ebdac1240902f65593828a21aa3808e8a Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 17:37:25 -0500 Subject: [PATCH 09/14] drop unused interfaces --- src/interfaces/IERC1155.sol | 115 --------------------- src/interfaces/IERC1155Metadata.sol | 22 ---- src/interfaces/IERC165.sol | 25 ----- src/interfaces/IOptionSettlementEngine.sol | 2 - src/interfaces/ITokenURIGenerator.sol | 1 - 5 files changed, 165 deletions(-) delete mode 100644 src/interfaces/IERC1155.sol delete mode 100644 src/interfaces/IERC1155Metadata.sol delete mode 100644 src/interfaces/IERC165.sol diff --git a/src/interfaces/IERC1155.sol b/src/interfaces/IERC1155.sol deleted file mode 100644 index a2c3c68..0000000 --- a/src/interfaces/IERC1155.sol +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol) - -pragma solidity 0.8.11; - -import "./IERC165.sol"; - -/** - * @dev Required interface of an ERC1155 compliant contract, as defined in the - * https://eips.ethereum.org/EIPS/eip-1155[EIP]. - * - * _Available since v3.1._ - */ -interface IERC1155 is IERC165 { - /** - * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. - */ - event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); - - /** - * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all - * transfers. - */ - event TransferBatch( - address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values - ); - - /** - * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to - * `approved`. - */ - event ApprovalForAll(address indexed account, address indexed operator, bool approved); - - /** - * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. - * - * If an {URI} event was emitted for `id`, the standard - * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value - * returned by {IERC1155MetadataURI-uri}. - */ - event URI(string value, uint256 indexed id); - - /** - * @dev Returns the amount of tokens of token type `id` owned by `account`. - * - * Requirements: - * - * - `account` cannot be the zero address. - */ - function balanceOf(address account, uint256 id) external view returns (uint256); - - /** - * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}. - * - * Requirements: - * - * - `accounts` and `ids` must have the same length. - */ - function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) - external - view - returns (uint256[] memory); - - /** - * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, - * - * Emits an {ApprovalForAll} event. - * - * Requirements: - * - * - `operator` cannot be the caller. - */ - function setApprovalForAll(address operator, bool approved) external; - - /** - * @dev Returns true if `operator` is approved to transfer ``account``'s tokens. - * - * See {setApprovalForAll}. - */ - function isApprovedForAll(address account, address operator) external view returns (bool); - - /** - * @dev Transfers `amount` tokens of token type `id` from `from` to `to`. - * - * Emits a {TransferSingle} event. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}. - * - `from` must have a balance of tokens of type `id` of at least `amount`. - * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the - * acceptance magic value. - */ - function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; - - /** - * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}. - * - * Emits a {TransferBatch} event. - * - * Requirements: - * - * - `ids` and `amounts` must have the same length. - * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the - * acceptance magic value. - */ - function safeBatchTransferFrom( - address from, - address to, - uint256[] calldata ids, - uint256[] calldata amounts, - bytes calldata data - ) external; -} diff --git a/src/interfaces/IERC1155Metadata.sol b/src/interfaces/IERC1155Metadata.sol deleted file mode 100644 index 87e9923..0000000 --- a/src/interfaces/IERC1155Metadata.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol) - -pragma solidity 0.8.11; - -import "./IERC1155.sol"; - -/** - * @dev Interface of the optional ERC1155MetadataExtension interface, as defined - * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP]. - * - * _Available since v3.1._ - */ -interface IERC1155MetadataURI is IERC1155 { - /** - * @dev Returns the URI for token type `id`. - * - * If the `\{id\}` substring is present in the URI, it must be replaced by - * clients with the actual token type ID. - */ - function uri(uint256 id) external view returns (string memory); -} diff --git a/src/interfaces/IERC165.sol b/src/interfaces/IERC165.sol deleted file mode 100644 index 9177148..0000000 --- a/src/interfaces/IERC165.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol) - -pragma solidity 0.8.11; - -/** - * @dev Interface of the ERC165 standard, as defined in the - * https://eips.ethereum.org/EIPS/eip-165[EIP]. - * - * Implementers can declare support of contract interfaces, which can then be - * queried by others ({ERC165Checker}). - * - * For an implementation, see {ERC165}. - */ -interface IERC165 { - /** - * @dev Returns true if this contract implements the interface defined by - * `interfaceId`. See the corresponding - * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] - * to learn more about how these ids are created. - * - * This function call must use less than 30 000 gas. - */ - function supportsInterface(bytes4 interfaceId) external view returns (bool); -} diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index e0b8eca..4ba0855 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: BUSL 1.1 pragma solidity 0.8.11; -import "./IERC1155Metadata.sol"; - /// @title A settlement engine for options /// @author 0xAlcibiades /// @author Flip-Liquid diff --git a/src/interfaces/ITokenURIGenerator.sol b/src/interfaces/ITokenURIGenerator.sol index 74985c3..912352f 100644 --- a/src/interfaces/ITokenURIGenerator.sol +++ b/src/interfaces/ITokenURIGenerator.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BUSL 1.1 pragma solidity 0.8.11; -import "./IERC1155Metadata.sol"; import "./IOptionSettlementEngine.sol"; /// @title A token URI geneartor for Claim NFTs From fb6baacf65a2628e1ef5ab4096cf734294c7b050 Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 18:37:52 -0500 Subject: [PATCH 10/14] udpates from review --- src/OptionSettlementEngine.sol | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index 3561a75..984b5a7 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -190,10 +190,10 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @inheritdoc IOptionSettlementEngine function decodeTokenId(uint256 tokenId) public pure returns (uint160 optionKey, uint96 claimNum) { - // move key to LSB to fit into uint160 + // move key to lsb to fit into uint160 optionKey = uint160(tokenId >> 96); - // grab lower 96b of id for claim index + // grab lower 96b of id for claim number uint256 claimNumMask = 0xFFFFFFFFFFFFFFFFFFFFFFFF; claimNum = uint96(tokenId & claimNumMask); } @@ -540,11 +540,11 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// are then iterated from oldest to newest (looping if we reach "today") if the /// exercise amount overflows into another bucket. The seed for the pseudorandom /// index is updated accordingly on the option type. - function _assignExercise(uint160 optionId, Option storage optionRecord, uint112 amount) internal { + function _assignExercise(uint160 optionKey, Option storage optionRecord, uint112 amount) internal { // A bucket of the overall amounts written and exercised for all claims // on a given day - OptionsDayBucket[] storage claimBucketArray = _claimBucketByOption[optionId]; - uint16[] storage unexercisedBucketIndices = _unexercisedBucketsByOption[optionId]; + OptionsDayBucket[] storage claimBucketArray = _claimBucketByOption[optionKey]; + uint16[] storage unexercisedBucketIndices = _unexercisedBucketsByOption[optionKey]; uint16 unexercisedBucketsMod = uint16(unexercisedBucketIndices.length); uint16 unexercisedBucketsIndex = uint16(optionRecord.settlementSeed % unexercisedBucketsMod); while (amount > 0) { @@ -562,7 +562,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { unexercisedBucketIndices[unexercisedBucketsIndex] = overwrite; unexercisedBucketIndices.pop(); unexercisedBucketsMod -= 1; - _doesBucketIndexHaveUnexercisedOptions[optionId][bucketIndex] = false; + _doesBucketIndexHaveUnexercisedOptions[optionKey][bucketIndex] = false; } else { amountPresentlyExercised = amount; amount = 0; @@ -606,7 +606,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } /// @dev Get the exercise and underlying amounts for a claim - function _getPositionsForClaim(uint160 optionId, uint256 claimId, Option storage optionRecord) + function _getPositionsForClaim(uint160 optionKey, uint256 claimId, Option storage optionRecord) internal view returns (uint256 exerciseAmount, uint256 underlyingAmount) @@ -614,7 +614,7 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { OptionLotClaimIndex[] storage claimIndexArray = _claimIdToClaimIndexArray[claimId]; for (uint256 i = 0; i < claimIndexArray.length; i++) { OptionLotClaimIndex storage claimIndex = claimIndexArray[i]; - OptionsDayBucket storage claimBucketInfo = _claimBucketByOption[optionId][claimIndex.bucketIndex]; + OptionsDayBucket storage claimBucketInfo = _claimBucketByOption[optionKey][claimIndex.bucketIndex]; (uint256 amountExercised, uint256 amountUnexercised) = _getAmountExercised(claimIndex, claimBucketInfo); exerciseAmount += optionRecord.exerciseAmount * amountExercised; underlyingAmount += optionRecord.underlyingAmount * amountUnexercised; @@ -622,9 +622,9 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } /// @dev Help with internal options bucket accounting - function _addOrUpdateClaimBucket(uint160 optionId, uint112 amount) internal returns (uint16) { - OptionsDayBucket[] storage claimBucketsInfo = _claimBucketByOption[optionId]; - uint16[] storage unexercised = _unexercisedBucketsByOption[optionId]; + function _addOrUpdateClaimBucket(uint160 optionKey, uint112 amount) internal returns (uint16) { + OptionsDayBucket[] storage claimBucketsInfo = _claimBucketByOption[optionKey]; + uint16[] storage unexercised = _unexercisedBucketsByOption[optionKey]; OptionsDayBucket storage currentBucket; uint16 daysAfterEpoch = _getDaysBucket(); uint16 bucketIndex = uint16(claimBucketsInfo.length); @@ -632,14 +632,14 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { // add a new bucket none exist claimBucketsInfo.push(OptionsDayBucket(amount, 0, daysAfterEpoch)); // update _unexercisedBucketsByOption and corresponding index mapping - _updateUnexercisedBucketIndices(optionId, bucketIndex, unexercised); + _updateUnexercisedBucketIndices(optionKey, bucketIndex, unexercised); return bucketIndex; } currentBucket = claimBucketsInfo[bucketIndex - 1]; if (currentBucket.daysAfterEpoch < daysAfterEpoch) { claimBucketsInfo.push(OptionsDayBucket(amount, 0, daysAfterEpoch)); - _updateUnexercisedBucketIndices(optionId, bucketIndex, unexercised); + _updateUnexercisedBucketIndices(optionKey, bucketIndex, unexercised); } else { // Update claim bucket for today currentBucket.amountWritten += amount; @@ -647,8 +647,8 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { // This block is executed if a bucket has been previously fully exercised // and now more options are being written into it - if (!_doesBucketIndexHaveUnexercisedOptions[optionId][bucketIndex]) { - _updateUnexercisedBucketIndices(optionId, bucketIndex, unexercised); + if (!_doesBucketIndexHaveUnexercisedOptions[optionKey][bucketIndex]) { + _updateUnexercisedBucketIndices(optionKey, bucketIndex, unexercised); } } @@ -657,12 +657,12 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { /// @dev Help with internal claim bucket accounting function _updateUnexercisedBucketIndices( - uint160 optionId, + uint160 optionKey, uint16 bucketIndex, uint16[] storage unexercisedBucketIndices ) internal { unexercisedBucketIndices.push(bucketIndex); - _doesBucketIndexHaveUnexercisedOptions[optionId][bucketIndex] = true; + _doesBucketIndexHaveUnexercisedOptions[optionKey][bucketIndex] = true; } /// @dev Help with internal claim bucket accounting From 234e2f06b396a545fdb1cbca04fd484dd043d360 Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 19:04:42 -0500 Subject: [PATCH 11/14] drop dead code --- src/OptionSettlementEngine.sol | 5 ----- src/interfaces/IOptionSettlementEngine.sol | 6 ------ 2 files changed, 11 deletions(-) diff --git a/src/OptionSettlementEngine.sol b/src/OptionSettlementEngine.sol index 984b5a7..264e4a8 100644 --- a/src/OptionSettlementEngine.sol +++ b/src/OptionSettlementEngine.sol @@ -444,11 +444,6 @@ contract OptionSettlementEngine is ERC1155, IOptionSettlementEngine { } OptionLotClaim storage claimRecord = _claim[claimId]; - - if (claimRecord.claimed) { - revert AlreadyClaimed(claimId); - } - Option storage optionRecord = _option[optionKey]; if (optionRecord.expiryTimestamp > block.timestamp) { diff --git a/src/interfaces/IOptionSettlementEngine.sol b/src/interfaces/IOptionSettlementEngine.sol index 4ba0855..bce3ab8 100644 --- a/src/interfaces/IOptionSettlementEngine.sol +++ b/src/interfaces/IOptionSettlementEngine.sol @@ -198,12 +198,6 @@ interface IOptionSettlementEngine { */ error CallerHoldsInsufficientOptions(uint256 optionId, uint112 amount); - /** - * @notice This claimId has already been claimed. - * @param claimId Supplied claim ID. - */ - error AlreadyClaimed(uint256 claimId); - /** * @notice You can't claim before expiry. * @param claimId Supplied claim ID. From e8dc742ac02de152764cb9d32fbe3934c7dfd46b Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 19:04:53 -0500 Subject: [PATCH 12/14] improve test cov --- test/OptionSettlementEngine.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/OptionSettlementEngine.t.sol b/test/OptionSettlementEngine.t.sol index 1bc43c4..8f30f4f 100644 --- a/test/OptionSettlementEngine.t.sol +++ b/test/OptionSettlementEngine.t.sol @@ -1082,8 +1082,8 @@ contract OptionSettlementTest is Test, NFTreceiver { engine.write(invalidOptionId, 1); // Option ID not initialized - invalidOptionId = testOptionId / 2; - vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidOption.selector, invalidOptionId)); + invalidOptionId = engine.encodeTokenId(0x1, 0x0); + vm.expectRevert(abi.encodeWithSelector(IOptionSettlementEngine.InvalidOption.selector, invalidOptionId >> 96)); engine.write(invalidOptionId, 1); } From 4c7f838ded874d4fa3e7625e2e9d3b327b2e1b07 Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 19:26:30 -0500 Subject: [PATCH 13/14] increase test coverage 4 full exercise and subsequent write --- test/OptionSettlementEngine.t.sol | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/OptionSettlementEngine.t.sol b/test/OptionSettlementEngine.t.sol index 8f30f4f..e979b87 100644 --- a/test/OptionSettlementEngine.t.sol +++ b/test/OptionSettlementEngine.t.sol @@ -317,6 +317,19 @@ contract OptionSettlementTest is Test, NFTreceiver { _assertPosition(underlyingPositions.exercisePosition, 2 * testExerciseAmount); } + function testWriteAfterFullyExercisingDay() public { + uint256 claim1 = _writeAndExerciseOption(testOptionId, ALICE, BOB, 2, 2); + uint256 claim2 = _writeAndExerciseOption(testOptionId, ALICE, BOB, 2, 2); + + IOptionSettlementEngine.Underlying memory underlyingPositions = engine.underlying(claim1); + _assertPosition(underlyingPositions.underlyingPosition, 0); + _assertPosition(underlyingPositions.exercisePosition, 2 * testExerciseAmount); + + underlyingPositions = engine.underlying(claim2); + _assertPosition(underlyingPositions.underlyingPosition, 0); + _assertPosition(underlyingPositions.exercisePosition, 2 * testExerciseAmount); + } + function testUnderlyingForFungibleOptionToken() public { IOptionSettlementEngine.Underlying memory underlying = engine.underlying(testOptionId); // before expiry, position is entirely the underlying amount From 577df1c61af7ef70111817b9ca2dc9c18f2ff82f Mon Sep 17 00:00:00 2001 From: Flip Liquid <13227294+Flip-Liquid@users.noreply.github.com> Date: Fri, 18 Nov 2022 20:09:53 -0500 Subject: [PATCH 14/14] lint --- test/OptionSettlementEngine.t.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/OptionSettlementEngine.t.sol b/test/OptionSettlementEngine.t.sol index e979b87..ca5ffd7 100644 --- a/test/OptionSettlementEngine.t.sol +++ b/test/OptionSettlementEngine.t.sol @@ -317,17 +317,17 @@ contract OptionSettlementTest is Test, NFTreceiver { _assertPosition(underlyingPositions.exercisePosition, 2 * testExerciseAmount); } - function testWriteAfterFullyExercisingDay() public { - uint256 claim1 = _writeAndExerciseOption(testOptionId, ALICE, BOB, 2, 2); - uint256 claim2 = _writeAndExerciseOption(testOptionId, ALICE, BOB, 2, 2); + function testWriteAfterFullyExercisingDay() public { + uint256 claim1 = _writeAndExerciseOption(testOptionId, ALICE, BOB, 1, 1); + uint256 claim2 = _writeAndExerciseOption(testOptionId, ALICE, BOB, 1, 1); IOptionSettlementEngine.Underlying memory underlyingPositions = engine.underlying(claim1); _assertPosition(underlyingPositions.underlyingPosition, 0); - _assertPosition(underlyingPositions.exercisePosition, 2 * testExerciseAmount); + _assertPosition(underlyingPositions.exercisePosition, testExerciseAmount); underlyingPositions = engine.underlying(claim2); _assertPosition(underlyingPositions.underlyingPosition, 0); - _assertPosition(underlyingPositions.exercisePosition, 2 * testExerciseAmount); + _assertPosition(underlyingPositions.exercisePosition, testExerciseAmount); } function testUnderlyingForFungibleOptionToken() public {