diff --git a/.circleci/config.yml b/.circleci/config.yml index 9fbed7bde44d8..dcde80397f39e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1304,7 +1304,7 @@ workflows: # Heavily fuzz any fuzz tests within added or modified test files. name: contracts-bedrock-tests-heavy-fuzz-modified test_parallelism: 1 - test_list: git diff origin/develop...HEAD --name-only -- './test/**/*.t.sol' | sed 's|packages/contracts-bedrock/||' + test_list: git diff origin/develop...HEAD --name-only --diff-filter=AM -- './test/**/*.t.sol' | sed 's|packages/contracts-bedrock/||' test_timeout: 1h test_profile: ciheavy - contracts-bedrock-coverage diff --git a/packages/contracts-bedrock/snapshots/abi/AttestationStation.json b/packages/contracts-bedrock/snapshots/abi/AttestationStation.json deleted file mode 100644 index ba7d5f9759e7e..0000000000000 --- a/packages/contracts-bedrock/snapshots/abi/AttestationStation.json +++ /dev/null @@ -1,128 +0,0 @@ -[ - { - "inputs": [ - { - "components": [ - { - "internalType": "address", - "name": "about", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "key", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "val", - "type": "bytes" - } - ], - "internalType": "struct AttestationStation.AttestationData[]", - "name": "_attestations", - "type": "tuple[]" - } - ], - "name": "attest", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_about", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "_key", - "type": "bytes32" - }, - { - "internalType": "bytes", - "name": "_val", - "type": "bytes" - } - ], - "name": "attest", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "attestations", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "creator", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "about", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "key", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "val", - "type": "bytes" - } - ], - "name": "AttestationCreated", - "type": "event" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/Optimist.json b/packages/contracts-bedrock/snapshots/abi/Optimist.json deleted file mode 100644 index 96bbc0591a30e..0000000000000 --- a/packages/contracts-bedrock/snapshots/abi/Optimist.json +++ /dev/null @@ -1,536 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "string", - "name": "_name", - "type": "string" - }, - { - "internalType": "string", - "name": "_symbol", - "type": "string" - }, - { - "internalType": "address", - "name": "_baseURIAttestor", - "type": "address" - }, - { - "internalType": "contract AttestationStation", - "name": "_attestationStation", - "type": "address" - }, - { - "internalType": "contract OptimistAllowlist", - "name": "_optimistAllowlist", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ATTESTATION_STATION", - "outputs": [ - { - "internalType": "contract AttestationStation", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "BASE_URI_ATTESTATION_KEY", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "BASE_URI_ATTESTOR", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "OPTIMIST_ALLOWLIST", - "outputs": [ - { - "internalType": "contract OptimistAllowlist", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "baseURI", - "outputs": [ - { - "internalType": "string", - "name": "uri_", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "getApproved", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_name", - "type": "string" - }, - { - "internalType": "string", - "name": "_symbol", - "type": "string" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "isApprovedForAll", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_recipient", - "type": "address" - } - ], - "name": "isOnAllowList", - "outputs": [ - { - "internalType": "bool", - "name": "allowed_", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_recipient", - "type": "address" - } - ], - "name": "mint", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_owner", - "type": "address" - } - ], - "name": "tokenIdOfAddress", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_tokenId", - "type": "uint256" - } - ], - "name": "tokenURI", - "outputs": [ - { - "internalType": "string", - "name": "uri_", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimistAllowlist.json b/packages/contracts-bedrock/snapshots/abi/OptimistAllowlist.json deleted file mode 100644 index 87ac8f8a014f8..0000000000000 --- a/packages/contracts-bedrock/snapshots/abi/OptimistAllowlist.json +++ /dev/null @@ -1,138 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "contract AttestationStation", - "name": "_attestationStation", - "type": "address" - }, - { - "internalType": "address", - "name": "_allowlistAttestor", - "type": "address" - }, - { - "internalType": "address", - "name": "_coinbaseQuestAttestor", - "type": "address" - }, - { - "internalType": "address", - "name": "_optimistInviter", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ALLOWLIST_ATTESTOR", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "ATTESTATION_STATION", - "outputs": [ - { - "internalType": "contract AttestationStation", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "COINBASE_QUEST_ATTESTOR", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "OPTIMIST_CAN_MINT_ATTESTATION_KEY", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "OPTIMIST_INVITER", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_claimer", - "type": "address" - } - ], - "name": "isAllowedToMint", - "outputs": [ - { - "internalType": "bool", - "name": "allowed_", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimistInviter.json b/packages/contracts-bedrock/snapshots/abi/OptimistInviter.json deleted file mode 100644 index a5300b20a3c10..0000000000000 --- a/packages/contracts-bedrock/snapshots/abi/OptimistInviter.json +++ /dev/null @@ -1,282 +0,0 @@ -[ - { - "inputs": [ - { - "internalType": "address", - "name": "_inviteGranter", - "type": "address" - }, - { - "internalType": "contract AttestationStation", - "name": "_attestationStation", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "ATTESTATION_STATION", - "outputs": [ - { - "internalType": "contract AttestationStation", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "CAN_INVITE_ATTESTATION_KEY", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "CLAIMABLE_INVITE_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "EIP712_VERSION", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "INVITE_GRANTER", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MIN_COMMITMENT_PERIOD", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_claimer", - "type": "address" - }, - { - "components": [ - { - "internalType": "address", - "name": "issuer", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - } - ], - "internalType": "struct OptimistInviter.ClaimableInvite", - "name": "_claimableInvite", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "_signature", - "type": "bytes" - } - ], - "name": "claimInvite", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_commitment", - "type": "bytes32" - } - ], - "name": "commitInvite", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "commitmentTimestamps", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "_name", - "type": "string" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "inviteCounts", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "_accounts", - "type": "address[]" - }, - { - "internalType": "uint256", - "name": "_inviteCount", - "type": "uint256" - } - ], - "name": "setInviteCounts", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "usedNonces", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "Initialized", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "issuer", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "claimer", - "type": "address" - } - ], - "name": "InviteClaimed", - "type": "event" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index dd2618cc5a7ee..767c7eebec16c 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -179,22 +179,6 @@ "initCodeHash": "0xefc6ed9e325c2d614ea0d28c3eabfff1b345f7c6054e90253c6a091c29508267", "sourceCodeHash": "0xaa08a61448f485b277af57251d2089cc6a80ce0a763bf7184d48ffed5034ef69" }, - "src/periphery/op-nft/AttestationStation.sol": { - "initCodeHash": "0x2e665d9ee554430980f64bcb6d2611a1cb03dbacfd58bb0d6f5d32951a267bde", - "sourceCodeHash": "0xe0bc805b22c7d04b5a9444cddd4c0e1bcb3006c69c03610494277ab2cc83f553" - }, - "src/periphery/op-nft/Optimist.sol": { - "initCodeHash": "0x8fccdef5fb6e6d51215b39acc449faad8ba15416699c9b3af77866f4297805a3", - "sourceCodeHash": "0xfa9354827b642803e10415ed30ca789be1bd23d88fac14f7adaa65c6eb1c1643" - }, - "src/periphery/op-nft/OptimistAllowlist.sol": { - "initCodeHash": "0x166dd3fc18cb238895f2faa7fdd635af48ce2c54e21ed2d6dae857c3731c4d6c", - "sourceCodeHash": "0x3a5f61046f729c9a70274b8b2a739382987ec5eb77705b259e8a3210a5f43462" - }, - "src/periphery/op-nft/OptimistInviter.sol": { - "initCodeHash": "0x28dfa6676702a7abd19609cc773158d1f958210bc0a38c008d67a002dc1df862", - "sourceCodeHash": "0x3a0a294932d6deba043f6a2b46b4e8477ee96e7fb054d7e7229a43ce4352c68d" - }, "src/safe/DeputyGuardianModule.sol": { "initCodeHash": "0xd95e562f395d4eb6e332f4474dffab660ada9e9da7c79f58fb6052278e0904df", "sourceCodeHash": "0x45daabe094de0287e244e6fea4f1887b9adc09b07c47dc77361b1678645a1470" diff --git a/packages/contracts-bedrock/snapshots/storageLayout/AttestationStation.json b/packages/contracts-bedrock/snapshots/storageLayout/AttestationStation.json deleted file mode 100644 index c3c732cec14d1..0000000000000 --- a/packages/contracts-bedrock/snapshots/storageLayout/AttestationStation.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "bytes": "32", - "label": "attestations", - "offset": 0, - "slot": "0", - "type": "mapping(address => mapping(address => mapping(bytes32 => bytes)))" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/Optimist.json b/packages/contracts-bedrock/snapshots/storageLayout/Optimist.json deleted file mode 100644 index 6049beb54245e..0000000000000 --- a/packages/contracts-bedrock/snapshots/storageLayout/Optimist.json +++ /dev/null @@ -1,86 +0,0 @@ -[ - { - "bytes": "1", - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "uint8" - }, - { - "bytes": "1", - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "bool" - }, - { - "bytes": "1600", - "label": "__gap", - "offset": 0, - "slot": "1", - "type": "uint256[50]" - }, - { - "bytes": "1600", - "label": "__gap", - "offset": 0, - "slot": "51", - "type": "uint256[50]" - }, - { - "bytes": "32", - "label": "_name", - "offset": 0, - "slot": "101", - "type": "string" - }, - { - "bytes": "32", - "label": "_symbol", - "offset": 0, - "slot": "102", - "type": "string" - }, - { - "bytes": "32", - "label": "_owners", - "offset": 0, - "slot": "103", - "type": "mapping(uint256 => address)" - }, - { - "bytes": "32", - "label": "_balances", - "offset": 0, - "slot": "104", - "type": "mapping(address => uint256)" - }, - { - "bytes": "32", - "label": "_tokenApprovals", - "offset": 0, - "slot": "105", - "type": "mapping(uint256 => address)" - }, - { - "bytes": "32", - "label": "_operatorApprovals", - "offset": 0, - "slot": "106", - "type": "mapping(address => mapping(address => bool))" - }, - { - "bytes": "1408", - "label": "__gap", - "offset": 0, - "slot": "107", - "type": "uint256[44]" - }, - { - "bytes": "1600", - "label": "__gap", - "offset": 0, - "slot": "151", - "type": "uint256[50]" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimistAllowlist.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimistAllowlist.json deleted file mode 100644 index 0637a088a01e8..0000000000000 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimistAllowlist.json +++ /dev/null @@ -1 +0,0 @@ -[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimistInviter.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimistInviter.json deleted file mode 100644 index 5d1a6bbc43c91..0000000000000 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimistInviter.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "bytes": "1", - "label": "_initialized", - "offset": 0, - "slot": "0", - "type": "uint8" - }, - { - "bytes": "1", - "label": "_initializing", - "offset": 1, - "slot": "0", - "type": "bool" - }, - { - "bytes": "32", - "label": "_HASHED_NAME", - "offset": 0, - "slot": "1", - "type": "bytes32" - }, - { - "bytes": "32", - "label": "_HASHED_VERSION", - "offset": 0, - "slot": "2", - "type": "bytes32" - }, - { - "bytes": "1600", - "label": "__gap", - "offset": 0, - "slot": "3", - "type": "uint256[50]" - }, - { - "bytes": "32", - "label": "commitmentTimestamps", - "offset": 0, - "slot": "53", - "type": "mapping(bytes32 => uint256)" - }, - { - "bytes": "32", - "label": "usedNonces", - "offset": 0, - "slot": "54", - "type": "mapping(address => mapping(bytes32 => bool))" - }, - { - "bytes": "32", - "label": "inviteCounts", - "offset": 0, - "slot": "55", - "type": "mapping(address => uint256)" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/periphery/op-nft/AttestationStation.sol b/packages/contracts-bedrock/src/periphery/op-nft/AttestationStation.sol deleted file mode 100644 index 4d15862d43580..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/AttestationStation.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { ISemver } from "src/universal/interfaces/ISemver.sol"; - -/// @title AttestationStation -/// @author Optimism Collective -/// @author Gitcoin -/// @notice Where attestations live. -contract AttestationStation is ISemver { - /// @notice Struct representing data that is being attested. - /// @custom:field about Address for which the attestation is about. - /// @custom:field key A bytes32 key for the attestation. - /// @custom:field val The attestation as arbitrary bytes. - struct AttestationData { - address about; - bytes32 key; - bytes val; - } - - /// @notice Maps addresses to attestations. Creator => About => Key => Value. - mapping(address => mapping(address => mapping(bytes32 => bytes))) public attestations; - - /// @notice Emitted when Attestation is created. - /// @param creator Address that made the attestation. - /// @param about Address attestation is about. - /// @param key Key of the attestation. - /// @param val Value of the attestation. - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - /// @notice Semantic version. - /// @custom:semver 1.2.1-beta.1 - string public constant version = "1.2.1-beta.1"; - - /// @notice Allows anyone to create an attestation. - /// @param _about Address that the attestation is about. - /// @param _key A key used to namespace the attestation. - /// @param _val An arbitrary value stored as part of the attestation. - function attest(address _about, bytes32 _key, bytes memory _val) public { - attestations[msg.sender][_about][_key] = _val; - - emit AttestationCreated(msg.sender, _about, _key, _val); - } - - /// @notice Allows anyone to create attestations. - /// @param _attestations An array of AttestationData structs. - function attest(AttestationData[] calldata _attestations) external { - uint256 length = _attestations.length; - for (uint256 i = 0; i < length;) { - AttestationData memory attestation = _attestations[i]; - - attest(attestation.about, attestation.key, attestation.val); - - unchecked { - ++i; - } - } - } -} diff --git a/packages/contracts-bedrock/src/periphery/op-nft/Optimist.sol b/packages/contracts-bedrock/src/periphery/op-nft/Optimist.sol deleted file mode 100644 index b15c0f00044c6..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/Optimist.sol +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { ERC721BurnableUpgradeable } from - "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721BurnableUpgradeable.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol"; -import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; - -/// @author Optimism Collective -/// @author Gitcoin -/// @title Optimist -/// @notice A Soul Bound Token for real humans only(tm). -contract Optimist is ERC721BurnableUpgradeable, ISemver { - /// @notice Attestation key used by the attestor to attest the baseURI. - bytes32 public constant BASE_URI_ATTESTATION_KEY = bytes32("optimist.base-uri"); - - /// @notice Attestor who attests to baseURI. - address public immutable BASE_URI_ATTESTOR; - - /// @notice Address of the AttestationStation contract. - AttestationStation public immutable ATTESTATION_STATION; - - /// @notice Address of the OptimistAllowlist contract. - OptimistAllowlist public immutable OPTIMIST_ALLOWLIST; - - /// @notice Semantic version. - /// @custom:semver 2.1.1-beta.1 - string public constant version = "2.1.1-beta.1"; - - /// @param _name Token name. - /// @param _symbol Token symbol. - /// @param _baseURIAttestor Address of the baseURI attestor. - /// @param _attestationStation Address of the AttestationStation contract. - /// @param _optimistAllowlist Address of the OptimistAllowlist contract - constructor( - string memory _name, - string memory _symbol, - address _baseURIAttestor, - AttestationStation _attestationStation, - OptimistAllowlist _optimistAllowlist - ) { - BASE_URI_ATTESTOR = _baseURIAttestor; - ATTESTATION_STATION = _attestationStation; - OPTIMIST_ALLOWLIST = _optimistAllowlist; - initialize(_name, _symbol); - } - - /// @notice Initializes the Optimist contract. - /// @param _name Token name. - /// @param _symbol Token symbol. - function initialize(string memory _name, string memory _symbol) public initializer { - __ERC721_init(_name, _symbol); - __ERC721Burnable_init(); - } - - /// @notice Allows an address to mint an Optimist NFT. Token ID is the uint256 representation - /// of the recipient's address. Recipients must be permitted to mint, eventually anyone - /// will be able to mint. One token per address. - /// @param _recipient Address of the token recipient. - function mint(address _recipient) public { - require(isOnAllowList(_recipient), "Optimist: address is not on allowList"); - _safeMint(_recipient, tokenIdOfAddress(_recipient)); - } - - /// @notice Returns the baseURI for all tokens. - /// @return uri_ BaseURI for all tokens. - function baseURI() public view returns (string memory uri_) { - uri_ = string( - abi.encodePacked( - ATTESTATION_STATION.attestations(BASE_URI_ATTESTOR, address(this), bytes32("optimist.base-uri")) - ) - ); - } - - /// @notice Returns the token URI for a given token by ID - /// @param _tokenId Token ID to query. - /// @return uri_ Token URI for the given token by ID. - function tokenURI(uint256 _tokenId) public view virtual override returns (string memory uri_) { - uri_ = string( - abi.encodePacked( - baseURI(), - "/", - // Properly format the token ID as a 20 byte hex string (address). - Strings.toHexString(_tokenId, 20), - ".json" - ) - ); - } - - /// @notice Checks OptimistAllowlist to determine whether a given address is allowed to mint - /// the Optimist NFT. Since the Optimist NFT will also be used as part of the - /// Citizens House, mints are currently restricted. Eventually anyone will be able - /// to mint. - /// @return allowed_ Whether or not the address is allowed to mint yet. - function isOnAllowList(address _recipient) public view returns (bool allowed_) { - allowed_ = OPTIMIST_ALLOWLIST.isAllowedToMint(_recipient); - } - - /// @notice Returns the token ID for the token owned by a given address. This is the uint256 - /// representation of the given address. - /// @return Token ID for the token owned by the given address. - function tokenIdOfAddress(address _owner) public pure returns (uint256) { - return uint256(uint160(_owner)); - } - - /// @notice Disabled for the Optimist NFT (Soul Bound Token). - function approve(address, uint256) public pure override { - revert("Optimist: soul bound token"); - } - - /// @notice Disabled for the Optimist NFT (Soul Bound Token). - function setApprovalForAll(address, bool) public virtual override { - revert("Optimist: soul bound token"); - } - - /// @notice Prevents transfers of the Optimist NFT (Soul Bound Token). - /// @param _from Address of the token sender. - /// @param _to Address of the token recipient. - function _beforeTokenTransfer(address _from, address _to, uint256) internal virtual override { - require(_from == address(0) || _to == address(0), "Optimist: soul bound token"); - } -} diff --git a/packages/contracts-bedrock/src/periphery/op-nft/OptimistAllowlist.sol b/packages/contracts-bedrock/src/periphery/op-nft/OptimistAllowlist.sol deleted file mode 100644 index ffa46116a4c59..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/OptimistAllowlist.sol +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol"; - -/// @title OptimistAllowlist -/// @notice Source of truth for whether an address is able to mint an Optimist NFT. -/// isAllowedToMint function checks various signals to return boolean value -/// for whether an address is eligible or not. -contract OptimistAllowlist is ISemver { - /// @notice Attestation key used by the AllowlistAttestor to manually add addresses to the - /// allowlist. - bytes32 public constant OPTIMIST_CAN_MINT_ATTESTATION_KEY = bytes32("optimist.can-mint"); - - /// @notice Attestation key used by Coinbase to issue attestations for Quest participants. - bytes32 public constant COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY = bytes32("coinbase.quest-eligible"); - - /// @notice Address of the AttestationStation contract. - AttestationStation public immutable ATTESTATION_STATION; - - /// @notice Attestor that issues 'optimist.can-mint' attestations. - address public immutable ALLOWLIST_ATTESTOR; - - /// @notice Attestor that issues 'coinbase.quest-eligible' attestations. - address public immutable COINBASE_QUEST_ATTESTOR; - - /// @notice Address of OptimistInviter contract that issues 'optimist.can-mint-from-invite' - /// attestations. - address public immutable OPTIMIST_INVITER; - - /// @notice Semantic version. - /// @custom:semver 1.1.1-beta.1 - string public constant version = "1.1.1-beta.1"; - - /// @param _attestationStation Address of the AttestationStation contract. - /// @param _allowlistAttestor Address of the allowlist attestor. - /// @param _coinbaseQuestAttestor Address of the Coinbase Quest attestor. - /// @param _optimistInviter Address of the OptimistInviter contract. - constructor( - AttestationStation _attestationStation, - address _allowlistAttestor, - address _coinbaseQuestAttestor, - address _optimistInviter - ) { - ATTESTATION_STATION = _attestationStation; - ALLOWLIST_ATTESTOR = _allowlistAttestor; - COINBASE_QUEST_ATTESTOR = _coinbaseQuestAttestor; - OPTIMIST_INVITER = _optimistInviter; - } - - /// @notice Checks whether a given address is allowed to mint the Optimist NFT yet. Since the - /// Optimist NFT will also be used as part of the Citizens House, mints are currently - /// restricted. Eventually anyone will be able to mint. - /// Currently, address is allowed to mint if it satisfies any of the following: - /// 1) Has a valid 'optimist.can-mint' attestation from the allowlist attestor. - /// 2) Has a valid 'coinbase.quest-eligible' attestation from Coinbase Quest attestor - /// 3) Has a valid 'optimist.can-mint-from-invite' attestation from the OptimistInviter - /// contract. - /// @param _claimer Address to check. - /// @return allowed_ Whether or not the address is allowed to mint yet. - function isAllowedToMint(address _claimer) public view returns (bool allowed_) { - allowed_ = _hasAttestationFromAllowlistAttestor(_claimer) || _hasAttestationFromCoinbaseQuestAttestor(_claimer) - || _hasAttestationFromOptimistInviter(_claimer); - } - - /// @notice Checks whether an address has a valid 'optimist.can-mint' attestation from the - /// allowlist attestor. - /// @param _claimer Address to check. - /// @return valid_ Whether or not the address has a valid attestation. - function _hasAttestationFromAllowlistAttestor(address _claimer) internal view returns (bool valid_) { - // Expected attestation value is bytes32("true") - valid_ = _hasValidAttestation(ALLOWLIST_ATTESTOR, _claimer, OPTIMIST_CAN_MINT_ATTESTATION_KEY); - } - - /// @notice Checks whether an address has a valid attestation from the Coinbase attestor. - /// @param _claimer Address to check. - /// @return valid_ Whether or not the address has a valid attestation. - function _hasAttestationFromCoinbaseQuestAttestor(address _claimer) internal view returns (bool valid_) { - // Expected attestation value is bytes32("true") - valid_ = _hasValidAttestation(COINBASE_QUEST_ATTESTOR, _claimer, COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY); - } - - /// @notice Checks whether an address has a valid attestation from the OptimistInviter contract. - /// @param _claimer Address to check. - /// @return valid_ Whether or not the address has a valid attestation. - function _hasAttestationFromOptimistInviter(address _claimer) internal view returns (bool valid_) { - // Expected attestation value is the inviter's address - valid_ = _hasValidAttestation( - OPTIMIST_INVITER, _claimer, OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY - ); - } - - /// @notice Checks whether an address has a valid truthy attestation. - /// Any attestation val other than bytes32("") is considered truthy. - /// @param _creator Address that made the attestation. - /// @param _about Address attestation is about. - /// @param _key Key of the attestation. - /// @return valid_ Whether or not the address has a valid truthy attestation. - function _hasValidAttestation(address _creator, address _about, bytes32 _key) internal view returns (bool valid_) { - valid_ = ATTESTATION_STATION.attestations(_creator, _about, _key).length > 0; - } -} diff --git a/packages/contracts-bedrock/src/periphery/op-nft/OptimistInviter.sol b/packages/contracts-bedrock/src/periphery/op-nft/OptimistInviter.sol deleted file mode 100644 index ae0ab9d92657c..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/OptimistInviter.sol +++ /dev/null @@ -1,235 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol"; -import { ISemver } from "src/universal/interfaces/ISemver.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; -import { EIP712Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol"; - -/// @custom:upgradeable -/// @title OptimistInviter -/// @notice OptimistInviter issues "optimist.can-invite" and "optimist.can-mint-from-invite" -/// attestations. Accounts that have invites can issue signatures that allow other -/// accounts to claim an invite. The invitee uses a claim and reveal flow to claim the -/// invite to an address of their choosing. -/// -/// Parties involved: -/// 1) INVITE_GRANTER: trusted account that can allow accounts to issue invites -/// 2) issuer: account that is allowed to issue invites -/// 3) claimer: account that receives the invites -/// -/// Flow: -/// 1) INVITE_GRANTER calls _setInviteCount to allow an issuer to issue a certain number -/// of invites, and also creates a "optimist.can-invite" attestation for the issuer -/// 2) Off-chain, the issuer signs (EIP-712) a ClaimableInvite to produce a signature -/// 3) Off-chain, invite issuer sends the plaintext ClaimableInvite and the signature -/// to the recipient -/// 4) claimer chooses an address they want to receive the invite on -/// 5) claimer commits the hash of the address they want to receive the invite on and the -/// received signature keccak256(abi.encode(addressToReceiveTo, receivedSignature)) -/// using the commitInvite function -/// 6) claimer waits for the MIN_COMMITMENT_PERIOD to pass. -/// 7) claimer reveals the plaintext ClaimableInvite and the signature using the -/// claimInvite function, receiving the "optimist.can-mint-from-invite" attestation -contract OptimistInviter is ISemver, EIP712Upgradeable { - /// @notice Emitted when an invite is claimed. - /// @param issuer Address that issued the signature. - /// @param claimer Address that claimed the invite. - event InviteClaimed(address indexed issuer, address indexed claimer); - - /// @notice Version used for the EIP712 domain separator. This version is separated from the - /// contract semver because the EIP712 domain separator is used to sign messages, and - /// changing the domain separator invalidates all existing signatures. We should only - /// bump this version if we make a major change to the signature scheme. - string public constant EIP712_VERSION = "1.0.0"; - - /// @notice EIP712 typehash for the ClaimableInvite type. - bytes32 public constant CLAIMABLE_INVITE_TYPEHASH = keccak256("ClaimableInvite(address issuer,bytes32 nonce)"); - - /// @notice Attestation key for that signals that an account was allowed to issue invites - bytes32 public constant CAN_INVITE_ATTESTATION_KEY = bytes32("optimist.can-invite"); - - /// @notice Granter who can set accounts' invite counts. - address public immutable INVITE_GRANTER; - - /// @notice Address of the AttestationStation contract. - AttestationStation public immutable ATTESTATION_STATION; - - /// @notice Minimum age of a commitment (in seconds) before it can be revealed using - /// claimInvite. Currently set to 60 seconds. - /// - /// Prevents an attacker from front-running a commitment by taking the signature in the - /// claimInvite call and quickly committing and claiming it before the the claimer's - /// transaction succeeds. With this, frontrunning a commitment requires that an attacker - /// be able to prevent the honest claimer's claimInvite transaction from being included - /// for this long. - uint256 public constant MIN_COMMITMENT_PERIOD = 60; - - /// @notice Struct that represents a claimable invite that will be signed by the issuer. - /// @custom:field issuer Address that issued the signature. Reason this is explicitly included, - /// and not implicitly assumed to be the recovered address from the - /// signature is that the issuer may be using a ERC-1271 compatible - /// contract wallet, where the recovered address is not the same as the - /// issuer, or the signature is not an ECDSA signature at all. - /// @custom:field nonce Pseudorandom nonce to prevent replay attacks. - struct ClaimableInvite { - address issuer; - bytes32 nonce; - } - - /// @notice Maps from hashes to the timestamp when they were committed. - mapping(bytes32 => uint256) public commitmentTimestamps; - - /// @notice Maps from addresses to nonces to whether or not they have been used. - mapping(address => mapping(bytes32 => bool)) public usedNonces; - - /// @notice Maps from addresses to number of invites they have. - mapping(address => uint256) public inviteCounts; - - /// @notice Semantic version. - /// @custom:semver 1.1.1-beta.1 - string public constant version = "1.1.1-beta.1"; - - /// @param _inviteGranter Address of the invite granter. - /// @param _attestationStation Address of the AttestationStation contract. - constructor(address _inviteGranter, AttestationStation _attestationStation) { - INVITE_GRANTER = _inviteGranter; - ATTESTATION_STATION = _attestationStation; - } - - /// @notice Initializes this contract, setting the EIP712 context. - /// Only update the EIP712_VERSION when there is a change to the signature scheme. - /// After the EIP712 version is changed, any signatures issued off-chain but not - /// claimed yet will no longer be accepted by the claimInvite function. Please make - /// sure to notify the issuers that they must re-issue their invite signatures. - /// @param _name Contract name. - function initialize(string memory _name) public initializer { - __EIP712_init(_name, EIP712_VERSION); - } - - /// @notice Allows invite granter to set the number of invites an address has. - /// @param _accounts An array of accounts to update the invite counts of. - /// @param _inviteCount Number of invites to set to. - function setInviteCounts(address[] calldata _accounts, uint256 _inviteCount) public { - // Only invite granter can grant invites - require(msg.sender == INVITE_GRANTER, "OptimistInviter: only invite granter can grant invites"); - - uint256 length = _accounts.length; - - AttestationStation.AttestationData[] memory attestations = new AttestationStation.AttestationData[](length); - - for (uint256 i; i < length;) { - // Set invite count for account to _inviteCount - inviteCounts[_accounts[i]] = _inviteCount; - - // Create an attestation for posterity that the account is allowed to create invites - attestations[i] = AttestationStation.AttestationData({ - about: _accounts[i], - key: CAN_INVITE_ATTESTATION_KEY, - val: bytes("true") - }); - - unchecked { - ++i; - } - } - - ATTESTATION_STATION.attest(attestations); - } - - /// @notice Allows anyone (but likely the claimer) to commit a received signature along with the - /// address to claim to. - /// - /// Before calling this function, the claimer should have received a signature from the - /// issuer off-chain. The claimer then calls this function with the hash of the - /// claimer's address and the received signature. This is necessary to prevent - /// front-running when the invitee is claiming the invite. Without a commit and reveal - /// scheme, anyone who is watching the mempool can take the signature being submitted - /// and front run the transaction to claim the invite to their own address. - /// - /// The same commitment can only be made once, and the function reverts if the - /// commitment has already been made. This prevents griefing where a malicious party can - /// prevent the original claimer from being able to claimInvite. - /// @param _commitment A hash of the claimer and signature concatenated. - /// keccak256(abi.encode(_claimer, _signature)) - function commitInvite(bytes32 _commitment) public { - // Check that the commitment hasn't already been made. This prevents griefing where - // a malicious party continuously re-submits the same commitment, preventing the original - // claimer from claiming their invite by resetting the minimum commitment period. - require(commitmentTimestamps[_commitment] == 0, "OptimistInviter: commitment already made"); - - commitmentTimestamps[_commitment] = block.timestamp; - } - - /// @notice Allows anyone to reveal a commitment and claim an invite. - /// The hash, keccak256(abi.encode(_claimer, _signature)), should have been already - /// committed using commitInvite. Before issuing the "optimist.can-mint-from-invite" - /// attestation, this function checks that - /// 1) the hash corresponding to the _claimer and the _signature was committed - /// 2) MIN_COMMITMENT_PERIOD has passed since the commitment was made. - /// 3) the _signature is signed correctly by the issuer - /// 4) the _signature hasn't already been used to claim an invite before - /// 5) the _signature issuer has not used up all of their invites - /// This function doesn't require that the _claimer is calling this function. - /// @param _claimer Address that will be granted the invite. - /// @param _claimableInvite ClaimableInvite struct containing the issuer and nonce. - /// @param _signature Signature signed over the claimable invite. - function claimInvite(address _claimer, ClaimableInvite calldata _claimableInvite, bytes memory _signature) public { - uint256 commitmentTimestamp = commitmentTimestamps[keccak256(abi.encode(_claimer, _signature))]; - - // Make sure the claimer and signature have been committed. - require(commitmentTimestamp > 0, "OptimistInviter: claimer and signature have not been committed yet"); - - // Check that MIN_COMMITMENT_PERIOD has passed since the commitment was made. - require( - commitmentTimestamp + MIN_COMMITMENT_PERIOD <= block.timestamp, - "OptimistInviter: minimum commitment period has not elapsed yet" - ); - - // Generate a EIP712 typed data hash to compare against the signature. - bytes32 digest = _hashTypedDataV4( - keccak256(abi.encode(CLAIMABLE_INVITE_TYPEHASH, _claimableInvite.issuer, _claimableInvite.nonce)) - ); - - // Uses SignatureChecker, which supports both regular ECDSA signatures from EOAs as well as - // ERC-1271 signatures from contract wallets or multi-sigs. This means that if the issuer - // wants to revoke a signature, they can use a smart contract wallet to issue the signature, - // then invalidate the signature after issuing it. - require( - SignatureChecker.isValidSignatureNow(_claimableInvite.issuer, digest, _signature), - "OptimistInviter: invalid signature" - ); - - // The issuer's signature commits to a nonce to prevent replay attacks. - // This checks that the nonce has not been used for this issuer before. The nonces are - // scoped to the issuer address, so the same nonce can be used by different issuers without - // clashing. - require( - usedNonces[_claimableInvite.issuer][_claimableInvite.nonce] == false, - "OptimistInviter: nonce has already been used" - ); - - // Set the nonce as used for the issuer so that it cannot be replayed. - usedNonces[_claimableInvite.issuer][_claimableInvite.nonce] = true; - - // Failing this check means that the issuer has used up all of their existing invites. - require(inviteCounts[_claimableInvite.issuer] > 0, "OptimistInviter: issuer has no invites"); - - // Reduce the issuer's invite count by 1. Can be unchecked because we check above that - // count is > 0. - unchecked { - --inviteCounts[_claimableInvite.issuer]; - } - - // Create the attestation that the claimer can mint from the issuer's invite. - // The invite issuer is included in the data of the attestation. - ATTESTATION_STATION.attest( - _claimer, - OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, - abi.encode(_claimableInvite.issuer) - ); - - emit InviteClaimed(_claimableInvite.issuer, _claimer); - } -} diff --git a/packages/contracts-bedrock/src/periphery/op-nft/libraries/OptimistConstants.sol b/packages/contracts-bedrock/src/periphery/op-nft/libraries/OptimistConstants.sol deleted file mode 100644 index 225f778894954..0000000000000 --- a/packages/contracts-bedrock/src/periphery/op-nft/libraries/OptimistConstants.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -/// @title OptimistConstants -/// @notice Library for storing Optimist related constants that are shared in multiple contracts. -library OptimistConstants { - /// @notice Attestation key issued by OptimistInviter allowing the attested account to mint. - bytes32 internal constant OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY = bytes32("optimist.can-mint-from-invite"); -} diff --git a/packages/contracts-bedrock/test/mocks/OptimistInviterHelper.sol b/packages/contracts-bedrock/test/mocks/OptimistInviterHelper.sol deleted file mode 100644 index ebc2289f9c10d..0000000000000 --- a/packages/contracts-bedrock/test/mocks/OptimistInviterHelper.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; -import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; - -/// @notice Simple helper contract that helps with testing flow and signature for -/// OptimistInviter contract. Made this a separate contract instead of including -/// in OptimistInviter.t.sol for reusability. -contract OptimistInviterHelper { - /// @notice EIP712 typehash for the ClaimableInvite type. - bytes32 public constant CLAIMABLE_INVITE_TYPEHASH = keccak256("ClaimableInvite(address issuer,bytes32 nonce)"); - - /// @notice EIP712 typehash for the EIP712Domain type that is included as part of the signature. - bytes32 public constant EIP712_DOMAIN_TYPEHASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - /// @notice Address of OptimistInviter contract we are testing. - OptimistInviter public optimistInviter; - - /// @notice OptimistInviter contract name. Used to construct the EIP-712 domain. - string public name; - - /// @notice Keeps track of current nonce to generate new nonces for each invite. - uint256 public currentNonce; - - constructor(OptimistInviter _optimistInviter, string memory _name) { - optimistInviter = _optimistInviter; - name = _name; - } - - /// @notice Returns the hash of the struct ClaimableInvite. - /// @param _claimableInvite ClaimableInvite struct to hash. - /// @return EIP-712 typed struct hash. - function getClaimableInviteStructHash(OptimistInviter.ClaimableInvite memory _claimableInvite) - public - pure - returns (bytes32) - { - return keccak256(abi.encode(CLAIMABLE_INVITE_TYPEHASH, _claimableInvite.issuer, _claimableInvite.nonce)); - } - - /// @notice Returns a bytes32 nonce that should change everytime. In practice, people should use - /// pseudorandom nonces. - /// @return Nonce that should be used as part of ClaimableInvite. - function consumeNonce() public returns (bytes32) { - return bytes32(keccak256(abi.encode(currentNonce++))); - } - - /// @notice Returns a ClaimableInvite with the issuer and current nonce. - /// @param _issuer Issuer to include in the ClaimableInvite. - /// @return ClaimableInvite that can be hashed & signed. - function getClaimableInviteWithNewNonce(address _issuer) public returns (OptimistInviter.ClaimableInvite memory) { - return OptimistInviter.ClaimableInvite(_issuer, consumeNonce()); - } - - /// @notice Computes the EIP712 digest with default correct parameters. - /// @param _claimableInvite ClaimableInvite struct to hash. - /// @return EIP-712 compatible digest. - function getDigest(OptimistInviter.ClaimableInvite calldata _claimableInvite) public view returns (bytes32) { - return getDigestWithEIP712Domain( - _claimableInvite, - bytes(name), - bytes(optimistInviter.EIP712_VERSION()), - block.chainid, - address(optimistInviter) - ); - } - - /// @notice Computes the EIP712 digest with the given domain parameters. - /// Used for testing that different domain parameters fail. - /// @param _claimableInvite ClaimableInvite struct to hash. - /// @param _name Contract name to use in the EIP712 domain. - /// @param _version Contract version to use in the EIP712 domain. - /// @param _chainid Chain ID to use in the EIP712 domain. - /// @param _verifyingContract Address to use in the EIP712 domain. - /// @return EIP-712 compatible digest. - function getDigestWithEIP712Domain( - OptimistInviter.ClaimableInvite calldata _claimableInvite, - bytes memory _name, - bytes memory _version, - uint256 _chainid, - address _verifyingContract - ) - public - pure - returns (bytes32) - { - bytes32 domainSeparator = keccak256( - abi.encode(EIP712_DOMAIN_TYPEHASH, keccak256(_name), keccak256(_version), _chainid, _verifyingContract) - ); - return ECDSA.toTypedDataHash(domainSeparator, getClaimableInviteStructHash(_claimableInvite)); - } -} diff --git a/packages/contracts-bedrock/test/periphery/op-nft/AttestationStation.t.sol b/packages/contracts-bedrock/test/periphery/op-nft/AttestationStation.t.sol deleted file mode 100644 index 4c9b72254d347..0000000000000 --- a/packages/contracts-bedrock/test/periphery/op-nft/AttestationStation.t.sol +++ /dev/null @@ -1,115 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -/* Testing utilities */ -import { Test } from "forge-std/Test.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; - -contract AttestationStation_Initializer is Test { - address alice_attestor = address(128); - address bob = address(256); - address sally = address(512); - - function setUp() public { - // Give alice and bob some ETH - vm.deal(alice_attestor, 1 ether); - - vm.label(alice_attestor, "alice_attestor"); - vm.label(bob, "bob"); - vm.label(sally, "sally"); - } -} - -contract AttestationStationTest is AttestationStation_Initializer { - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - function test_attest_individual_succeeds() external { - AttestationStation attestationStation = new AttestationStation(); - - vm.expectEmit(true, true, true, true); - emit AttestationCreated(alice_attestor, bob, bytes32("foo"), bytes("bar")); - - vm.prank(alice_attestor); - attestationStation.attest({ _about: bob, _key: bytes32("foo"), _val: bytes("bar") }); - } - - function test_attest_single_succeeds() external { - AttestationStation attestationStation = new AttestationStation(); - - AttestationStation.AttestationData[] memory attestationDataArr = new AttestationStation.AttestationData[](1); - - // alice is going to attest about bob - AttestationStation.AttestationData memory attestationData = AttestationStation.AttestationData({ - about: bob, - key: bytes32("test-key:string"), - val: bytes("test-value") - }); - - // assert the attestation starts empty - assertEq(attestationStation.attestations(alice_attestor, attestationData.about, attestationData.key), ""); - - // make attestation - vm.prank(alice_attestor); - attestationDataArr[0] = attestationData; - attestationStation.attest(attestationDataArr); - - // assert the attestation is there - assertEq( - attestationStation.attestations(alice_attestor, attestationData.about, attestationData.key), - attestationData.val - ); - - bytes memory new_val = bytes("new updated value"); - // make a new attestations to same about and key - attestationData = - AttestationStation.AttestationData({ about: attestationData.about, key: attestationData.key, val: new_val }); - - vm.prank(alice_attestor); - attestationDataArr[0] = attestationData; - attestationStation.attest(attestationDataArr); - - // assert the attestation is updated - assertEq( - attestationStation.attestations(alice_attestor, attestationData.about, attestationData.key), - attestationData.val - ); - } - - function test_attest_bulk_succeeds() external { - AttestationStation attestationStation = new AttestationStation(); - - vm.prank(alice_attestor); - - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](3); - attestationData[0] = AttestationStation.AttestationData({ - about: bob, - key: bytes32("test-key:string"), - val: bytes("test-value") - }); - - attestationData[1] = - AttestationStation.AttestationData({ about: bob, key: bytes32("test-key2"), val: bytes("test-value2") }); - - attestationData[2] = AttestationStation.AttestationData({ - about: sally, - key: bytes32("test-key:string"), - val: bytes("test-value3") - }); - - attestationStation.attest(attestationData); - - // assert the attestations are there - assertEq( - attestationStation.attestations(alice_attestor, attestationData[0].about, attestationData[0].key), - attestationData[0].val - ); - assertEq( - attestationStation.attestations(alice_attestor, attestationData[1].about, attestationData[1].key), - attestationData[1].val - ); - assertEq( - attestationStation.attestations(alice_attestor, attestationData[2].about, attestationData[2].key), - attestationData[2].val - ); - } -} diff --git a/packages/contracts-bedrock/test/periphery/op-nft/Optimist.t.sol b/packages/contracts-bedrock/test/periphery/op-nft/Optimist.t.sol deleted file mode 100644 index 2eb2f07e860f2..0000000000000 --- a/packages/contracts-bedrock/test/periphery/op-nft/Optimist.t.sol +++ /dev/null @@ -1,543 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.6.2 <0.9.0; - -// Testing utilities -import { Test } from "forge-std/Test.sol"; -import { IMulticall3 } from "forge-std/interfaces/IMulticall3.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { Optimist } from "src/periphery/op-nft/Optimist.sol"; -import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol"; -import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; -import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; - -library Multicall { - bytes internal constant code = - hex"6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c0033"; - address internal constant addr = 0xcA11bde05977b3631167028862bE2a173976CA11; -} - -contract Optimist_Initializer is Test { - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - event Initialized(uint8); - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - string constant name = "Optimist name"; - string constant symbol = "OPTIMISTSYMBOL"; - string constant base_uri = "https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes"; - AttestationStation attestationStation; - Optimist optimist; - OptimistAllowlist optimistAllowlist; - OptimistInviter optimistInviter; - - // Helps with EIP-712 signature generation - OptimistInviterHelper optimistInviterHelper; - - // To test multicall for claiming and minting in one call - IMulticall3 multicall3; - - address internal carol_baseURIAttestor; - address internal alice_allowlistAttestor; - address internal eve_inviteGranter; - address internal ted_coinbaseAttestor; - address internal bob; - address internal sally; - - /// @notice BaseURI attestor sets the baseURI of the Optimist NFT. - function _attestBaseURI(string memory _baseUri) internal { - bytes32 baseURIAttestationKey = optimist.BASE_URI_ATTESTATION_KEY(); - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - attestationData[0] = - AttestationStation.AttestationData(address(optimist), baseURIAttestationKey, bytes(_baseUri)); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated(carol_baseURIAttestor, address(optimist), baseURIAttestationKey, bytes(_baseUri)); - vm.prank(carol_baseURIAttestor); - attestationStation.attest(attestationData); - } - - /// @notice Allowlist attestor creates an attestation for an address. - function _attestAllowlist(address _about) internal { - bytes32 attestationKey = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - // we are using true but it can be any non empty value - attestationData[0] = - AttestationStation.AttestationData({ about: _about, key: attestationKey, val: bytes("true") }); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated(alice_allowlistAttestor, _about, attestationKey, bytes("true")); - - vm.prank(alice_allowlistAttestor); - attestationStation.attest(attestationData); - - assertTrue(optimist.isOnAllowList(_about)); - } - - /// @notice Coinbase Quest attestor creates an attestation for an address. - function _attestCoinbaseQuest(address _about) internal { - bytes32 attestationKey = optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(); - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - // we are using true but it can be any non empty value - attestationData[0] = - AttestationStation.AttestationData({ about: _about, key: attestationKey, val: bytes("true") }); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated(ted_coinbaseAttestor, _about, attestationKey, bytes("true")); - - vm.prank(ted_coinbaseAttestor); - attestationStation.attest(attestationData); - - assertTrue(optimist.isOnAllowList(_about)); - } - - /// @notice Issues invite, then claims it using the claimer's address. - function _inviteAndClaim(address _about) internal { - uint256 inviterPrivateKey = 0xbeefbeef; - address inviter = vm.addr(inviterPrivateKey); - - address[] memory addresses = new address[](1); - addresses[0] = inviter; - - vm.prank(eve_inviteGranter); - - // grant invites to Inviter; - optimistInviter.setInviteCounts(addresses, 3); - - // issue a new invite - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(inviter); - - // EIP-712 sign with Inviter's private key - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(inviterPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); - bytes memory signature = abi.encodePacked(r, s, v); - - bytes32 hashedCommit = keccak256(abi.encode(_about, signature)); - - // commit the invite - vm.prank(_about); - optimistInviter.commitInvite(hashedCommit); - - // wait minimum commitment period - vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); - - // reveal and claim the invite - optimistInviter.claimInvite(_about, claimableInvite, signature); - - assertTrue(optimist.isOnAllowList(_about)); - } - - /// @notice Mocks the allowlistAttestor to always return true for a given address. - function _mockAllowlistTrueFor(address _claimer) internal { - vm.mockCall( - address(optimistAllowlist), abi.encodeCall(OptimistAllowlist.isAllowedToMint, (_claimer)), abi.encode(true) - ); - - assertTrue(optimist.isOnAllowList(_claimer)); - } - - /// @notice Returns address as uint256. - function _getTokenId(address _owner) internal pure returns (uint256) { - return uint256(uint160(address(_owner))); - } - - function setUp() public { - carol_baseURIAttestor = makeAddr("carol_baseURIAttestor"); - alice_allowlistAttestor = makeAddr("alice_allowlistAttestor"); - eve_inviteGranter = makeAddr("eve_inviteGranter"); - ted_coinbaseAttestor = makeAddr("ted_coinbaseAttestor"); - bob = makeAddr("bob"); - sally = makeAddr("sally"); - _initializeContracts(); - } - - function _initializeContracts() internal { - attestationStation = new AttestationStation(); - vm.expectEmit(true, true, false, false); - emit Initialized(1); - - optimistInviter = - new OptimistInviter({ _inviteGranter: eve_inviteGranter, _attestationStation: attestationStation }); - - optimistInviter.initialize("OptimistInviter"); - - // Initialize the helper which helps sign EIP-712 signatures - optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter"); - - optimistAllowlist = new OptimistAllowlist({ - _attestationStation: attestationStation, - _allowlistAttestor: alice_allowlistAttestor, - _coinbaseQuestAttestor: ted_coinbaseAttestor, - _optimistInviter: address(optimistInviter) - }); - - optimist = new Optimist({ - _name: name, - _symbol: symbol, - _baseURIAttestor: carol_baseURIAttestor, - _attestationStation: attestationStation, - _optimistAllowlist: optimistAllowlist - }); - - multicall3 = IMulticall3(Multicall.addr); - vm.etch(Multicall.addr, Multicall.code); - } -} - -contract OptimistTest is Optimist_Initializer { - /// @notice Check that constructor and initializer parameters are correctly set. - function test_initialize_succeeds() external view { - // expect name to be set - assertEq(optimist.name(), name); - // expect symbol to be set - assertEq(optimist.symbol(), symbol); - // expect attestationStation to be set - assertEq(address(optimist.ATTESTATION_STATION()), address(attestationStation)); - assertEq(optimist.BASE_URI_ATTESTOR(), carol_baseURIAttestor); - } - - /// @notice Bob should be able to mint an NFT if he is allowlisted - /// by the allowlistAttestor and has a balance of 0. - function test_mint_afterAllowlistAttestation_succeeds() external { - // bob should start with 0 balance - assertEq(optimist.balanceOf(bob), 0); - - // allowlist bob - _attestAllowlist(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // Check that the OptimistAllowlist is checked - bytes memory data = abi.encodeCall(OptimistAllowlist.isAllowedToMint, (bob)); - vm.expectCall(address(optimistAllowlist), data); - - // mint an NFT and expect mint transfer event to be emitted - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - vm.prank(bob); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Bob should be able to mint an NFT if he claimed an invite through OptimistInviter - /// and has a balance of 0. - function test_mint_afterInviteClaimed_succeeds() external { - // bob should start with 0 balance - assertEq(optimist.balanceOf(bob), 0); - - // bob claims an invite - _inviteAndClaim(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // Check that the OptimistAllowlist is checked - bytes memory data = abi.encodeCall(OptimistAllowlist.isAllowedToMint, (bob)); - vm.expectCall(address(optimistAllowlist), data); - - // mint an NFT and expect mint transfer event to be emitted - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - vm.prank(bob); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Bob should be able to mint an NFT if he has an attestation from Coinbase Quest - /// attestor and has a balance of 0. - function test_mint_afterCoinbaseQuestAttestation_succeeds() external { - // bob should start with 0 balance - assertEq(optimist.balanceOf(bob), 0); - - // bob receives attestation from Coinbase Quest attestor - _attestCoinbaseQuest(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // Check that the OptimistAllowlist is checked - bytes memory data = abi.encodeCall(OptimistAllowlist.isAllowedToMint, (bob)); - vm.expectCall(address(optimistAllowlist), data); - - // mint an NFT and expect mint transfer event to be emitted - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - vm.prank(bob); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Multiple valid attestations should allow Bob to mint. - function test_mint_afterMultipleAttestations_succeeds() external { - // bob should start with 0 balance - assertEq(optimist.balanceOf(bob), 0); - - // bob receives attestation from Coinbase Quest attestor - _attestCoinbaseQuest(bob); - - // allowlist bob - _attestAllowlist(bob); - - // bob claims an invite - _inviteAndClaim(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // Check that the OptimistAllowlist is checked - bytes memory data = abi.encodeCall(OptimistAllowlist.isAllowedToMint, (bob)); - vm.expectCall(address(optimistAllowlist), data); - - // mint an NFT and expect mint transfer event to be emitted - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - vm.prank(bob); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Sally should be able to mint a token on behalf of bob. - function test_mint_secondaryMinter_succeeds() external { - _mockAllowlistTrueFor(bob); - - vm.expectEmit(true, true, true, true); - emit Transfer(address(0), bob, _getTokenId(bob)); - - // mint as sally instead of bob - vm.prank(sally); - optimist.mint(bob); - - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Bob should not be able to mint an NFT if he is not allowlisted. - function test_mint_forNonAllowlistedClaimer_reverts() external { - vm.prank(bob); - vm.expectRevert("Optimist: address is not on allowList"); - optimist.mint(bob); - } - - /// @notice Bob's tx should revert if he already minted. - function test_mint_forAlreadyMintedClaimer_reverts() external { - _attestAllowlist(bob); - - // mint initial nft with bob - vm.prank(bob); - optimist.mint(bob); - // expect the NFT to be owned by bob - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - - // attempt to mint again - vm.expectRevert("ERC721: token already minted"); - optimist.mint(bob); - } - - /// @notice The baseURI should be set by attestation station by the baseURIAttestor. - function test_baseURI_returnsCorrectBaseURI_succeeds() external { - _attestBaseURI(base_uri); - - bytes memory data = abi.encodeCall( - attestationStation.attestations, - (carol_baseURIAttestor, address(optimist), optimist.BASE_URI_ATTESTATION_KEY()) - ); - vm.expectCall(address(attestationStation), data); - vm.prank(carol_baseURIAttestor); - - // assert baseURI is set - assertEq(optimist.baseURI(), base_uri); - } - - /// @notice tokenURI should return the token uri for a minted token. - function test_tokenURI_returnsCorrectTokenURI_succeeds() external { - // we are using true but it can be any non empty value - _attestBaseURI(base_uri); - - // mint an NFT - _mockAllowlistTrueFor(bob); - vm.prank(bob); - optimist.mint(bob); - - // assert tokenURI is set - assertEq(optimist.baseURI(), base_uri); - assertEq( - optimist.tokenURI(_getTokenId(bob)), - "https://storageapi.fleek.co/6442819a1b05-bucket/optimist-nft/attributes/0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e.json" - ); - } - - /// @notice Should return the token id of the owner. - function test_tokenIdOfAddress_returnsOwnerID_succeeds() external { - uint256 willTokenId = 1024; - address will = address(1024); - - _mockAllowlistTrueFor(will); - - optimist.mint(will); - - assertEq(optimist.tokenIdOfAddress(will), willTokenId); - } - - /// @notice transferFrom should revert since Optimist is a SBT. - function test_transferFrom_soulbound_reverts() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - - // attempt to transfer to sally - vm.expectRevert(bytes("Optimist: soul bound token")); - vm.prank(bob); - optimist.transferFrom(bob, sally, _getTokenId(bob)); - - // attempt to transfer to sally - vm.expectRevert(bytes("Optimist: soul bound token")); - vm.prank(bob); - optimist.safeTransferFrom(bob, sally, _getTokenId(bob)); - // attempt to transfer to sally - vm.expectRevert(bytes("Optimist: soul bound token")); - vm.prank(bob); - optimist.safeTransferFrom(bob, sally, _getTokenId(bob), bytes("0x")); - } - - /// @notice approve should revert since Optimist is a SBT. - function test_approve_soulbound_reverts() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - - // attempt to approve sally - vm.prank(bob); - vm.expectRevert("Optimist: soul bound token"); - optimist.approve(address(attestationStation), _getTokenId(bob)); - - assertEq(optimist.getApproved(_getTokenId(bob)), address(0)); - } - - /// @notice setApprovalForAll should revert since Optimist is a SBT. - function test_setApprovalForAll_soulbound_reverts() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - vm.prank(alice_allowlistAttestor); - vm.expectRevert(bytes("Optimist: soul bound token")); - optimist.setApprovalForAll(alice_allowlistAttestor, true); - - // expect approval amount to stil be 0 - assertEq(optimist.getApproved(_getTokenId(bob)), address(0)); - // isApprovedForAll should return false - assertEq(optimist.isApprovedForAll(alice_allowlistAttestor, alice_allowlistAttestor), false); - } - - /// @notice Only owner should be able to burn token. - function test_burn_byOwner_succeeds() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - - // burn as bob - vm.prank(bob); - optimist.burn(_getTokenId(bob)); - - // expect bob to have no balance now - assertEq(optimist.balanceOf(bob), 0); - } - - /// @notice Non-owner attempting to burn token should revert. - function test_burn_byNonOwner_reverts() external { - _mockAllowlistTrueFor(bob); - - // mint as bob - vm.prank(bob); - optimist.mint(bob); - - vm.expectRevert("ERC721: caller is not token owner nor approved"); - // burn as Sally - vm.prank(sally); - optimist.burn(_getTokenId(bob)); - - // expect bob to have still have the token - assertEq(optimist.balanceOf(bob), 1); - } - - /// @notice Should support ERC-721 interface. - function test_supportsInterface_returnsCorrectInterfaceForERC721_succeeds() external view { - bytes4 iface721 = type(IERC721).interfaceId; - // check that it supports ERC-721 interface - assertEq(optimist.supportsInterface(iface721), true); - } - - /// @notice Checking that multi-call using the invite & claim flow works correctly, since the - /// frontend will be making multicalls to improve UX. The OptimistInviter.claimInvite - /// and Optimist.mint will be batched - function test_multicall_batchingClaimAndMint_succeeds() external { - uint256 inviterPrivateKey = 0xbeefbeef; - address inviter = vm.addr(inviterPrivateKey); - - address[] memory addresses = new address[](1); - addresses[0] = inviter; - - vm.prank(eve_inviteGranter); - - // grant invites to Inviter; - optimistInviter.setInviteCounts(addresses, 3); - - // issue a new invite - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(inviter); - - // EIP-712 sign with Inviter's private key - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(inviterPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); - bytes memory signature = abi.encodePacked(r, s, v); - - bytes32 hashedCommit = keccak256(abi.encode(bob, signature)); - - // commit the invite - vm.prank(bob); - optimistInviter.commitInvite(hashedCommit); - - // wait minimum commitment period - vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); - - IMulticall3.Call3[] memory calls = new IMulticall3.Call3[](2); - - // First call is to claim the invite, receiving the attestation - calls[0] = IMulticall3.Call3({ - target: address(optimistInviter), - callData: abi.encodeCall(OptimistInviter.claimInvite, (bob, claimableInvite, signature)), - allowFailure: false - }); - - // Second call is to mint the Optimist NFT - calls[1] = IMulticall3.Call3({ - target: address(optimist), - callData: abi.encodeCall(Optimist.mint, (bob)), - allowFailure: false - }); - - multicall3.aggregate3(calls); - - assertTrue(optimist.isOnAllowList(bob)); - assertEq(optimist.ownerOf(_getTokenId(bob)), bob); - assertEq(optimist.balanceOf(bob), 1); - } -} diff --git a/packages/contracts-bedrock/test/periphery/op-nft/OptimistAllowlist.t.sol b/packages/contracts-bedrock/test/periphery/op-nft/OptimistAllowlist.t.sol deleted file mode 100644 index c0c2aef2bce0c..0000000000000 --- a/packages/contracts-bedrock/test/periphery/op-nft/OptimistAllowlist.t.sol +++ /dev/null @@ -1,227 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Testing utilities -import { Test } from "forge-std/Test.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { OptimistAllowlist } from "src/periphery/op-nft/OptimistAllowlist.sol"; -import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; -import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol"; -import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol"; - -contract OptimistAllowlist_Initializer is Test { - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - address internal alice_allowlistAttestor; - address internal sally_coinbaseQuestAttestor; - address internal ted; - - uint256 internal bobPrivateKey; - address internal bob; - - AttestationStation attestationStation; - OptimistAllowlist optimistAllowlist; - OptimistInviter optimistInviter; - - // Helps with EIP-712 signature generation - OptimistInviterHelper optimistInviterHelper; - - function setUp() public { - alice_allowlistAttestor = makeAddr("alice_allowlistAttestor"); - sally_coinbaseQuestAttestor = makeAddr("sally_coinbaseQuestAttestor"); - ted = makeAddr("ted"); - - bobPrivateKey = 0xB0B0B0B0; - bob = vm.addr(bobPrivateKey); - vm.label(bob, "bob"); - - // Give alice and bob and sally some ETH - vm.deal(alice_allowlistAttestor, 1 ether); - vm.deal(sally_coinbaseQuestAttestor, 1 ether); - vm.deal(bob, 1 ether); - vm.deal(ted, 1 ether); - - _initializeContracts(); - } - - function attestAllowlist(address _about) internal { - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - // we are using true but it can be any non empty value - attestationData[0] = AttestationStation.AttestationData({ - about: _about, - key: optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(), - val: bytes("true") - }); - vm.prank(alice_allowlistAttestor); - attestationStation.attest(attestationData); - } - - function attestCoinbaseQuest(address _about) internal { - AttestationStation.AttestationData[] memory attestationData = new AttestationStation.AttestationData[](1); - // we are using true but it can be any non empty value - attestationData[0] = AttestationStation.AttestationData({ - about: _about, - key: optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(), - val: bytes("true") - }); - vm.prank(sally_coinbaseQuestAttestor); - attestationStation.attest(attestationData); - } - - function inviteAndClaim(address claimer) internal { - address[] memory addresses = new address[](1); - addresses[0] = bob; - - vm.prank(alice_allowlistAttestor); - - // grant invites to Bob; - optimistInviter.setInviteCounts(addresses, 3); - - // issue a new invite - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(bob); - - // EIP-712 sign with Bob's private key - bytes memory signature = _getSignature(bobPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); - - bytes32 hashedCommit = keccak256(abi.encode(claimer, signature)); - - // commit the invite - vm.prank(claimer); - optimistInviter.commitInvite(hashedCommit); - - // wait minimum commitment period - vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); - - // reveal and claim the invite - optimistInviter.claimInvite(claimer, claimableInvite, signature); - } - - /// @notice Get signature as a bytes blob, since SignatureChecker takes arbitrary signature blobs. - function _getSignature(uint256 _signingPrivateKey, bytes32 _digest) internal pure returns (bytes memory) { - (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signingPrivateKey, _digest); - - bytes memory signature = abi.encodePacked(r, s, v); - return signature; - } - - function _initializeContracts() internal { - attestationStation = new AttestationStation(); - - optimistInviter = new OptimistInviter(alice_allowlistAttestor, attestationStation); - optimistInviter.initialize("OptimistInviter"); - - optimistAllowlist = new OptimistAllowlist( - attestationStation, alice_allowlistAttestor, sally_coinbaseQuestAttestor, address(optimistInviter) - ); - - optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter"); - } -} - -contract OptimistAllowlistTest is OptimistAllowlist_Initializer { - function test_constructor_succeeds() external view { - // expect attestationStation to be set - assertEq(address(optimistAllowlist.ATTESTATION_STATION()), address(attestationStation)); - assertEq(optimistAllowlist.ALLOWLIST_ATTESTOR(), alice_allowlistAttestor); - assertEq(optimistAllowlist.COINBASE_QUEST_ATTESTOR(), sally_coinbaseQuestAttestor); - assertEq(address(optimistAllowlist.OPTIMIST_INVITER()), address(optimistInviter)); - } - - /// @notice Base case, a account without any relevant attestations should not be able to mint. - function test_isAllowedToMint_withoutAnyAttestations_fails() external view { - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice After receiving a valid allowlist attestation, the account should be able to mint. - function test_isAllowedToMint_fromAllowlistAttestor_succeeds() external { - attestAllowlist(bob); - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice After receiving a valid attestation from the Coinbase Quest attestor, - /// the account should be able to mint. - function test_isAllowedToMint_fromCoinbaseQuestAttestor_succeeds() external { - attestCoinbaseQuest(bob); - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Account that received an attestation from the OptimistInviter contract by going - /// through the claim invite flow should be able to mint. - function test_isAllowedToMint_fromInvite_succeeds() external { - inviteAndClaim(bob); - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Attestation from the wrong allowlist attestor should not allow minting. - function test_isAllowedToMint_fromWrongAllowlistAttestor_fails() external { - // Ted is not the allowlist attestor - vm.prank(ted); - attestationStation.attest(bob, optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(), bytes("true")); - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Coinbase quest attestation from wrong attestor should not allow minting. - function test_isAllowedToMint_fromWrongCoinbaseQuestAttestor_fails() external { - // Ted is not the coinbase quest attestor - vm.prank(ted); - attestationStation.attest(bob, optimistAllowlist.COINBASE_QUEST_ELIGIBLE_ATTESTATION_KEY(), bytes("true")); - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Claiming an invite on the non-official OptimistInviter contract should not allow - /// minting. - function test_isAllowedToMint_fromWrongOptimistInviter_fails() external { - vm.prank(ted); - attestationStation.attest(bob, OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, bytes("true")); - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Having multiple signals, even if one is invalid, should still allow minting. - function test_isAllowedToMint_withMultipleAttestations_succeeds() external { - attestAllowlist(bob); - attestCoinbaseQuest(bob); - inviteAndClaim(bob); - - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - - // A invalid attestation, as Ted is not allowlist attestor - vm.prank(ted); - attestationStation.attest(bob, optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(), bytes("true")); - - // Since Bob has at least one valid attestation, he should be allowed to mint - assertTrue(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Having falsy attestation value should not allow minting. - function test_isAllowedToMint_fromAllowlistAttestorWithFalsyValue_fails() external { - // First sends correct attestation - attestAllowlist(bob); - - bytes32 key = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); - vm.expectEmit(true, true, true, false); - emit AttestationCreated(alice_allowlistAttestor, bob, key, bytes("dsafsds")); - - // Invalidates existing attestation - vm.prank(alice_allowlistAttestor); - attestationStation.attest(bob, key, bytes("")); - - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } - - /// @notice Having falsy attestation value from Coinbase attestor should not allow minting. - function test_isAllowedToMint_fromCoinbaseQuestAttestorWithFalsyValue_fails() external { - // First sends correct attestation - attestAllowlist(bob); - - bytes32 key = optimistAllowlist.OPTIMIST_CAN_MINT_ATTESTATION_KEY(); - vm.expectEmit(true, true, true, true); - emit AttestationCreated(alice_allowlistAttestor, bob, key, bytes("")); - - // Invalidates existing attestation - vm.prank(alice_allowlistAttestor); - attestationStation.attest(bob, key, bytes("")); - - assertFalse(optimistAllowlist.isAllowedToMint(bob)); - } -} diff --git a/packages/contracts-bedrock/test/periphery/op-nft/OptimistInviter.t.sol b/packages/contracts-bedrock/test/periphery/op-nft/OptimistInviter.t.sol deleted file mode 100644 index 58e71a13a8d77..0000000000000 --- a/packages/contracts-bedrock/test/periphery/op-nft/OptimistInviter.t.sol +++ /dev/null @@ -1,529 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Testing utilities -import { Test } from "forge-std/Test.sol"; -import { AttestationStation } from "src/periphery/op-nft/AttestationStation.sol"; -import { OptimistInviter } from "src/periphery/op-nft/OptimistInviter.sol"; -import { Optimist } from "src/periphery/op-nft/Optimist.sol"; -import { TestERC1271Wallet } from "test/mocks/TestERC1271Wallet.sol"; -import { OptimistInviterHelper } from "test/mocks/OptimistInviterHelper.sol"; -import { OptimistConstants } from "src/periphery/op-nft/libraries/OptimistConstants.sol"; - -contract OptimistInviter_Initializer is Test { - event InviteClaimed(address indexed issuer, address indexed claimer); - event Initialized(uint8 version); - event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); - event AttestationCreated(address indexed creator, address indexed about, bytes32 indexed key, bytes val); - - bytes32 EIP712_DOMAIN_TYPEHASH; - - address internal alice_inviteGranter; - address internal sally; - address internal ted; - address internal eve; - - address internal bob; - uint256 internal bobPrivateKey; - address internal carol; - uint256 internal carolPrivateKey; - - TestERC1271Wallet carolERC1271Wallet; - - AttestationStation attestationStation; - OptimistInviter optimistInviter; - - OptimistInviterHelper optimistInviterHelper; - - function setUp() public { - alice_inviteGranter = makeAddr("alice_inviteGranter"); - sally = makeAddr("sally"); - ted = makeAddr("ted"); - eve = makeAddr("eve"); - - bobPrivateKey = 0xB0B0B0B0; - bob = vm.addr(bobPrivateKey); - - carolPrivateKey = 0xC0C0C0C0; - carol = vm.addr(carolPrivateKey); - - carolERC1271Wallet = new TestERC1271Wallet(carol); - - // Give alice and bob and sally some ETH - vm.deal(alice_inviteGranter, 1 ether); - vm.deal(bob, 1 ether); - vm.deal(sally, 1 ether); - vm.deal(ted, 1 ether); - vm.deal(eve, 1 ether); - - EIP712_DOMAIN_TYPEHASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - _initializeContracts(); - } - - /// @notice Instantiates an AttestationStation, and an OptimistInviter. - function _initializeContracts() internal { - attestationStation = new AttestationStation(); - - optimistInviter = new OptimistInviter(alice_inviteGranter, attestationStation); - - vm.expectEmit(true, true, true, true, address(optimistInviter)); - emit Initialized(1); - optimistInviter.initialize("OptimistInviter"); - - optimistInviterHelper = new OptimistInviterHelper(optimistInviter, "OptimistInviter"); - } - - function _passMinCommitmentPeriod() internal { - vm.warp(optimistInviter.MIN_COMMITMENT_PERIOD() + block.timestamp); - } - - /// @notice Returns a user's current invite count, as stored in the AttestationStation. - function _getInviteCount(address _issuer) internal view returns (uint256) { - return optimistInviter.inviteCounts(_issuer); - } - - /// @notice Returns true if claimer has the proper attestation from OptimistInviter to mint. - function _hasMintAttestation(address _claimer) internal view returns (bool) { - bytes memory attestation = attestationStation.attestations( - address(optimistInviter), _claimer, OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY - ); - return attestation.length > 0; - } - - /// @notice Get signature as a bytes blob, since SignatureChecker takes arbitrary signature blobs. - function _getSignature(uint256 _signingPrivateKey, bytes32 _digest) internal pure returns (bytes memory) { - (uint8 v, bytes32 r, bytes32 s) = vm.sign(_signingPrivateKey, _digest); - - bytes memory signature = abi.encodePacked(r, s, v); - return signature; - } - - /// @notice Signs a claimable invite with the given private key and returns the signature using - /// correct EIP712 domain separator. - function _issueInviteAs(uint256 _privateKey) - internal - returns (OptimistInviter.ClaimableInvite memory, bytes memory) - { - return _issueInviteWithEIP712Domain( - _privateKey, - bytes("OptimistInviter"), - bytes(optimistInviter.EIP712_VERSION()), - block.chainid, - address(optimistInviter) - ); - } - - /// @notice Signs a claimable invite with the given private key and returns the signature using - /// the given EIP712 domain separator. This assumes that the issuer's address is the - /// corresponding public key to _issuerPrivateKey. - function _issueInviteWithEIP712Domain( - uint256 _issuerPrivateKey, - bytes memory _eip712Name, - bytes memory _eip712Version, - uint256 _eip712Chainid, - address _eip712VerifyingContract - ) - internal - returns (OptimistInviter.ClaimableInvite memory, bytes memory) - { - address issuer = vm.addr(_issuerPrivateKey); - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(issuer); - return ( - claimableInvite, - _getSignature( - _issuerPrivateKey, - optimistInviterHelper.getDigestWithEIP712Domain( - claimableInvite, _eip712Name, _eip712Version, _eip712Chainid, _eip712VerifyingContract - ) - ) - ); - } - - /// @notice Commits a signature and claimer address to the OptimistInviter contract. - function _commitInviteAs(address _as, bytes memory _signature) internal { - vm.prank(_as); - bytes32 hashedSignature = keccak256(abi.encode(_as, _signature)); - optimistInviter.commitInvite(hashedSignature); - - // Check that the commitment was stored correctly - assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); - } - - /// @notice Signs a claimable invite with the given private key. The claimer commits then claims - /// the invite. Checks that all expected events are emitted and that state is updated - /// correctly. Returns the signature and invite for use in tests. - function _issueThenClaimShouldSucceed( - uint256 _issuerPrivateKey, - address _claimer - ) - internal - returns (OptimistInviter.ClaimableInvite memory, bytes memory) - { - address issuer = vm.addr(_issuerPrivateKey); - uint256 prevInviteCount = _getInviteCount(issuer); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = - _issueInviteAs(_issuerPrivateKey); - - _commitInviteAs(_claimer, signature); - - // The hash(claimer ++ signature) should be committed - assertEq(optimistInviter.commitmentTimestamps(keccak256(abi.encode(_claimer, signature))), block.timestamp); - - _passMinCommitmentPeriod(); - - // OptimistInviter should issue a new attestation allowing claimer to mint - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), - _claimer, - OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, - abi.encode(issuer) - ); - - // Should emit an event indicating that the invite was claimed - vm.expectEmit(true, false, false, false, address(optimistInviter)); - emit InviteClaimed(issuer, _claimer); - - vm.prank(_claimer); - optimistInviter.claimInvite(_claimer, claimableInvite, signature); - - // The nonce that issuer used should be marked as used - assertTrue(optimistInviter.usedNonces(issuer, claimableInvite.nonce)); - - // Issuer should have one less invite - assertEq(prevInviteCount - 1, _getInviteCount(issuer)); - - // Claimer should have the mint attestation from the OptimistInviter contract - assertTrue(_hasMintAttestation(_claimer)); - - return (claimableInvite, signature); - } - - /// @notice Issues 3 invites to the given address. Checks that all expected events are emitted - /// and that state is updated correctly. - function _grantInvitesTo(address _to) internal { - address[] memory addresses = new address[](1); - addresses[0] = _to; - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), _to, optimistInviter.CAN_INVITE_ATTESTATION_KEY(), bytes("true") - ); - - vm.prank(alice_inviteGranter); - optimistInviter.setInviteCounts(addresses, 3); - - assertEq(_getInviteCount(_to), 3); - } -} - -contract OptimistInviterTest is OptimistInviter_Initializer { - function test_initialize_succeeds() external view { - // expect attestationStation to be set - assertEq(address(optimistInviter.ATTESTATION_STATION()), address(attestationStation)); - assertEq(optimistInviter.INVITE_GRANTER(), alice_inviteGranter); - } - - /// @notice Alice the admin should be able to give Bob, Sally, and Carol 3 invites, and the - /// OptimistInviter contract should increment invite counts on inviteCounts and issue - /// 'optimist.can-invite' attestations. - function test_grantInvites_adminAddingInvites_succeeds() external { - address[] memory addresses = new address[](3); - addresses[0] = bob; - addresses[1] = sally; - addresses[2] = address(carolERC1271Wallet); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), bob, optimistInviter.CAN_INVITE_ATTESTATION_KEY(), bytes("true") - ); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), sally, optimistInviter.CAN_INVITE_ATTESTATION_KEY(), bytes("true") - ); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), - address(carolERC1271Wallet), - optimistInviter.CAN_INVITE_ATTESTATION_KEY(), - bytes("true") - ); - - vm.prank(alice_inviteGranter); - optimistInviter.setInviteCounts(addresses, 3); - - assertEq(_getInviteCount(bob), 3); - assertEq(_getInviteCount(sally), 3); - assertEq(_getInviteCount(address(carolERC1271Wallet)), 3); - } - - /// @notice Bob, who is not the invite granter, should not be able to issue invites. - function test_grantInvites_nonAdminAddingInvites_reverts() external { - address[] memory addresses = new address[](2); - addresses[0] = bob; - addresses[1] = sally; - - vm.expectRevert("OptimistInviter: only invite granter can grant invites"); - vm.prank(bob); - optimistInviter.setInviteCounts(addresses, 3); - } - - /// @notice Sally should be able to commit an invite given by by Bob. - function test_commitInvite_committingForYourself_succeeds() external { - _grantInvitesTo(bob); - (, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.prank(sally); - bytes32 hashedSignature = keccak256(abi.encode(sally, signature)); - optimistInviter.commitInvite(hashedSignature); - - assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); - } - - /// @notice Sally should be able to Bob's for a different claimer, Eve. - function test_commitInvite_committingForSomeoneElse_succeeds() external { - _grantInvitesTo(bob); - (, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.prank(sally); - bytes32 hashedSignature = keccak256(abi.encode(eve, signature)); - optimistInviter.commitInvite(hashedSignature); - - assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); - } - - /// @notice Attempting to commit the same hash twice should revert. This prevents griefing. - function test_commitInvite_committingSameHashTwice_reverts() external { - _grantInvitesTo(bob); - (, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.prank(sally); - bytes32 hashedSignature = keccak256(abi.encode(eve, signature)); - optimistInviter.commitInvite(hashedSignature); - - assertEq(optimistInviter.commitmentTimestamps(hashedSignature), block.timestamp); - - vm.expectRevert("OptimistInviter: commitment already made"); - optimistInviter.commitInvite(hashedSignature); - } - - /// @notice Bob issues signature, and Sally claims the invite. Bob's invite count should be - /// decremented, and Sally should be able to mint. - function test_claimInvite_succeeds() external { - _grantInvitesTo(bob); - _issueThenClaimShouldSucceed(bobPrivateKey, sally); - } - - /// @notice Bob issues signature, and Ted commits the invite for Sally. Eve claims for Sally. - function test_claimInvite_claimForSomeoneElse_succeeds() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.prank(ted); - optimistInviter.commitInvite(keccak256(abi.encode(sally, signature))); - _passMinCommitmentPeriod(); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), - sally, - OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, - abi.encode(bob) - ); - - // Should emit an event indicating that the invite was claimed - vm.expectEmit(true, true, true, true, address(optimistInviter)); - emit InviteClaimed(bob, sally); - - vm.prank(eve); - optimistInviter.claimInvite(sally, claimableInvite, signature); - - assertEq(_getInviteCount(bob), 2); - assertTrue(_hasMintAttestation(sally)); - assertFalse(_hasMintAttestation(eve)); - } - - function test_claimInvite_claimBeforeMinCommitmentPeriod_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - _commitInviteAs(sally, signature); - - // Some time passes, but not enough to meet the minimum commitment period - vm.warp(block.timestamp + 10); - - vm.expectRevert("OptimistInviter: minimum commitment period has not elapsed yet"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Signature issued for previous versions of the contract should fail. - function test_claimInvite_usingSignatureIssuedForDifferentVersion_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteWithEIP712Domain( - bobPrivateKey, "OptimismInviter", "0.9.1", block.chainid, address(optimistInviter) - ); - - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: invalid signature"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Replay attack for signature issued for contract on different chain (ie. mainnet) - /// should fail. - function test_claimInvite_usingSignatureIssuedForDifferentChain_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteWithEIP712Domain( - bobPrivateKey, "OptimismInviter", bytes(optimistInviter.EIP712_VERSION()), 1, address(optimistInviter) - ); - - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: invalid signature"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Replay attack for signature issued for instantiation of the OptimistInviter contract - /// on a different address should fail. - function test_claimInvite_usingSignatureIssuedForDifferentContract_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteWithEIP712Domain( - bobPrivateKey, "OptimismInviter", bytes(optimistInviter.EIP712_VERSION()), block.chainid, address(0xBEEF) - ); - - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: invalid signature"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Attempting to claim again using the same signature again should fail. - function test_claimInvite_replayingUsedNonce_reverts() external { - _grantInvitesTo(bob); - - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = - _issueThenClaimShouldSucceed(bobPrivateKey, sally); - - // Sally tries to claim the invite using the same signature - vm.expectRevert("OptimistInviter: nonce has already been used"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - - // Carol tries to claim the invite using the same signature - _commitInviteAs(carol, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: nonce has already been used"); - vm.prank(carol); - optimistInviter.claimInvite(carol, claimableInvite, signature); - } - - /// @notice Issuing signatures through a contract that implements ERC1271 should succeed (ie. - /// Gnosis Safe or other smart contract wallets). Carol is using a ERC1271 contract - /// wallet that is simply backed by her private key. - function test_claimInvite_usingERC1271Wallet_succeeds() external { - _grantInvitesTo(address(carolERC1271Wallet)); - - OptimistInviter.ClaimableInvite memory claimableInvite = - optimistInviterHelper.getClaimableInviteWithNewNonce(address(carolERC1271Wallet)); - - bytes memory signature = _getSignature(carolPrivateKey, optimistInviterHelper.getDigest(claimableInvite)); - - // Sally tries to claim the invite - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectEmit(true, true, true, true, address(attestationStation)); - emit AttestationCreated( - address(optimistInviter), - sally, - OptimistConstants.OPTIMIST_CAN_MINT_FROM_INVITE_ATTESTATION_KEY, - abi.encode(address(carolERC1271Wallet)) - ); - - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - assertEq(_getInviteCount(address(carolERC1271Wallet)), 2); - } - - /// @notice Claimer must commit the signature before claiming the invite. Sally attempts to - /// claim the Bob's invite without committing the signature first. - function test_claimInvite_withoutCommittingHash_reverts() external { - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - vm.expectRevert("OptimistInviter: claimer and signature have not been committed yet"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Using a signature that doesn't correspond to the claimable invite should fail. - function test_claimInvite_withIncorrectSignature_reverts() external { - _grantInvitesTo(carol); - _grantInvitesTo(bob); - (OptimistInviter.ClaimableInvite memory bobClaimableInvite, bytes memory bobSignature) = - _issueInviteAs(bobPrivateKey); - (, bytes memory carolSignature) = _issueInviteAs(carolPrivateKey); - - _commitInviteAs(sally, bobSignature); - _commitInviteAs(sally, carolSignature); - - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: invalid signature"); - vm.prank(sally); - optimistInviter.claimInvite(sally, bobClaimableInvite, carolSignature); - } - - /// @notice Attempting to use a signature from a issuer who never was granted invites should - /// fail. - function test_claimInvite_whenIssuerNeverReceivedInvites_reverts() external { - // Bob was never granted any invites, but issues an invite for Eve - (OptimistInviter.ClaimableInvite memory claimableInvite, bytes memory signature) = _issueInviteAs(bobPrivateKey); - - _commitInviteAs(sally, signature); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: issuer has no invites"); - vm.prank(sally); - optimistInviter.claimInvite(sally, claimableInvite, signature); - } - - /// @notice Attempting to use a signature from a issuer who has no more invites should fail. - /// Bob has 3 invites, but issues 4 invites for Sally, Carol, Ted, and Eve. Only the - /// first 3 invites should be claimable. The last claimer, Eve, should not be able to - /// claim the invite. - function test_claimInvite_whenIssuerHasNoInvitesLeft_reverts() external { - _grantInvitesTo(bob); - - _issueThenClaimShouldSucceed(bobPrivateKey, sally); - _issueThenClaimShouldSucceed(bobPrivateKey, carol); - _issueThenClaimShouldSucceed(bobPrivateKey, ted); - - assertEq(_getInviteCount(bob), 0); - - (OptimistInviter.ClaimableInvite memory claimableInvite4, bytes memory signature4) = - _issueInviteAs(bobPrivateKey); - - _commitInviteAs(eve, signature4); - _passMinCommitmentPeriod(); - - vm.expectRevert("OptimistInviter: issuer has no invites"); - vm.prank(eve); - optimistInviter.claimInvite(eve, claimableInvite4, signature4); - - assertEq(_getInviteCount(bob), 0); - } -}