├── src ├── vite-env.d.ts ├── constants │ ├── L2OutputOracleAddress.ts │ ├── L2TestCoinAbi.ts │ ├── L2OutputOracleAbi.ts │ └── L2TestNFTAbi.ts ├── App.css ├── components │ ├── NetworkConnectedTag.tsx │ ├── L1Panel.tsx │ ├── L1NFTPassportViewer.tsx │ ├── TxBlockExplorerLink.tsx │ ├── SwitchNetworkButton.tsx │ ├── ChainPanel.tsx │ ├── GitHubLink.tsx │ ├── L2Panel.tsx │ ├── DirectionsBanner.tsx │ ├── LatestL2OutputOracleDisplay.tsx │ ├── L2TransactionList.tsx │ ├── L2TestCoinCard.tsx │ ├── L2TestNFTCard.tsx │ └── L1NFTPassportCard.tsx ├── main.tsx ├── lib │ └── useLatestAvailableL2BlockNumberOnL1.ts ├── wagmi.ts └── App.tsx ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── contracts ├── lib │ └── forge-std │ │ ├── .gitmodules │ │ ├── lib │ │ └── ds-test │ │ │ ├── default.nix │ │ │ ├── Makefile │ │ │ ├── package.json │ │ │ └── demo │ │ │ └── demo.sol │ │ ├── test │ │ ├── compilation │ │ │ ├── CompilationTest.sol │ │ │ ├── CompilationScript.sol │ │ │ ├── CompilationTestBase.sol │ │ │ └── CompilationScriptBase.sol │ │ ├── StdError.t.sol │ │ ├── StdChains.t.sol │ │ ├── StdUtils.t.sol │ │ └── StdMath.t.sol │ │ ├── package.json │ │ ├── src │ │ ├── interfaces │ │ │ ├── IERC165.sol │ │ │ ├── IERC20.sol │ │ │ ├── IERC1155.sol │ │ │ └── IERC721.sol │ │ ├── Script.sol │ │ ├── StdError.sol │ │ ├── Test.sol │ │ ├── StdMath.sol │ │ ├── Base.sol │ │ ├── InvariantTest.sol │ │ ├── StdUtils.sol │ │ ├── StdJson.sol │ │ └── StdChains.sol │ │ ├── foundry.toml │ │ ├── LICENSE-MIT │ │ ├── .github │ │ └── workflows │ │ │ └── ci.yml │ │ └── README.md ├── script │ └── Counter.s.sol ├── src │ ├── evm-verifier │ │ ├── IEVMVerifier.sol │ │ ├── EVMFetchTarget.sol │ │ ├── SecureMerkleTrie.sol │ │ ├── EVMProofHelper.sol │ │ └── EVMFetcher.sol │ ├── Counter.sol │ ├── L2TestCoin.sol │ ├── L2TestNFT.sol │ ├── op-verifier │ │ └── OPVerifier.sol │ └── L1PassportNFT.sol ├── test │ └── Counter.t.sol └── README.md ├── polyfills.ts ├── .gitmodules ├── remappings.txt ├── index.html ├── .vscode ├── extensions.json └── settings.json ├── foundry.toml ├── tsconfig.json ├── vite.config.ts ├── .gitignore ├── .env.example ├── package.json └── README.md /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies = false 2 | legacy-peer-deps = true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | contracts/out 4 | contracts/cache 5 | cache 6 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /src/constants/L2OutputOracleAddress.ts: -------------------------------------------------------------------------------- 1 | export const L2OutputOracleAddress = 2 | "0xE6Dfba0953616Bacab0c9A8ecb3a9BBa77FC15c0"; 3 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/lib/ds-test/default.nix: -------------------------------------------------------------------------------- 1 | { solidityPackage, dappsys }: solidityPackage { 2 | name = "ds-test"; 3 | src = ./src; 4 | } 5 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | margin: 0; 5 | } 6 | 7 | #root { 8 | height: 100%; 9 | } 10 | 11 | #root > div { 12 | height: 100%; 13 | } 14 | -------------------------------------------------------------------------------- /polyfills.ts: -------------------------------------------------------------------------------- 1 | import { Buffer } from "buffer"; 2 | import process from "process"; 3 | 4 | /** 5 | * Polyfills necessary modules to use ethers.js 6 | */ 7 | window.global = window; 8 | window.process = process; 9 | window.Buffer = Buffer; 10 | -------------------------------------------------------------------------------- /contracts/script/Counter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | contract CounterScript is Script { 7 | function setUp() public {} 8 | 9 | function run() public { 10 | vm.broadcast(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "contracts/lib/forge-std"] 2 | path = contracts/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/forge-std"] 5 | branch = v1.2.0 6 | 7 | [submodule "contracts/lib/solmate"] 8 | path = contracts/lib/solmate 9 | url = https://github.com/transmissions11/solmate 10 | -------------------------------------------------------------------------------- /contracts/src/evm-verifier/IEVMVerifier.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface IEVMVerifier { 5 | function gatewayURLs() external view returns(string[] memory); 6 | function getStorageValues(address target, bytes32[] memory commands, bytes[] memory constants, bytes memory proof) external view returns(bytes[] memory values); 7 | } 8 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | @eth-optimism/contracts-bedrock/=node_modules/@eth-optimism/contracts-bedrock/ 2 | @openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ 3 | @openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/ 4 | ds-test/=contracts/lib/forge-std/lib/ds-test/src/ 5 | forge-std/=contracts/lib/forge-std/src/ 6 | solmate/=contracts/lib/solmate/src/ -------------------------------------------------------------------------------- /contracts/lib/forge-std/lib/ds-test/Makefile: -------------------------------------------------------------------------------- 1 | all:; dapp build 2 | 3 | test: 4 | -dapp --use solc:0.4.23 build 5 | -dapp --use solc:0.4.26 build 6 | -dapp --use solc:0.5.17 build 7 | -dapp --use solc:0.6.12 build 8 | -dapp --use solc:0.7.5 build 9 | 10 | demo: 11 | DAPP_SRC=demo dapp --use solc:0.7.5 build 12 | -hevm dapp-test --verbose 3 13 | 14 | .PHONY: test demo 15 | -------------------------------------------------------------------------------- /contracts/src/Counter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract Counter { 5 | uint256 public number = 0; 6 | 7 | event Transfer(address indexed from, address indexed to, uint256 value); 8 | 9 | function setNumber(uint256 newNumber) public { 10 | number = newNumber; 11 | } 12 | 13 | function increment() public { 14 | number++; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EVMGateway OP-Goerli Passport 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/lib/ds-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ds-test", 3 | "version": "1.0.0", 4 | "description": "Assertions, equality checks and other test helpers ", 5 | "bugs": "https://github.com/dapphub/ds-test/issues", 6 | "license": "GPL-3.0", 7 | "author": "Contributors to ds-test", 8 | "files": [ 9 | "src/*" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/dapphub/ds-test.git" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/NetworkConnectedTag.tsx: -------------------------------------------------------------------------------- 1 | import { Tag } from "@ensdomains/thorin"; 2 | import { useNetwork } from "wagmi"; 3 | 4 | export const NetworkConnectedTag = ({ chainId }: { chainId: number }) => { 5 | const { chain } = useNetwork(); 6 | 7 | if (!chain || chain.id !== chainId) { 8 | return null; 9 | } 10 | 11 | return ( 12 | 13 | Network connected to wallet 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "esbenp.prettier-vscode", 8 | "juanblanco.solidity", 9 | "antfu.vite", 10 | "naps62.foundry-vscode-test-adapter" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/test/compilation/CompilationTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Test.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationTest is Test {} 11 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/test/compilation/CompilationScript.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Script.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationScript is Script {} 11 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/test/compilation/CompilationTestBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Test.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationTestBase is TestBase {} 11 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/test/compilation/CompilationScriptBase.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import "../../src/Script.sol"; 7 | 8 | // The purpose of this contract is to benchmark compilation time to avoid accidentally introducing 9 | // a change that results in very long compilation times with via-ir. See https://github.com/foundry-rs/forge-std/issues/207 10 | contract CompilationScriptBase is ScriptBase {} 11 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts/src' 3 | test = 'contracts/test' 4 | out = 'contracts/out' 5 | libs = ['contracts/lib'] 6 | solc = '0.8.17' 7 | 8 | remappings = [ 9 | '@eth-optimism/contracts-bedrock/=node_modules/@eth-optimism/contracts-bedrock', 10 | '@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/', 11 | '@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/', 12 | ] 13 | 14 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": false, 6 | "forceConsistentCasingInFileNames": true, 7 | "isolatedModules": true, 8 | "jsx": "react-jsx", 9 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 10 | "module": "ESNext", 11 | "moduleResolution": "Node", 12 | "noEmit": true, 13 | "resolveJsonModule": true, 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "target": "ESNext", 17 | "useDefineForClassFields": true 18 | }, 19 | "include": ["./src"] 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm.packageManager": "npm", 3 | "editor.formatOnSaveMode": "file", 4 | "editor.tabCompletion": "on", 5 | "editor.tabSize": 2, 6 | "editor.formatOnSave": true, 7 | "editor.inlineSuggest.enabled": true, 8 | "editor.codeActionsOnSave": { 9 | "source.fixAll": true 10 | }, 11 | "files.eol": "\n", 12 | "[typescript]": { 13 | "editor.defaultFormatter": "esbenp.prettier-vscode" 14 | }, 15 | "files.trimTrailingWhitespace": true, 16 | "solidity.compileUsingRemoteVersion": "v0.8.17+commit.8df45f5f", 17 | "prettier.configPath": ".prettierrc.json" 18 | } 19 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forge-std", 3 | "version": "1.2.0", 4 | "description": "Forge Standard Library is a collection of helpful contracts and libraries for use with Forge and Foundry.", 5 | "homepage": "https://book.getfoundry.sh/forge/forge-std", 6 | "bugs": "https://github.com/foundry-rs/forge-std/issues", 7 | "license": "(Apache-2.0 OR MIT)", 8 | "author": "Contributors to Forge Standard Library", 9 | "files": [ 10 | "src/*" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/foundry-rs/forge-std.git" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/interfaces/IERC165.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2; 3 | 4 | interface IERC165 { 5 | /// @notice Query if a contract implements an interface 6 | /// @param interfaceID The interface identifier, as specified in ERC-165 7 | /// @dev Interface identification is specified in ERC-165. This function 8 | /// uses less than 30,000 gas. 9 | /// @return `true` if the contract implements `interfaceID` and 10 | /// `interfaceID` is not 0xffffffff, `false` otherwise 11 | function supportsInterface(bytes4 interfaceID) external view returns (bool); 12 | } 13 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from "@vitejs/plugin-react"; 2 | import { defineConfig } from "vite"; 3 | 4 | /** 5 | * @see https://vitejs.dev/config/ 6 | */ 7 | export default defineConfig({ 8 | resolve: { 9 | /** 10 | * Polyfills nodejs imports 11 | * @see https://vitejs.dev/config/shared-options.html#resolve-alias 12 | */ 13 | alias: { 14 | process: "process/browser", 15 | util: "util", 16 | }, 17 | }, 18 | /** 19 | * Enables react 20 | * @see https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md 21 | */ 22 | plugins: [react()], 23 | }); 24 | -------------------------------------------------------------------------------- /contracts/test/Counter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/Counter.sol"; 6 | 7 | contract CounterTest is Test { 8 | Counter public counter; 9 | 10 | function setUp() public { 11 | counter = new Counter(); 12 | counter.setNumber(0); 13 | } 14 | 15 | function testIncrement() public { 16 | counter.increment(); 17 | assertEq(counter.number(), 1); 18 | } 19 | 20 | function testSetNumber(uint256 x) public { 21 | counter.setNumber(x); 22 | assertEq(counter.number(), x); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/L1Panel.tsx: -------------------------------------------------------------------------------- 1 | import { goerli } from "wagmi/chains"; 2 | import { ChainPanel } from "./ChainPanel"; 3 | import { LatestL2OutputOracleDisplay } from "./LatestL2OutputOracleDisplay"; 4 | import { L1NFTPassportCard } from "./L1NFTPassportCard"; 5 | 6 | export const L1Panel = () => { 7 | return ( 8 | 16 | 17 |
18 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | 9 | # testing 10 | 11 | /coverage 12 | 13 | # vite 14 | 15 | dist 16 | dist-ssr 17 | 18 | # production 19 | 20 | /build 21 | 22 | # misc 23 | 24 | .DS_Store 25 | \*.pem 26 | *.local 27 | 28 | # debug 29 | 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | .pnpm-debug.log* 34 | 35 | # local env files 36 | 37 | .env\*.local 38 | .env 39 | 40 | # vercel 41 | 42 | .vercel 43 | 44 | # typescript 45 | 46 | \*.tsbuildinfo 47 | next-env.d.ts 48 | 49 | # forge 50 | 51 | out 52 | cache 53 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # contracts 2 | 3 | Add contracts and scripts here! 4 | 5 | ## src/L1PassportNFT.sol 6 | 7 | Non-transferrable ERC721 NFT contract that uses CCIP-Read. Should be deployed on the L1. 8 | 9 | ## src/L2TestCoin.sol 10 | 11 | Simple ERC20 contract based on `solmate` meant to be deployed on the L2 12 | 13 | ## src/L2TestNFT.sol 14 | 15 | Simple ERC721 contract based on `solmate` meant to be deployed on the L2 16 | 17 | ## src/evm-verifier 18 | 19 | Copied from [@ensdomains/evmgateway](https://github.com/ensdomains/evmgateway/tree/main/evm-verifier/contracts) 20 | 21 | ## src/op-verifier 22 | 23 | Copied from [@ensdomains/evmgateway](https://github.com/ensdomains/evmgateway/tree/main/op-verifier/contracts) 24 | -------------------------------------------------------------------------------- /src/components/L1NFTPassportViewer.tsx: -------------------------------------------------------------------------------- 1 | const stripPrefix = (prefix: string, str: string) => { 2 | if (str.startsWith(prefix)) { 3 | return str.substring(prefix.length); 4 | } 5 | return str; 6 | }; 7 | 8 | const base64ToJson = (base64String: string) => { 9 | return JSON.parse( 10 | atob(stripPrefix("data:application/json;base64,", base64String)), 11 | ); 12 | }; 13 | 14 | const getImgSrcFromTokenUri = (tokenUri: string) => { 15 | const json = base64ToJson(tokenUri); 16 | return json.image; 17 | }; 18 | 19 | export const L1NFTPassportViewer = ({ tokenUri }: { tokenUri: string }) => { 20 | const x = base64ToJson(tokenUri); 21 | return ; 22 | }; 23 | -------------------------------------------------------------------------------- /contracts/src/L2TestCoin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { ERC20 } from "solmate/tokens/ERC20.sol"; 5 | import { Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 6 | 7 | contract L2TestCoin is ERC20 { 8 | constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20(_name, _symbol, _decimals) { 9 | } 10 | 11 | function mintTo(address _to, uint256 _amount) public { 12 | _mint(_to, _amount); 13 | } 14 | 15 | function getTotalSupplyStorageSlot() pure public returns (uint256 slot) { 16 | assembly { 17 | slot := totalSupply.slot 18 | } 19 | } 20 | 21 | function getBalanceOfStorageSlot() pure public returns (uint256 slot) { 22 | assembly { 23 | slot := balanceOf.slot 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import "@rainbow-me/rainbowkit/styles.css"; 2 | import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; 3 | import * as React from "react"; 4 | import * as ReactDOM from "react-dom/client"; 5 | import { WagmiConfig } from "wagmi"; 6 | 7 | import { App } from "./App"; 8 | import { chains, config } from "./wagmi"; 9 | 10 | /** 11 | * Root providers and initialization of app 12 | * @see https://reactjs.org/docs/strict-mode.html 13 | * @see https://wagmi.sh/react/WagmiConfig 14 | * @see https://www.rainbowkit.com/docs/installation 15 | */ 16 | ReactDOM.createRoot(document.getElementById("root")!).render( 17 | 18 | 19 | 20 | 21 | 22 | 23 | , 24 | ); 25 | -------------------------------------------------------------------------------- /src/components/TxBlockExplorerLink.tsx: -------------------------------------------------------------------------------- 1 | import { Typography, UpRightArrowSVG } from "@ensdomains/thorin"; 2 | import { Hex } from "viem"; 3 | import { Chain } from "wagmi"; 4 | 5 | const truncateHash = (hash: Hex) => { 6 | return `${hash.slice(0, 6)}...${hash.slice(-6)}`; 7 | }; 8 | 9 | export const TxBlockExplorerLink = ({ 10 | hash, 11 | chain, 12 | }: { 13 | hash: Hex; 14 | chain: Chain; 15 | }) => { 16 | return ( 17 | 27 | 28 | {truncateHash(hash)} 29 | 30 | 31 | 32 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/components/SwitchNetworkButton.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@ensdomains/thorin"; 2 | import { Chain, useSwitchNetwork } from "wagmi"; 3 | 4 | export const SwitchNetworkButton = ({ chain }: { chain: Chain }) => { 5 | const chainName = chain.name; 6 | const chainId = chain.id; 7 | 8 | const { switchNetwork, isLoading } = useSwitchNetwork(); 9 | if (!isLoading && !switchNetwork) { 10 | return
Please switch the network to {chainName} on your wallet
; 11 | } 12 | return ( 13 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /contracts/src/L2TestNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { ERC721 } from "solmate/tokens/ERC721.sol"; 5 | import { Strings} from "@openzeppelin/contracts/utils/Strings.sol"; 6 | 7 | 8 | contract L2TestNFT is ERC721 { 9 | 10 | uint256 public currentTokenId; 11 | 12 | constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) { 13 | } 14 | 15 | function mintTo(address _to) public returns (uint256) { 16 | uint256 newTokenId = currentTokenId++; 17 | _safeMint(_to, newTokenId); 18 | return newTokenId; 19 | } 20 | 21 | function tokenURI(uint256 id) public pure override returns (string memory) { 22 | return Strings.toString(id); 23 | } 24 | 25 | function getBalanceOfStorageSlot() pure public returns (uint256 slot) { 26 | assembly { 27 | slot := _balanceOf.slot 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | fs_permissions = [{ access = "read-write", path = "./"}] 3 | 4 | [rpc_endpoints] 5 | # The RPC URLs are modified versions of the default for testing initialization. 6 | mainnet = "https://mainnet.infura.io/v3/7a8769b798b642f6933f2ed52042bd70" # Different API key. 7 | optimism_goerli = "https://goerli.optimism.io/" # Adds a trailing slash. 8 | arbitrum_one_goerli = "https://goerli-rollup.arbitrum.io/rpc/" # Adds a trailing slash. 9 | needs_undefined_env_var = "${UNDEFINED_RPC_URL_PLACEHOLDER}" 10 | 11 | [fmt] 12 | # These are all the `forge fmt` defaults. 13 | line_length = 120 14 | tab_width = 4 15 | bracket_spacing = false 16 | int_types = 'long' 17 | multiline_func_header = 'attributes_first' 18 | quote_style = 'double' 19 | number_underscore = 'preserve' 20 | single_line_statement_blocks = 'preserve' 21 | ignore = ["src/console.sol", "src/console2.sol"] -------------------------------------------------------------------------------- /src/components/ChainPanel.tsx: -------------------------------------------------------------------------------- 1 | import { Heading } from "@ensdomains/thorin"; 2 | import { Chain } from "wagmi"; 3 | import { NetworkConnectedTag } from "./NetworkConnectedTag"; 4 | 5 | export const ChainPanel = ({ 6 | chain, 7 | children, 8 | style = {}, 9 | }: { 10 | chain: Chain; 11 | children: React.ReactNode; 12 | style?: React.CSSProperties; 13 | }) => { 14 | return ( 15 |
23 |
30 | {chain.name} 31 | 32 |
33 | {children} 34 |
35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/Script.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | // 💬 ABOUT 5 | // Standard Library's default Script. 6 | 7 | // 🧩 MODULES 8 | import {ScriptBase} from "./Base.sol"; 9 | import {console} from "./console.sol"; 10 | import {console2} from "./console2.sol"; 11 | import {StdChains} from "./StdChains.sol"; 12 | import {StdCheatsSafe} from "./StdCheats.sol"; 13 | import {stdJson} from "./StdJson.sol"; 14 | import {stdMath} from "./StdMath.sol"; 15 | import {StdStorage, stdStorageSafe} from "./StdStorage.sol"; 16 | import {StdUtils} from "./StdUtils.sol"; 17 | import {VmSafe} from "./Vm.sol"; 18 | 19 | // 📦 BOILERPLATE 20 | import {ScriptBase} from "./Base.sol"; 21 | 22 | // ⭐️ SCRIPT 23 | abstract contract Script is StdChains, StdCheatsSafe, StdUtils, ScriptBase { 24 | // Note: IS_SCRIPT() must return true. 25 | bool public IS_SCRIPT = true; 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/useLatestAvailableL2BlockNumberOnL1.ts: -------------------------------------------------------------------------------- 1 | import { useContractEvent, useContractRead } from "wagmi"; 2 | import { L2OutputOracleAbi } from "../constants/L2OutputOracleAbi"; 3 | import { goerli } from "wagmi/chains"; 4 | import { L2OutputOracleAddress } from "../constants/L2OutputOracleAddress"; 5 | 6 | export const useLatestAvailableL2BlockNumberOnL1 = ( 7 | onUpdate?: (latestL1Timestamp?: bigint) => void, 8 | ) => { 9 | const queryResult = useContractRead({ 10 | abi: L2OutputOracleAbi, 11 | address: L2OutputOracleAddress, 12 | functionName: "latestBlockNumber", 13 | chainId: goerli.id, 14 | }); 15 | 16 | const { refetch } = queryResult; 17 | 18 | useContractEvent({ 19 | abi: L2OutputOracleAbi, 20 | address: L2OutputOracleAddress, 21 | eventName: "OutputProposed", 22 | chainId: goerli.id, 23 | listener: (events) => { 24 | onUpdate?.(events[0].args.l1Timestamp); 25 | // refetch the L2OutputOracle data when a new output is proposed 26 | refetch(); 27 | }, 28 | }); 29 | 30 | return queryResult; 31 | }; 32 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/StdError.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Panics work for versions >=0.8.0, but we lowered the pragma to make this compatible with Test 3 | pragma solidity >=0.6.2 <0.9.0; 4 | 5 | library stdError { 6 | bytes public constant assertionError = abi.encodeWithSignature("Panic(uint256)", 0x01); 7 | bytes public constant arithmeticError = abi.encodeWithSignature("Panic(uint256)", 0x11); 8 | bytes public constant divisionError = abi.encodeWithSignature("Panic(uint256)", 0x12); 9 | bytes public constant enumConversionError = abi.encodeWithSignature("Panic(uint256)", 0x21); 10 | bytes public constant encodeStorageError = abi.encodeWithSignature("Panic(uint256)", 0x22); 11 | bytes public constant popError = abi.encodeWithSignature("Panic(uint256)", 0x31); 12 | bytes public constant indexOOBError = abi.encodeWithSignature("Panic(uint256)", 0x32); 13 | bytes public constant memOverflowError = abi.encodeWithSignature("Panic(uint256)", 0x41); 14 | bytes public constant zeroVarError = abi.encodeWithSignature("Panic(uint256)", 0x51); 15 | } 16 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/Test.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | // 💬 ABOUT 5 | // Standard Library's default Test 6 | 7 | // 🧩 MODULES 8 | import {console} from "./console.sol"; 9 | import {console2} from "./console2.sol"; 10 | import {StdAssertions} from "./StdAssertions.sol"; 11 | import {StdChains} from "./StdChains.sol"; 12 | import {StdCheats} from "./StdCheats.sol"; 13 | import {stdError} from "./StdError.sol"; 14 | import {stdJson} from "./StdJson.sol"; 15 | import {stdMath} from "./StdMath.sol"; 16 | import {StdStorage, stdStorage} from "./StdStorage.sol"; 17 | import {StdUtils} from "./StdUtils.sol"; 18 | import {Vm} from "./Vm.sol"; 19 | 20 | // 📦 BOILERPLATE 21 | import {TestBase} from "./Base.sol"; 22 | import {DSTest} from "ds-test/test.sol"; 23 | 24 | // ⭐️ TEST 25 | abstract contract Test is DSTest, StdAssertions, StdChains, StdCheats, StdUtils, TestBase { 26 | // Note: IS_TEST() must return true. 27 | // Note: Must have failure system, https://github.com/dapphub/ds-test/blob/cd98eff28324bfac652e63a239a60632a761790b/src/test.sol#L39-L76. 28 | } 29 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright Contributors to Forge Standard Library 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE O THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE.R 26 | -------------------------------------------------------------------------------- /src/components/GitHubLink.tsx: -------------------------------------------------------------------------------- 1 | export const GitHubLink = () => { 2 | return ( 3 | 13 | 14 | GitHub 15 | 16 | 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/components/L2Panel.tsx: -------------------------------------------------------------------------------- 1 | import { Banner, Spinner } from "@ensdomains/thorin"; 2 | import { ChainPanel } from "./ChainPanel"; 3 | import { optimismGoerli } from "wagmi/chains"; 4 | import { useBlockNumber } from "wagmi"; 5 | import { L2TestNFTCard } from "./L2TestNFTCard"; 6 | import { L2TestCoinCard } from "./L2TestCoinCard"; 7 | 8 | const BlockNumberDisplay = () => { 9 | const { data } = useBlockNumber({ watch: true, chainId: optimismGoerli.id }); 10 | 11 | return ( 12 | 19 |
20 | 21 | {data ? data.toString() : "Loading..."} 22 |
23 |
24 | ); 25 | }; 26 | 27 | export const L2Panel = () => { 28 | return ( 29 | 37 | 38 |
39 | 40 | 41 | 42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Key used to upload source to Goerli Etherscan (get from https://etherscan.io/register) 2 | ETHERSCAN_API_KEY_GOERLI= 3 | 4 | # Key used to upload source to OP-Goerli Etherscan (get from https://explorer.optimism.io/myapikey) 5 | ETHERSCAN_API_KEY_OP_GOERLI= 6 | 7 | # OPVerifier contract address 8 | # example 1: https://github.com/ensdomains/evmgateway/tree/main/op-gateway#current-deployments 9 | # example 2: https://github.com/ethereum-optimism/evmgateway-starter#op-gateway-and-op-verifier-deployments 10 | OP_VERIFIER_CONTRACT_ADDRESS=0xe58448bfc2fa097953e800e0af0b0a5257ecc4b1 11 | 12 | # WalletConnect v2 requires a project ID. You can obtain it from your WC dashboard: 13 | # https://cloud.walletconnect.com/sign-in 14 | VITE_WALLETCONNECT_PROJECT_ID= 15 | 16 | # Goerli RPC URL 17 | VITE_RPC_URL_GOERLI= 18 | 19 | # OP Goerli RPC URL 20 | VITE_RPC_URL_OP_GOERLI= 21 | 22 | # L2TestNFT deployed to OP Goerli, update if you deploy a new one 23 | VITE_L2TESTNFT_CONTRACT_ADDRESS_OP_GOERLI=0x22299910e573ecd436f4987c75f093894904d107 24 | 25 | # L2TestCoin deployed to OP Goerli, update if you deploy a new one 26 | VITE_L2TESTCOIN_CONTRACT_ADDRESS_OP_GOERLI=0x5a81f1f4d30f4153150848c31fabd0311946ed72 27 | 28 | # L1PassportNFT deployed to Goerli, update if you deploy a new one 29 | VITE_L1PASSPORTNFT_CONTRACT_ADDRESS_GOERLI=0x0e24f4af1d5cd7fac0a96649511a15439d7e0c04 30 | -------------------------------------------------------------------------------- /src/components/DirectionsBanner.tsx: -------------------------------------------------------------------------------- 1 | import { Banner, Typography } from "@ensdomains/thorin"; 2 | 3 | const StepItem = ({ 4 | step, 5 | children, 6 | }: { 7 | step: number; 8 | children: React.ReactNode; 9 | }) => { 10 | return ( 11 |
12 |
{step}.
13 |
{children}
14 |
15 | ); 16 | }; 17 | 18 | export const DirectionsBanner = () => { 19 | return ( 20 | 25 |
32 | 33 | Steps 34 | 35 | 36 | Mint an L1PassportNFT on Goerli 37 | 38 | 39 | 40 | Mint some L2TestNFT and L2TestCoin on OP Goerli 41 | 42 | 43 | Wait for the OP Goerli blocks with your transactions to be available 44 | on Goerli 45 | 46 | 47 | Watch your L1PassportNFT dynamically update with your latest L2 48 | balances 49 | 50 |
51 |
52 | ); 53 | }; 54 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/StdMath.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | library stdMath { 5 | int256 private constant INT256_MIN = -57896044618658097711785492504343953926634992332820282019728792003956564819968; 6 | 7 | function abs(int256 a) internal pure returns (uint256) { 8 | // Required or it will fail when `a = type(int256).min` 9 | if (a == INT256_MIN) { 10 | return 57896044618658097711785492504343953926634992332820282019728792003956564819968; 11 | } 12 | 13 | return uint256(a > 0 ? a : -a); 14 | } 15 | 16 | function delta(uint256 a, uint256 b) internal pure returns (uint256) { 17 | return a > b ? a - b : b - a; 18 | } 19 | 20 | function delta(int256 a, int256 b) internal pure returns (uint256) { 21 | // a and b are of the same sign 22 | // this works thanks to two's complement, the left-most bit is the sign bit 23 | if ((a ^ b) > -1) { 24 | return delta(abs(a), abs(b)); 25 | } 26 | 27 | // a and b are of opposite signs 28 | return abs(a) + abs(b); 29 | } 30 | 31 | function percentDelta(uint256 a, uint256 b) internal pure returns (uint256) { 32 | uint256 absDelta = delta(a, b); 33 | 34 | return absDelta * 1e18 / b; 35 | } 36 | 37 | function percentDelta(int256 a, int256 b) internal pure returns (uint256) { 38 | uint256 absDelta = delta(a, b); 39 | uint256 absB = abs(b); 40 | 41 | return absDelta * 1e18 / absB; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /contracts/src/evm-verifier/EVMFetchTarget.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { IEVMVerifier } from './IEVMVerifier.sol'; 5 | import { Address } from '@openzeppelin/contracts/utils/Address.sol'; 6 | 7 | /** 8 | * @dev Callback implementation for users of `EVMFetcher`. If you use `EVMFetcher`, your contract must 9 | * inherit from this contract in order to handle callbacks correctly. 10 | */ 11 | abstract contract EVMFetchTarget { 12 | using Address for address; 13 | 14 | error ResponseLengthMismatch(uint256 actual, uint256 expected); 15 | 16 | /** 17 | * @dev Internal callback function invoked by CCIP-Read in response to a `getStorageSlots` request. 18 | */ 19 | function getStorageSlotsCallback(bytes calldata response, bytes calldata extradata) external { 20 | bytes memory proof = abi.decode(response, (bytes)); 21 | (IEVMVerifier verifier, address addr, bytes32[] memory commands, bytes[] memory constants, bytes4 callback, bytes memory callbackData) = 22 | abi.decode(extradata, (IEVMVerifier, address, bytes32[], bytes[], bytes4, bytes)); 23 | bytes[] memory values = verifier.getStorageValues(addr, commands, constants, proof); 24 | if(values.length != commands.length) { 25 | revert ResponseLengthMismatch(values.length, commands.length); 26 | } 27 | bytes memory ret = address(this).functionCall(abi.encodeWithSelector(callback, values, callbackData)); 28 | assembly { 29 | return(add(ret, 32), mload(ret)) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/Base.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | import {StdStorage} from "./StdStorage.sol"; 5 | import {Vm, VmSafe} from "./Vm.sol"; 6 | 7 | abstract contract CommonBase { 8 | // Cheat code address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D. 9 | address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); 10 | // console.sol and console2.sol work by executing a staticcall to this address. 11 | address internal constant CONSOLE = 0x000000000000000000636F6e736F6c652e6c6f67; 12 | // Default address for tx.origin and msg.sender, 0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38. 13 | address internal constant DEFAULT_SENDER = address(uint160(uint256(keccak256("foundry default caller")))); 14 | // Address of the test contract, deployed by the DEFAULT_SENDER. 15 | address internal constant DEFAULT_TEST_CONTRACT = 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f; 16 | // Deterministic deployment address of the Multicall3 contract. 17 | address internal constant MULTICALL3_ADDRESS = 0xcA11bde05977b3631167028862bE2a173976CA11; 18 | 19 | uint256 internal constant UINT256_MAX = 20 | 115792089237316195423570985008687907853269984665640564039457584007913129639935; 21 | 22 | Vm internal constant vm = Vm(VM_ADDRESS); 23 | StdStorage internal stdstore; 24 | } 25 | 26 | abstract contract TestBase is CommonBase {} 27 | 28 | abstract contract ScriptBase is CommonBase { 29 | // Used when deploying with create2, https://github.com/Arachnid/deterministic-deployment-proxy. 30 | address internal constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C; 31 | 32 | VmSafe internal constant vmSafe = VmSafe(VM_ADDRESS); 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "optimism-starter", 3 | "version": "0.0.4", 4 | "private": true, 5 | "scripts": { 6 | "deploy:l2-test-coin": "source .env && forge create contracts/src/L2TestCoin.sol:L2TestCoin --rpc-url $VITE_RPC_URL_OP_GOERLI -i --etherscan-api-key $ETHERSCAN_API_KEY_OP_GOERLI --verify --constructor-args \"L2 Test Coin\" \"L2TESTCOIN\" 6", 7 | "deploy:l2-test-nft": "source .env && forge create contracts/src/L2TestNFT.sol:L2TestNFT --rpc-url $VITE_RPC_URL_OP_GOERLI -i --etherscan-api-key $ETHERSCAN_API_KEY_OP_GOERLI --verify --constructor-args \"L2 Test NFT\" \"L2TESTNFT\"", 8 | "deploy:l1-passport-nft": "source .env && forge create contracts/src/L1PassportNFT.sol:L1PassportNFT --rpc-url $VITE_RPC_URL_GOERLI -i --etherscan-api-key $ETHERSCAN_API_KEY_GOERLI --verify --constructor-args \"L1 Passport NFT\" \"L1PASSPORT\" $OP_VERIFIER_CONTRACT_ADDRESS $VITE_L2TESTCOIN_CONTRACT_ADDRESS_OP_GOERLI $VITE_L2TESTNFT_CONTRACT_ADDRESS_OP_GOERLI", 9 | "dev": "vite", 10 | "build": "vite build", 11 | "build:production": "vite build", 12 | "preview": "vite preview", 13 | "lint": "prettier --write \"**/*.{ts,tsx}\"", 14 | "typecheck": "tsc --noEmit" 15 | }, 16 | "dependencies": { 17 | "@ensdomains/thorin": "^0.6.44", 18 | "@eth-optimism/contracts-bedrock": "^0.16.2", 19 | "@openzeppelin/contracts": "4.7.3", 20 | "@openzeppelin/contracts-upgradeable": "4.7.3", 21 | "@rainbow-me/rainbowkit": "^1.0.7", 22 | "buffer": "^6.0.3", 23 | "process": "^0.11.10", 24 | "react": "^18.2.0", 25 | "react-dom": "^18.2.0", 26 | "react-transition-state": "^2.1.1", 27 | "styled-components": "^6.1.0", 28 | "util": "^0.12.4", 29 | "viem": "^1.19.15", 30 | "wagmi": "^1.4.12" 31 | }, 32 | "devDependencies": { 33 | "@types/react": "^18.0.9", 34 | "@types/react-dom": "^18.0.3", 35 | "@vitejs/plugin-react": "^3.1.0", 36 | "prettier": "^2.8.4", 37 | "typescript": "^4.9.5", 38 | "vite": "^4.1.4" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /contracts/src/op-verifier/OPVerifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { IEVMVerifier } from "../evm-verifier/IEVMVerifier.sol"; 5 | import { RLPReader } from "@eth-optimism/contracts-bedrock/src/libraries/rlp/RLPReader.sol"; 6 | import { StateProof, EVMProofHelper } from "../evm-verifier/EVMProofHelper.sol"; 7 | import { Types } from "@eth-optimism/contracts-bedrock/src/libraries/Types.sol"; 8 | import { Hashing } from "@eth-optimism/contracts-bedrock/src/libraries/Hashing.sol"; 9 | 10 | struct OPWitnessData { 11 | uint256 l2OutputIndex; 12 | Types.OutputRootProof outputRootProof; 13 | } 14 | 15 | interface IL2OutputOracle { 16 | function getL2Output(uint256 _l2OutputIndex) external view returns (Types.OutputProposal memory); 17 | } 18 | 19 | contract OPVerifier is IEVMVerifier { 20 | error OutputRootMismatch(uint256 l2OutputIndex, bytes32 expected, bytes32 actual); 21 | 22 | IL2OutputOracle public opOracle; 23 | string[] _gatewayURLs; 24 | 25 | constructor(string[] memory urls, address outputOracle) { 26 | _gatewayURLs = urls; 27 | opOracle = IL2OutputOracle(outputOracle); 28 | } 29 | 30 | function gatewayURLs() external view returns(string[] memory) { 31 | return _gatewayURLs; 32 | } 33 | 34 | function getStorageValues(address target, bytes32[] memory commands, bytes[] memory constants, bytes memory proof) external view returns(bytes[] memory values) { 35 | (OPWitnessData memory opData, StateProof memory stateProof) = abi.decode(proof, (OPWitnessData, StateProof)); 36 | Types.OutputProposal memory l2out = opOracle.getL2Output(opData.l2OutputIndex); 37 | bytes32 expectedRoot = Hashing.hashOutputRootProof(opData.outputRootProof); 38 | if(l2out.outputRoot != expectedRoot) { 39 | revert OutputRootMismatch(opData.l2OutputIndex, expectedRoot, l2out.outputRoot); 40 | } 41 | return EVMProofHelper.getStorageValues(target, commands, constants, opData.outputRootProof.stateRoot, stateProof); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/wagmi.ts: -------------------------------------------------------------------------------- 1 | import { configureChains, createConfig } from "wagmi"; 2 | import { foundry, goerli, optimismGoerli } from "wagmi/chains"; 3 | import { jsonRpcProvider } from "wagmi/providers/jsonRpc"; 4 | import { getDefaultWallets } from "@rainbow-me/rainbowkit"; 5 | 6 | /** 7 | * Tell wagmi which chains you want to support 8 | * To add a new chain simply import it and add it here 9 | * @see https://wagmi.sh/react/providers/configuring-chains 10 | */ 11 | const { chains, publicClient } = configureChains( 12 | [goerli, optimismGoerli, foundry], 13 | [ 14 | /** 15 | * Tells wagmi to use the default RPC URL for each chain 16 | * for some dapps the higher rate limits of Alchemy may be required 17 | */ 18 | jsonRpcProvider({ 19 | rpc: (chain) => { 20 | if (chain.id === foundry.id) { 21 | return { http: "http://localhost:8545" }; 22 | } 23 | if (chain.id === goerli.id && import.meta.env.VITE_RPC_URL_GOERLI) { 24 | return { http: import.meta.env.VITE_RPC_URL_GOERLI }; 25 | } 26 | if ( 27 | chain.id === optimismGoerli.id && 28 | import.meta.env.VITE_RPC_URL_OP_GOERLI 29 | ) { 30 | return { http: import.meta.env.VITE_RPC_URL_OP_GOERLI }; 31 | } 32 | return { http: chain.rpcUrls.default.http[0] }; 33 | }, 34 | }), 35 | ], 36 | ); 37 | 38 | /** 39 | * Export chains to be used by rainbowkit 40 | * @see https://wagmi.sh/react/providers/configuring-chains 41 | */ 42 | export { chains }; 43 | 44 | /** 45 | * Configures wagmi connectors for rainbowkit 46 | * @see https://www.rainbowkit.com/docs/custom-wallet-list 47 | * @see https://wagmi.sh/react/connectors 48 | */ 49 | const { connectors } = getDefaultWallets({ 50 | appName: "EVMGateway OPGoerli Passport", 51 | chains, 52 | projectId: import.meta.env.VITE_WALLETCONNECT_PROJECT_ID, 53 | }); 54 | 55 | /** 56 | * Creates a singleton wagmi client for the app 57 | * @see https://wagmi.sh/react/client 58 | */ 59 | export const config = createConfig({ 60 | autoConnect: true, 61 | connectors: connectors, 62 | publicClient, 63 | webSocketPublicClient: publicClient, 64 | }); 65 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2; 3 | 4 | /// @dev Interface of the ERC20 standard as defined in the EIP. 5 | /// @dev This includes the optional name, symbol, and decimals metadata. 6 | interface IERC20 { 7 | /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`). 8 | event Transfer(address indexed from, address indexed to, uint256 value); 9 | 10 | /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value` 11 | /// is the new allowance. 12 | event Approval(address indexed owner, address indexed spender, uint256 value); 13 | 14 | /// @notice Returns the amount of tokens in existence. 15 | function totalSupply() external view returns (uint256); 16 | 17 | /// @notice Returns the amount of tokens owned by `account`. 18 | function balanceOf(address account) external view returns (uint256); 19 | 20 | /// @notice Moves `amount` tokens from the caller's account to `to`. 21 | function transfer(address to, uint256 amount) external returns (bool); 22 | 23 | /// @notice Returns the remaining number of tokens that `spender` is allowed 24 | /// to spend on behalf of `owner` 25 | function allowance(address owner, address spender) external view returns (uint256); 26 | 27 | /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens. 28 | /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 29 | function approve(address spender, uint256 amount) external returns (bool); 30 | 31 | /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism. 32 | /// `amount` is then deducted from the caller's allowance. 33 | function transferFrom(address from, address to, uint256 amount) external returns (bool); 34 | 35 | /// @notice Returns the name of the token. 36 | function name() external view returns (string memory); 37 | 38 | /// @notice Returns the symbol of the token. 39 | function symbol() external view returns (string memory); 40 | 41 | /// @notice Returns the decimals places of the token. 42 | function decimals() external view returns (uint8); 43 | } 44 | -------------------------------------------------------------------------------- /src/components/LatestL2OutputOracleDisplay.tsx: -------------------------------------------------------------------------------- 1 | import { useBlockNumber } from "wagmi"; 2 | import { goerli, optimismGoerli } from "wagmi/chains"; 3 | import { Banner, Spinner } from "@ensdomains/thorin"; 4 | import { useState } from "react"; 5 | import { useLatestAvailableL2BlockNumberOnL1 } from "../lib/useLatestAvailableL2BlockNumberOnL1"; 6 | import { L2OutputOracleAddress } from "../constants/L2OutputOracleAddress"; 7 | 8 | const LastUpdatedAt = ({ date }: { date?: Date }) => { 9 | if (!date) { 10 | return null; 11 | } 12 | 13 | return
last submitted at {date.toLocaleTimeString()}
; 14 | }; 15 | 16 | const LatestL2OutputOracleContent = () => { 17 | const [latestL2OutputProposedDate, setLatestL2OutputProposedDate] = 18 | useState(); 19 | 20 | const { data: latestL2Block, isLoading: isLatestL2BlockLoading } = 21 | useBlockNumber({ 22 | watch: true, 23 | chainId: optimismGoerli.id, 24 | }); 25 | 26 | const { data: latestL2BlockOnL1, isLoading: isLatestL2BlockOnL1Loading } = 27 | useLatestAvailableL2BlockNumberOnL1((latestL1Timestamp) => { 28 | if (latestL1Timestamp) { 29 | setLatestL2OutputProposedDate( 30 | new Date(Number(latestL1Timestamp) * 1000), 31 | ); 32 | } 33 | }); 34 | 35 | if ( 36 | !latestL2BlockOnL1 || 37 | !latestL2Block || 38 | isLatestL2BlockOnL1Loading || 39 | isLatestL2BlockLoading 40 | ) { 41 | return <>"Loading..."; 42 | } 43 | 44 | const numBlocksBehind = latestL2Block - latestL2BlockOnL1; 45 | 46 | return ( 47 |
48 | 49 | {latestL2BlockOnL1.toString()} ({numBlocksBehind.toString()} blocks 50 | behind) 51 | {/* */} 52 |
53 | ); 54 | }; 55 | 56 | export const LatestL2OutputOracleDisplay = () => { 57 | return ( 58 | 65 | 66 | 67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /src/components/L2TransactionList.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionReceipt } from "viem"; 2 | import { TxBlockExplorerLink } from "./TxBlockExplorerLink"; 3 | import { optimismGoerli } from "wagmi/chains"; 4 | import { useLatestAvailableL2BlockNumberOnL1 } from "../lib/useLatestAvailableL2BlockNumberOnL1"; 5 | import { Tag } from "@ensdomains/thorin"; 6 | 7 | const L2BlockAvailableOnL1Tag = ({ 8 | transactionBlock, 9 | latestL2BlockOnL1, 10 | }: { 11 | transactionBlock: bigint; 12 | latestL2BlockOnL1?: bigint; 13 | }) => { 14 | if (!latestL2BlockOnL1 || latestL2BlockOnL1 < transactionBlock) { 15 | return ( 16 | 17 | Unavailable on L1 18 | 19 | ); 20 | } 21 | return ( 22 | 23 | Available on L1 24 | 25 | ); 26 | }; 27 | 28 | export const L2TransactionList = ({ 29 | transactionReceipts, 30 | }: { 31 | transactionReceipts: TransactionReceipt[]; 32 | }) => { 33 | const { data: latestL2BlockOnL1, isLoading: isLatestL2BlockOnL1Loading } = 34 | useLatestAvailableL2BlockNumberOnL1(); 35 | return ( 36 |
43 | {transactionReceipts.map((transactionReceipt) => { 44 | const { blockNumber, transactionHash } = transactionReceipt; 45 | 46 | return ( 47 |
56 | 60 |
61 |
Block #: {blockNumber.toString()}
62 | 63 |
66 | 70 |
71 |
72 |
73 | ); 74 | })} 75 |
76 | ); 77 | }; 78 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { ConnectButton } from "@rainbow-me/rainbowkit"; 2 | import { useAccount } from "wagmi"; 3 | import { ThemeProvider } from "styled-components"; 4 | import { Heading, ThorinGlobalStyles, lightTheme } from "@ensdomains/thorin"; 5 | import { L1Panel } from "./components/L1Panel"; 6 | import { L2Panel } from "./components/L2Panel"; 7 | 8 | import "./App.css"; 9 | import { GitHubLink } from "./components/GitHubLink"; 10 | import { DirectionsBanner } from "./components/DirectionsBanner"; 11 | 12 | export function App() { 13 | /** 14 | * Wagmi hook for getting account information 15 | * @see https://wagmi.sh/docs/hooks/useAccount 16 | */ 17 | const { isConnected } = useAccount(); 18 | 19 | return ( 20 | 21 | 22 |
32 |
41 | 42 |
43 |
44 | OP Goerli Passport using EVMGateway 45 |
46 |
52 | 53 |
54 |
55 | 56 |
57 | {isConnected && ( 58 |
65 |
72 | 73 |
79 | 80 |
81 |
82 | )} 83 |
84 | 85 | ); 86 | } 87 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Install Foundry 17 | uses: onbjerg/foundry-toolchain@v1 18 | with: 19 | version: nightly 20 | 21 | - name: Print forge version 22 | run: forge --version 23 | 24 | # Backwards compatibility checks. 25 | - name: Check compatibility with 0.8.0 26 | if: always() 27 | run: forge build --skip test --use solc:0.8.0 28 | 29 | - name: Check compatibility with 0.7.6 30 | if: always() 31 | run: forge build --skip test --use solc:0.7.6 32 | 33 | - name: Check compatibility with 0.7.0 34 | if: always() 35 | run: forge build --skip test --use solc:0.7.0 36 | 37 | - name: Check compatibility with 0.6.12 38 | if: always() 39 | run: forge build --skip test --use solc:0.6.12 40 | 41 | - name: Check compatibility with 0.6.2 42 | if: always() 43 | run: forge build --skip test --use solc:0.6.2 44 | 45 | # via-ir compilation time checks. 46 | - name: Measure compilation time of Test with 0.8.17 --via-ir 47 | if: always() 48 | run: forge build --skip test --contracts test/compilation/CompilationTest.sol --use solc:0.8.17 --via-ir 49 | 50 | - name: Measure compilation time of TestBase with 0.8.17 --via-ir 51 | if: always() 52 | run: forge build --skip test --contracts test/compilation/CompilationTestBase.sol --use solc:0.8.17 --via-ir 53 | 54 | - name: Measure compilation time of Script with 0.8.17 --via-ir 55 | if: always() 56 | run: forge build --skip test --contracts test/compilation/CompilationScript.sol --use solc:0.8.17 --via-ir 57 | 58 | - name: Measure compilation time of ScriptBase with 0.8.17 --via-ir 59 | if: always() 60 | run: forge build --skip test --contracts test/compilation/CompilationScriptBase.sol --use solc:0.8.17 --via-ir 61 | 62 | test: 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v3 66 | 67 | - name: Install Foundry 68 | uses: onbjerg/foundry-toolchain@v1 69 | with: 70 | version: nightly 71 | 72 | - name: Print forge version 73 | run: forge --version 74 | 75 | - name: Run tests 76 | run: forge test -vvv 77 | 78 | fmt: 79 | runs-on: ubuntu-latest 80 | steps: 81 | - uses: actions/checkout@v3 82 | 83 | - name: Install Foundry 84 | uses: onbjerg/foundry-toolchain@v1 85 | with: 86 | version: nightly 87 | 88 | - name: Print forge version 89 | run: forge --version 90 | 91 | - name: Check formatting 92 | run: forge fmt --check 93 | -------------------------------------------------------------------------------- /contracts/src/evm-verifier/SecureMerkleTrie.sol: -------------------------------------------------------------------------------- 1 | // Pulled from https://github.com/ethereum-optimism/optimism/blob/4d13f0afe8869faf7bba45d8339998525ebc5161/packages/contracts-bedrock/contracts/libraries/trie/MerkleTrie.sol 2 | // as this is the last version of Optimism's Merkle Trie library that supports nonexistence proofs; support was removed 3 | // in the next commit for some version. 4 | // Copyright 2020-2021 Optimism 5 | // SPDX-License-Identifier: MIT 6 | pragma solidity ^0.8.0; 7 | 8 | /* Library Imports */ 9 | import { MerkleTrie } from "./MerkleTrie.sol"; 10 | 11 | /** 12 | * @title SecureMerkleTrie 13 | * @notice SecureMerkleTrie is a thin wrapper around the MerkleTrie library that hashes the input 14 | * keys. Ethereum's state trie hashes input keys before storing them. 15 | */ 16 | library SecureMerkleTrie { 17 | /** 18 | * @notice Verifies a proof that a given key/value pair is present in the Merkle trie. 19 | * 20 | * @param _key Key of the node to search for, as a hex string. 21 | * @param _value Value of the node to search for, as a hex string. 22 | * @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle 23 | * trees, this proof is executed top-down and consists of a list of RLP-encoded 24 | * nodes that make a path down to the target node. 25 | * @param _root Known root of the Merkle trie. Used to verify that the included proof is 26 | * correctly constructed. 27 | * 28 | * @return Whether or not the proof is valid. 29 | */ 30 | function verifyInclusionProof( 31 | bytes memory _key, 32 | bytes memory _value, 33 | bytes[] memory _proof, 34 | bytes32 _root 35 | ) internal pure returns (bool) { 36 | bytes memory key = _getSecureKey(_key); 37 | return MerkleTrie.verifyInclusionProof(key, _value, _proof, _root); 38 | } 39 | 40 | /** 41 | * @notice Retrieves the value associated with a given key. 42 | * 43 | * @param _key Key to search for, as hex bytes. 44 | * @param _proof Merkle trie inclusion proof for the key. 45 | * @param _root Known root of the Merkle trie. 46 | * 47 | * @return Whether or not the key exists. 48 | * @return Value of the key if it exists. 49 | */ 50 | function get( 51 | bytes memory _key, 52 | bytes[] memory _proof, 53 | bytes32 _root 54 | ) internal pure returns (bool, bytes memory) { 55 | bytes memory key = _getSecureKey(_key); 56 | return MerkleTrie.get(key, _proof, _root); 57 | } 58 | 59 | /** 60 | * @notice Computes the hashed version of the input key. 61 | * 62 | * @param _key Key to hash. 63 | * 64 | * @return Hashed version of the key. 65 | */ 66 | function _getSecureKey(bytes memory _key) private pure returns (bytes memory) { 67 | return abi.encodePacked(keccak256(_key)); 68 | } 69 | } -------------------------------------------------------------------------------- /contracts/lib/forge-std/test/StdError.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "../src/StdError.sol"; 5 | import "../src/Test.sol"; 6 | 7 | contract StdErrorsTest is Test { 8 | ErrorsTest test; 9 | 10 | function setUp() public { 11 | test = new ErrorsTest(); 12 | } 13 | 14 | function testExpectAssertion() public { 15 | vm.expectRevert(stdError.assertionError); 16 | test.assertionError(); 17 | } 18 | 19 | function testExpectArithmetic() public { 20 | vm.expectRevert(stdError.arithmeticError); 21 | test.arithmeticError(10); 22 | } 23 | 24 | function testExpectDiv() public { 25 | vm.expectRevert(stdError.divisionError); 26 | test.divError(0); 27 | } 28 | 29 | function testExpectMod() public { 30 | vm.expectRevert(stdError.divisionError); 31 | test.modError(0); 32 | } 33 | 34 | function testExpectEnum() public { 35 | vm.expectRevert(stdError.enumConversionError); 36 | test.enumConversion(1); 37 | } 38 | 39 | function testExpectEncodeStg() public { 40 | vm.expectRevert(stdError.encodeStorageError); 41 | test.encodeStgError(); 42 | } 43 | 44 | function testExpectPop() public { 45 | vm.expectRevert(stdError.popError); 46 | test.pop(); 47 | } 48 | 49 | function testExpectOOB() public { 50 | vm.expectRevert(stdError.indexOOBError); 51 | test.indexOOBError(1); 52 | } 53 | 54 | function testExpectMem() public { 55 | vm.expectRevert(stdError.memOverflowError); 56 | test.mem(); 57 | } 58 | 59 | function testExpectIntern() public { 60 | vm.expectRevert(stdError.zeroVarError); 61 | test.intern(); 62 | } 63 | } 64 | 65 | contract ErrorsTest { 66 | enum T {T1} 67 | 68 | uint256[] public someArr; 69 | bytes someBytes; 70 | 71 | function assertionError() public pure { 72 | assert(false); 73 | } 74 | 75 | function arithmeticError(uint256 a) public pure { 76 | a -= 100; 77 | } 78 | 79 | function divError(uint256 a) public pure { 80 | 100 / a; 81 | } 82 | 83 | function modError(uint256 a) public pure { 84 | 100 % a; 85 | } 86 | 87 | function enumConversion(uint256 a) public pure { 88 | T(a); 89 | } 90 | 91 | function encodeStgError() public { 92 | /// @solidity memory-safe-assembly 93 | assembly { 94 | sstore(someBytes.slot, 1) 95 | } 96 | keccak256(someBytes); 97 | } 98 | 99 | function pop() public { 100 | someArr.pop(); 101 | } 102 | 103 | function indexOOBError(uint256 a) public pure { 104 | uint256[] memory t = new uint256[](0); 105 | t[a]; 106 | } 107 | 108 | function mem() public pure { 109 | uint256 l = 2 ** 256 / 32; 110 | new uint256[](l); 111 | } 112 | 113 | function intern() public returns (uint256) { 114 | function(uint256) internal returns (uint256) x; 115 | x(2); 116 | return 7; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/InvariantTest.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | contract InvariantTest { 7 | struct FuzzSelector { 8 | address addr; 9 | bytes4[] selectors; 10 | } 11 | 12 | address[] private _excludedContracts; 13 | address[] private _excludedSenders; 14 | address[] private _targetedContracts; 15 | address[] private _targetedSenders; 16 | 17 | string[] private _excludedArtifacts; 18 | string[] private _targetedArtifacts; 19 | 20 | FuzzSelector[] private _targetedArtifactSelectors; 21 | FuzzSelector[] private _targetedSelectors; 22 | 23 | // Functions for users: 24 | // These are intended to be called in tests. 25 | 26 | function excludeContract(address newExcludedContract_) internal { 27 | _excludedContracts.push(newExcludedContract_); 28 | } 29 | 30 | function excludeSender(address newExcludedSender_) internal { 31 | _excludedSenders.push(newExcludedSender_); 32 | } 33 | 34 | function targetArtifact(string memory newTargetedArtifact_) internal { 35 | _targetedArtifacts.push(newTargetedArtifact_); 36 | } 37 | 38 | function targetArtifactSelector(FuzzSelector memory newTargetedArtifactSelector_) internal { 39 | _targetedArtifactSelectors.push(newTargetedArtifactSelector_); 40 | } 41 | 42 | function targetContract(address newTargetedContract_) internal { 43 | _targetedContracts.push(newTargetedContract_); 44 | } 45 | 46 | function targetSelector(FuzzSelector memory newTargetedSelector_) internal { 47 | _targetedSelectors.push(newTargetedSelector_); 48 | } 49 | 50 | function targetSender(address newTargetedSender_) internal { 51 | _targetedSenders.push(newTargetedSender_); 52 | } 53 | 54 | // Functions for forge: 55 | // These are called by forge to run invariant tests and don't need to be called in tests. 56 | 57 | function excludeArtifact(string memory newExcludedArtifact_) internal { 58 | _excludedArtifacts.push(newExcludedArtifact_); 59 | } 60 | 61 | function excludeArtifacts() public view returns (string[] memory excludedArtifacts_) { 62 | excludedArtifacts_ = _excludedArtifacts; 63 | } 64 | 65 | function excludeContracts() public view returns (address[] memory excludedContracts_) { 66 | excludedContracts_ = _excludedContracts; 67 | } 68 | 69 | function excludeSenders() public view returns (address[] memory excludedSenders_) { 70 | excludedSenders_ = _excludedSenders; 71 | } 72 | 73 | function targetArtifacts() public view returns (string[] memory targetedArtifacts_) { 74 | targetedArtifacts_ = _targetedArtifacts; 75 | } 76 | 77 | function targetArtifactSelectors() public view returns (FuzzSelector[] memory targetedArtifactSelectors_) { 78 | targetedArtifactSelectors_ = _targetedArtifactSelectors; 79 | } 80 | 81 | function targetContracts() public view returns (address[] memory targetedContracts_) { 82 | targetedContracts_ = _targetedContracts; 83 | } 84 | 85 | function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors_) { 86 | targetedSelectors_ = _targetedSelectors; 87 | } 88 | 89 | function targetSenders() public view returns (address[] memory targetedSenders_) { 90 | targetedSenders_ = _targetedSenders; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/components/L2TestCoinCard.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Card, Typography } from "@ensdomains/thorin"; 2 | import { optimismGoerli } from "wagmi/chains"; 3 | import { 4 | useAccount, 5 | useContractReads, 6 | useContractWrite, 7 | useNetwork, 8 | usePrepareContractWrite, 9 | useWaitForTransaction, 10 | } from "wagmi"; 11 | import { L2TestCoinAbi } from "../constants/L2TestCoinAbi"; 12 | import { useState } from "react"; 13 | import { TransactionReceipt } from "viem"; 14 | import { L2TransactionList } from "./L2TransactionList"; 15 | import { SwitchNetworkButton } from "./SwitchNetworkButton"; 16 | 17 | const MintCoinButton = ({ 18 | onSuccess, 19 | }: { 20 | onSuccess: (transactionReceipt: TransactionReceipt) => void; 21 | }) => { 22 | const { address } = useAccount(); 23 | const { config } = usePrepareContractWrite({ 24 | address: import.meta.env.VITE_L2TESTCOIN_CONTRACT_ADDRESS_OP_GOERLI, 25 | abi: L2TestCoinAbi, 26 | chainId: optimismGoerli.id, 27 | functionName: "mintTo", 28 | args: [address!, 1000n], 29 | enabled: !!address, 30 | }); 31 | const { 32 | data, 33 | write, 34 | isLoading: isTransactionSendLoading, 35 | } = useContractWrite(config); 36 | const { isLoading: isTransactionConfirmationLoading } = useWaitForTransaction( 37 | { 38 | hash: data?.hash, 39 | chainId: optimismGoerli.id, 40 | confirmations: 2, 41 | onSuccess: (receipt) => { 42 | onSuccess(receipt); 43 | }, 44 | }, 45 | ); 46 | 47 | const { chain } = useNetwork(); 48 | if (chain?.id !== optimismGoerli.id) { 49 | return ; 50 | } 51 | 52 | const isDisabled = 53 | !write || isTransactionSendLoading || isTransactionConfirmationLoading; 54 | const isLoading = 55 | isTransactionSendLoading || isTransactionConfirmationLoading; 56 | 57 | return ( 58 | 67 | ); 68 | }; 69 | 70 | export const L2TestCoinCard = () => { 71 | const [latestTransactions, setLatestTransactions] = useState< 72 | TransactionReceipt[] 73 | >([]); 74 | 75 | const { address } = useAccount(); 76 | const { 77 | data = [], 78 | isLoading, 79 | refetch, 80 | } = useContractReads({ 81 | contracts: [ 82 | { 83 | abi: L2TestCoinAbi, 84 | address: import.meta.env.VITE_L2TESTCOIN_CONTRACT_ADDRESS_OP_GOERLI, 85 | functionName: "balanceOf", 86 | args: [address!], 87 | chainId: optimismGoerli.id, 88 | }, 89 | { 90 | abi: L2TestCoinAbi, 91 | address: import.meta.env.VITE_L2TESTCOIN_CONTRACT_ADDRESS_OP_GOERLI, 92 | functionName: "totalSupply", 93 | chainId: optimismGoerli.id, 94 | }, 95 | ], 96 | enabled: !!address, 97 | allowFailure: false, 98 | }); 99 | 100 | const [userCoinBalance, totalSupply] = data; 101 | 102 | return ( 103 | 104 | L2TestCoin 105 | 106 | Wallet balance:{" "} 107 | {userCoinBalance !== undefined 108 | ? userCoinBalance.toString() 109 | : "Loading..."} 110 | 111 | 112 | Total supply:{" "} 113 | {totalSupply !== undefined ? totalSupply.toString() : "Loading..."} 114 | 115 | 116 | { 118 | setLatestTransactions((latestTransactions) => [ 119 | transactionReceipt, 120 | ...latestTransactions, 121 | ]); 122 | refetch(); 123 | }} 124 | /> 125 | 126 | 127 | ); 128 | }; 129 | -------------------------------------------------------------------------------- /src/components/L2TestNFTCard.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Card, Typography } from "@ensdomains/thorin"; 2 | import { optimismGoerli } from "wagmi/chains"; 3 | import { 4 | useAccount, 5 | useContractReads, 6 | useContractWrite, 7 | useNetwork, 8 | usePrepareContractWrite, 9 | useWaitForTransaction, 10 | } from "wagmi"; 11 | import { L2TestNFTAbi } from "../constants/L2TestNFTAbi"; 12 | import { TransactionReceipt } from "viem"; 13 | import { useState } from "react"; 14 | import { L2TransactionList } from "./L2TransactionList"; 15 | import { SwitchNetworkButton } from "./SwitchNetworkButton"; 16 | 17 | const MintNFTButton = ({ 18 | onSuccess, 19 | }: { 20 | onSuccess: (transactionReceipt: TransactionReceipt) => void; 21 | }) => { 22 | const { address } = useAccount(); 23 | const { config } = usePrepareContractWrite({ 24 | address: import.meta.env.VITE_L2TESTNFT_CONTRACT_ADDRESS_OP_GOERLI, 25 | abi: L2TestNFTAbi, 26 | chainId: optimismGoerli.id, 27 | functionName: "mintTo", 28 | args: [address!], 29 | enabled: !!address, 30 | }); 31 | const { 32 | data, 33 | write, 34 | isLoading: isTransactionSendLoading, 35 | } = useContractWrite(config); 36 | const { isLoading: isTransactionConfirmationLoading } = useWaitForTransaction( 37 | { 38 | hash: data?.hash, 39 | chainId: optimismGoerli.id, 40 | confirmations: 2, 41 | onSuccess: (receipt) => { 42 | onSuccess(receipt); 43 | }, 44 | }, 45 | ); 46 | 47 | const { chain } = useNetwork(); 48 | if (chain?.id !== optimismGoerli.id) { 49 | return ; 50 | } 51 | 52 | const isDisabled = 53 | !write || isTransactionSendLoading || isTransactionConfirmationLoading; 54 | const isLoading = 55 | isTransactionSendLoading || isTransactionConfirmationLoading; 56 | 57 | return ( 58 | 67 | ); 68 | }; 69 | 70 | export const L2TestNFTCard = () => { 71 | const { address } = useAccount(); 72 | const [latestTransactions, setLatestTransactions] = useState< 73 | TransactionReceipt[] 74 | >([]); 75 | 76 | const { data = [], refetch } = useContractReads({ 77 | contracts: [ 78 | { 79 | abi: L2TestNFTAbi, 80 | address: import.meta.env.VITE_L2TESTNFT_CONTRACT_ADDRESS_OP_GOERLI, 81 | functionName: "balanceOf", 82 | args: [address!], 83 | chainId: optimismGoerli.id, 84 | }, 85 | { 86 | abi: L2TestNFTAbi, 87 | address: import.meta.env.VITE_L2TESTNFT_CONTRACT_ADDRESS_OP_GOERLI, 88 | functionName: "currentTokenId", 89 | chainId: optimismGoerli.id, 90 | }, 91 | ], 92 | enabled: !!address, 93 | allowFailure: false, 94 | }); 95 | 96 | const [userNFTBalance, currentTokenId] = data; 97 | return ( 98 | 104 | L2TestNFT 105 | 106 | Wallet balance:{" "} 107 | {userNFTBalance !== undefined 108 | ? userNFTBalance.toString() 109 | : "Loading..."} 110 | 111 | 112 | Total minted:{" "} 113 | {currentTokenId !== undefined 114 | ? currentTokenId.toString() 115 | : "Loading..."} 116 | 117 | { 119 | setLatestTransactions((latestTransactions) => [ 120 | transactionReceipt, 121 | ...latestTransactions, 122 | ]); 123 | refetch(); 124 | }} 125 | /> 126 | 127 | 128 | ); 129 | }; 130 | -------------------------------------------------------------------------------- /src/components/L1NFTPassportCard.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Card, Typography } from "@ensdomains/thorin"; 2 | import { goerli, optimismGoerli } from "wagmi/chains"; 3 | import { 4 | useAccount, 5 | useContractEvent, 6 | useContractRead, 7 | useContractWrite, 8 | useNetwork, 9 | usePrepareContractWrite, 10 | useWaitForTransaction, 11 | } from "wagmi"; 12 | import { TxBlockExplorerLink } from "./TxBlockExplorerLink"; 13 | import { L1PassportNFTAbi } from "../constants/L1PassportNFTAbi"; 14 | import { L1NFTPassportViewer } from "./L1NFTPassportViewer"; 15 | import { hexToBigInt } from "viem"; 16 | import { L2OutputOracleAbi } from "../constants/L2OutputOracleAbi"; 17 | import { SwitchNetworkButton } from "./SwitchNetworkButton"; 18 | import { L2OutputOracleAddress } from "../constants/L2OutputOracleAddress"; 19 | 20 | const MintL1PassportNFTButton = ({ onSuccess }: { onSuccess: () => void }) => { 21 | const { address } = useAccount(); 22 | const { config } = usePrepareContractWrite({ 23 | address: import.meta.env.VITE_L1PASSPORTNFT_CONTRACT_ADDRESS_GOERLI, 24 | abi: L1PassportNFTAbi, 25 | chainId: goerli.id, 26 | functionName: "mintTo", 27 | args: [address!], 28 | enabled: !!address, 29 | }); 30 | const { 31 | data, 32 | write, 33 | isLoading: isTransactionSendLoading, 34 | } = useContractWrite(config); 35 | const { isLoading: isTransactionConfirmationLoading } = useWaitForTransaction( 36 | { 37 | hash: data?.hash, 38 | chainId: goerli.id, 39 | confirmations: 2, 40 | onSuccess: () => { 41 | onSuccess(); 42 | }, 43 | }, 44 | ); 45 | 46 | const { chain } = useNetwork(); 47 | if (chain?.id !== goerli.id) { 48 | return ; 49 | } 50 | 51 | const isDisabled = 52 | !write || isTransactionSendLoading || isTransactionConfirmationLoading; 53 | const isLoading = 54 | isTransactionSendLoading || isTransactionConfirmationLoading; 55 | 56 | return ( 57 |
64 | 73 | {data?.hash && ( 74 |
80 | 81 |
82 | )} 83 |
84 | ); 85 | }; 86 | 87 | export const L1NFTPassportCard = () => { 88 | const { address } = useAccount(); 89 | const { 90 | data: balanceOf, 91 | isLoading: isBalanceOfLoading, 92 | refetch: refetchBalanceOf, 93 | } = useContractRead({ 94 | address: import.meta.env.VITE_L1PASSPORTNFT_CONTRACT_ADDRESS_GOERLI, 95 | abi: L1PassportNFTAbi, 96 | chainId: goerli.id, 97 | functionName: "balanceOf", 98 | args: [address!], 99 | enabled: !!address, 100 | }); 101 | 102 | const { 103 | data: tokenUri, 104 | isFetching, 105 | isLoading, 106 | refetch: refetchTokenUri, 107 | } = useContractRead({ 108 | address: import.meta.env.VITE_L1PASSPORTNFT_CONTRACT_ADDRESS_GOERLI, 109 | chainId: goerli.id, 110 | abi: L1PassportNFTAbi, 111 | functionName: "tokenURI", 112 | args: [hexToBigInt(address!, { size: 32 })], 113 | enabled: !!address, 114 | }); 115 | 116 | useContractEvent({ 117 | abi: L2OutputOracleAbi, 118 | address: L2OutputOracleAddress, 119 | eventName: "OutputProposed", 120 | chainId: goerli.id, 121 | listener: () => { 122 | // refetch the NFT tokenURI when a new output is proposed 123 | setTimeout(() => refetchTokenUri(), 1000); 124 | }, 125 | }); 126 | 127 | return ( 128 | 134 | 135 | L1PassportNFT (Non-transferrable) 136 | 137 | 138 | NFT will update dynamically as you mint new NFTs/Coins on{" "} 139 | {optimismGoerli.name} 140 | 141 | {balanceOf === undefined || balanceOf === 0n ? ( 142 | refetchBalanceOf()} /> 143 | ) : ( 144 |
151 |
157 | {tokenUri && } 158 |
159 |
167 | {(isFetching || isLoading) && "Refetching..."} 168 |
169 |
170 | )} 171 |
172 | ); 173 | }; 174 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/test/StdChains.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.7.0 <0.9.0; 3 | 4 | import "../src/Test.sol"; 5 | 6 | contract StdChainsTest is Test { 7 | function testChainRpcInitialization() public { 8 | // RPCs specified in `foundry.toml` should be updated. 9 | assertEq(getChain(1).rpcUrl, "https://mainnet.infura.io/v3/7a8769b798b642f6933f2ed52042bd70"); 10 | assertEq(getChain("optimism_goerli").rpcUrl, "https://goerli.optimism.io/"); 11 | assertEq(getChain("arbitrum_one_goerli").rpcUrl, "https://goerli-rollup.arbitrum.io/rpc/"); 12 | 13 | // Other RPCs should remain unchanged. 14 | assertEq(getChain(31337).rpcUrl, "http://127.0.0.1:8545"); 15 | assertEq(getChain("sepolia").rpcUrl, "https://sepolia.infura.io/v3/6770454bc6ea42c58aac12978531b93f"); 16 | } 17 | 18 | function testRpc(string memory rpcAlias) internal { 19 | string memory rpcUrl = getChain(rpcAlias).rpcUrl; 20 | vm.createSelectFork(rpcUrl); 21 | } 22 | 23 | // Ensure we can connect to the default RPC URL for each chain. 24 | function testRpcs() public { 25 | testRpc("mainnet"); 26 | testRpc("goerli"); 27 | testRpc("sepolia"); 28 | testRpc("optimism"); 29 | testRpc("optimism_goerli"); 30 | testRpc("arbitrum_one"); 31 | testRpc("arbitrum_one_goerli"); 32 | testRpc("arbitrum_nova"); 33 | testRpc("polygon"); 34 | testRpc("polygon_mumbai"); 35 | testRpc("avalanche"); 36 | testRpc("avalanche_fuji"); 37 | testRpc("bnb_smart_chain"); 38 | testRpc("bnb_smart_chain_testnet"); 39 | testRpc("gnosis_chain"); 40 | } 41 | 42 | function testChainNoDefault() public { 43 | vm.expectRevert("StdChains getChain(string): Chain with alias \"does_not_exist\" not found."); 44 | getChain("does_not_exist"); 45 | } 46 | 47 | function testSetChainFirstFails() public { 48 | vm.expectRevert("StdChains setChain(string,Chain): Chain ID 31337 already used by \"anvil\"."); 49 | setChain("anvil2", Chain("Anvil", 31337, "URL")); 50 | } 51 | 52 | function testChainBubbleUp() public { 53 | setChain("needs_undefined_env_var", Chain("", 123456789, "")); 54 | vm.expectRevert( 55 | "Failed to resolve env var `UNDEFINED_RPC_URL_PLACEHOLDER` in `${UNDEFINED_RPC_URL_PLACEHOLDER}`: environment variable not found" 56 | ); 57 | getChain("needs_undefined_env_var"); 58 | } 59 | 60 | function testCannotSetChain_ChainIdExists() public { 61 | setChain("custom_chain", Chain("Custom Chain", 123456789, "https://custom.chain/")); 62 | 63 | vm.expectRevert('StdChains setChain(string,Chain): Chain ID 123456789 already used by "custom_chain".'); 64 | 65 | setChain("another_custom_chain", Chain("", 123456789, "")); 66 | } 67 | 68 | function testSetChain() public { 69 | setChain("custom_chain", Chain("Custom Chain", 123456789, "https://custom.chain/")); 70 | Chain memory customChain = getChain("custom_chain"); 71 | assertEq(customChain.name, "Custom Chain"); 72 | assertEq(customChain.chainId, 123456789); 73 | assertEq(customChain.rpcUrl, "https://custom.chain/"); 74 | Chain memory chainById = getChain(123456789); 75 | assertEq(chainById.name, customChain.name); 76 | assertEq(chainById.chainId, customChain.chainId); 77 | assertEq(chainById.rpcUrl, customChain.rpcUrl); 78 | } 79 | 80 | function testSetNoEmptyAlias() public { 81 | vm.expectRevert("StdChains setChain(string,Chain): Chain alias cannot be the empty string."); 82 | setChain("", Chain("", 123456789, "")); 83 | } 84 | 85 | function testSetNoChainId0() public { 86 | vm.expectRevert("StdChains setChain(string,Chain): Chain ID cannot be 0."); 87 | setChain("alias", Chain("", 0, "")); 88 | } 89 | 90 | function testGetNoChainId0() public { 91 | vm.expectRevert("StdChains getChain(uint256): Chain ID cannot be 0."); 92 | getChain(0); 93 | } 94 | 95 | function testGetNoEmptyAlias() public { 96 | vm.expectRevert("StdChains getChain(string): Chain alias cannot be the empty string."); 97 | getChain(""); 98 | } 99 | 100 | function testChainIdNotFound() public { 101 | vm.expectRevert("StdChains getChain(string): Chain with alias \"no_such_alias\" not found."); 102 | getChain("no_such_alias"); 103 | } 104 | 105 | function testChainAliasNotFound() public { 106 | vm.expectRevert("StdChains getChain(uint256): Chain with ID 321 not found."); 107 | getChain(321); 108 | } 109 | 110 | function testSetChain_ExistingOne() public { 111 | setChain("custom_chain", Chain("Custom Chain", 123456789, "https://custom.chain/")); 112 | assertEq(getChain(123456789).chainId, 123456789); 113 | 114 | setChain("custom_chain", Chain("Modified Chain", 999999999, "https://modified.chain/")); 115 | vm.expectRevert("StdChains getChain(uint256): Chain with ID 123456789 not found."); 116 | getChain(123456789); 117 | 118 | Chain memory modifiedChain = getChain(999999999); 119 | assertEq(modifiedChain.name, "Modified Chain"); 120 | assertEq(modifiedChain.chainId, 999999999); 121 | assertEq(modifiedChain.rpcUrl, "https://modified.chain/"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /contracts/src/L1PassportNFT.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { ERC721 } from "solmate/tokens/ERC721.sol"; 5 | import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; 6 | import { Base64 } from "@openzeppelin/contracts/utils/Base64.sol"; 7 | import { EVMFetchTarget } from './evm-verifier/EVMFetchTarget.sol'; 8 | import { IEVMVerifier } from './evm-verifier/IEVMVerifier.sol'; 9 | import { EVMFetcher } from './evm-verifier/EVMFetcher.sol'; 10 | 11 | contract L1PassportNFT is ERC721, EVMFetchTarget { 12 | using EVMFetcher for EVMFetcher.EVMFetchRequest; 13 | 14 | error TokenIsSoulbound(); 15 | 16 | string public constant SVG_PREFIX = 'OP Goerli Passport'; 17 | 18 | IEVMVerifier public verifier; 19 | address public l2TestCoinAddress; 20 | address public l2TestNFTAddress; 21 | 22 | constructor(string memory _name, string memory _symbol, IEVMVerifier _evmVerifier, address _l2TestCoinAddress, address _l2TestNFTAddress) ERC721(_name, _symbol) { 23 | verifier = _evmVerifier; 24 | l2TestCoinAddress = _l2TestCoinAddress; 25 | l2TestNFTAddress = _l2TestNFTAddress; 26 | } 27 | 28 | function mintTo(address _to) public returns (uint256) { 29 | uint256 newTokenId = uint256(uint160(_to)); 30 | _safeMint(_to, newTokenId); 31 | return newTokenId; 32 | } 33 | 34 | function tokenURI(uint256 id) public view override returns (string memory) { 35 | address tokenOwner = ownerOf(id); 36 | 37 | bytes memory callbackData = abi.encode(id, tokenOwner); 38 | 39 | EVMFetcher.newFetchRequest(verifier, l2TestCoinAddress) 40 | .getStatic(2) // fetch totalSupply of L2TestCoin ERC20 token 41 | .getStatic(3) // fetch balance of owner for L2TestCoin ERC20 token 42 | .element(tokenOwner) 43 | .fetch(this.tokenURIFetchL2TestCoinBalanceCallback.selector, callbackData); 44 | } 45 | 46 | function tokenURIFetchL2TestCoinBalanceCallback(bytes[] memory _values, bytes memory _callbackData) public view returns (string memory) { 47 | uint256 l2TestCoinTotalSupply = abi.decode(_values[0], (uint256)); 48 | uint256 l2TestCoinBalance = abi.decode(_values[1], (uint256)); 49 | 50 | (uint256 tokenId, address tokenOwner) = abi.decode(_callbackData, (uint256, address)); 51 | 52 | bytes memory updatedCallbackData = abi.encode(tokenId, tokenOwner, l2TestCoinTotalSupply, l2TestCoinBalance); 53 | 54 | EVMFetcher.newFetchRequest(verifier, l2TestNFTAddress) 55 | .getStatic(3) // fetch balance of owner for L2TestNFT ERC721 token 56 | .element(tokenOwner) 57 | .fetch(this.tokenURIFetchL2TestNFTBalanceCallback.selector, updatedCallbackData); 58 | } 59 | 60 | function tokenURIFetchL2TestNFTBalanceCallback(bytes[] memory _values, bytes memory _callbackData) public pure returns (string memory) { 61 | (uint256 tokenId, address tokenOwner, uint256 l2TestCoinTotalSupply, uint256 l2TestCoinBalance) = abi.decode(_callbackData, (uint256, address, uint256, uint256)); 62 | 63 | uint256 l2NftBalance = abi.decode(_values[0], (uint256)); 64 | 65 | return _getTokenUri(tokenId, tokenOwner, l2TestCoinTotalSupply, l2TestCoinBalance, l2NftBalance); 66 | } 67 | 68 | // Simple souldbound NFT implementation from https://github.com/The-Arbiter/ERC721Soulbound 69 | function onlySoulbound(address from, address to) internal pure { 70 | // Revert if transfers are not from the 0 address and not to the 0 address 71 | if (from != address(0) && to != address(0)) { 72 | revert TokenIsSoulbound(); 73 | } 74 | } 75 | 76 | function transferFrom(address from, address to, uint256 id) public override { 77 | onlySoulbound(from, to); 78 | super.transferFrom(from, to, id); 79 | } 80 | 81 | function _getTokenUri( 82 | uint256 _tokenId, 83 | address _user, 84 | uint256 _erc20TotalSupply, 85 | uint256 _erc20Balance, 86 | uint256 _erc721Balance 87 | ) internal pure returns (string memory) { 88 | 89 | bytes memory dataURI = abi.encodePacked( 90 | "{", 91 | '"name": "OP Goerli Passport #', 92 | Strings.toString(_tokenId), 93 | '",', 94 | '"description": "OP Goerli Passport",', 95 | '"image": "', 96 | _renderSvg(_user, _erc20TotalSupply, _erc20Balance, _erc721Balance), 97 | '"', 98 | "}" 99 | ); 100 | 101 | return 102 | string( 103 | abi.encodePacked( 104 | "data:application/json;base64,", 105 | Base64.encode(dataURI) 106 | ) 107 | ); 108 | } 109 | 110 | 111 | function _renderSvg( 112 | address _tokenOwner, 113 | uint256 _erc20TotalSupply, 114 | uint256 _erc20Balance, 115 | uint256 _erc721Balance 116 | ) internal pure returns (string memory) { 117 | 118 | bytes memory svg = abi.encodePacked( 119 | SVG_PREFIX, 120 | 'Owns ', 121 | Strings.toString(_erc721Balance), 122 | " L2TestNFT", 123 | 'Owns ', 124 | Strings.toString(_erc20Balance), 125 | "/", 126 | Strings.toString(_erc20TotalSupply), 127 | " L2TestCoin", 128 | '', 129 | Strings.toHexString(_tokenOwner), 130 | "", 131 | "" 132 | ); 133 | 134 | return string( 135 | abi.encodePacked( 136 | "data:image/svg+xml;base64,", 137 | Base64.encode(svg) 138 | ) 139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/StdUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | // TODO Remove import. 5 | import {VmSafe} from "./Vm.sol"; 6 | 7 | abstract contract StdUtils { 8 | VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); 9 | address private constant CONSOLE2_ADDRESS = 0x000000000000000000636F6e736F6c652e6c6f67; 10 | 11 | uint256 private constant INT256_MIN_ABS = 12 | 57896044618658097711785492504343953926634992332820282019728792003956564819968; 13 | uint256 private constant UINT256_MAX = 14 | 115792089237316195423570985008687907853269984665640564039457584007913129639935; 15 | 16 | function _bound(uint256 x, uint256 min, uint256 max) internal pure virtual returns (uint256 result) { 17 | require(min <= max, "StdUtils bound(uint256,uint256,uint256): Max is less than min."); 18 | // If x is between min and max, return x directly. This is to ensure that dictionary values 19 | // do not get shifted if the min is nonzero. More info: https://github.com/foundry-rs/forge-std/issues/188 20 | if (x >= min && x <= max) return x; 21 | 22 | uint256 size = max - min + 1; 23 | 24 | // If the value is 0, 1, 2, 3, warp that to min, min+1, min+2, min+3. Similarly for the UINT256_MAX side. 25 | // This helps ensure coverage of the min/max values. 26 | if (x <= 3 && size > x) return min + x; 27 | if (x >= UINT256_MAX - 3 && size > UINT256_MAX - x) return max - (UINT256_MAX - x); 28 | 29 | // Otherwise, wrap x into the range [min, max], i.e. the range is inclusive. 30 | if (x > max) { 31 | uint256 diff = x - max; 32 | uint256 rem = diff % size; 33 | if (rem == 0) return max; 34 | result = min + rem - 1; 35 | } else if (x < min) { 36 | uint256 diff = min - x; 37 | uint256 rem = diff % size; 38 | if (rem == 0) return min; 39 | result = max - rem + 1; 40 | } 41 | } 42 | 43 | function bound(uint256 x, uint256 min, uint256 max) internal view virtual returns (uint256 result) { 44 | result = _bound(x, min, max); 45 | console2_log("Bound Result", result); 46 | } 47 | 48 | function bound(int256 x, int256 min, int256 max) internal view virtual returns (int256 result) { 49 | require(min <= max, "StdUtils bound(int256,int256,int256): Max is less than min."); 50 | 51 | // Shifting all int256 values to uint256 to use _bound function. The range of two types are: 52 | // int256 : -(2**255) ~ (2**255 - 1) 53 | // uint256: 0 ~ (2**256 - 1) 54 | // So, add 2**255, INT256_MIN_ABS to the integer values. 55 | // 56 | // If the given integer value is -2**255, we cannot use `-uint256(-x)` because of the overflow. 57 | // So, use `~uint256(x) + 1` instead. 58 | uint256 _x = x < 0 ? (INT256_MIN_ABS - ~uint256(x) - 1) : (uint256(x) + INT256_MIN_ABS); 59 | uint256 _min = min < 0 ? (INT256_MIN_ABS - ~uint256(min) - 1) : (uint256(min) + INT256_MIN_ABS); 60 | uint256 _max = max < 0 ? (INT256_MIN_ABS - ~uint256(max) - 1) : (uint256(max) + INT256_MIN_ABS); 61 | 62 | uint256 y = _bound(_x, _min, _max); 63 | 64 | // To move it back to int256 value, subtract INT256_MIN_ABS at here. 65 | result = y < INT256_MIN_ABS ? int256(~(INT256_MIN_ABS - y) + 1) : int256(y - INT256_MIN_ABS); 66 | console2_log("Bound result", vm.toString(result)); 67 | } 68 | 69 | /// @dev Compute the address a contract will be deployed at for a given deployer address and nonce 70 | /// @notice adapated from Solmate implementation (https://github.com/Rari-Capital/solmate/blob/main/src/utils/LibRLP.sol) 71 | function computeCreateAddress(address deployer, uint256 nonce) internal pure virtual returns (address) { 72 | // forgefmt: disable-start 73 | // The integer zero is treated as an empty byte string, and as a result it only has a length prefix, 0x80, computed via 0x80 + 0. 74 | // A one byte integer uses its own value as its length prefix, there is no additional "0x80 + length" prefix that comes before it. 75 | if (nonce == 0x00) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(0x80)))); 76 | if (nonce <= 0x7f) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, uint8(nonce)))); 77 | 78 | // Nonces greater than 1 byte all follow a consistent encoding scheme, where each value is preceded by a prefix of 0x80 + length. 79 | if (nonce <= 2**8 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd7), bytes1(0x94), deployer, bytes1(0x81), uint8(nonce)))); 80 | if (nonce <= 2**16 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd8), bytes1(0x94), deployer, bytes1(0x82), uint16(nonce)))); 81 | if (nonce <= 2**24 - 1) return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xd9), bytes1(0x94), deployer, bytes1(0x83), uint24(nonce)))); 82 | // forgefmt: disable-end 83 | 84 | // More details about RLP encoding can be found here: https://eth.wiki/fundamentals/rlp 85 | // 0xda = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x84 ++ nonce) 86 | // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex) 87 | // 0x84 = 0x80 + 0x04 (0x04 = the bytes length of the nonce, 4 bytes, in hex) 88 | // We assume nobody can have a nonce large enough to require more than 32 bytes. 89 | return addressFromLast20Bytes( 90 | keccak256(abi.encodePacked(bytes1(0xda), bytes1(0x94), deployer, bytes1(0x84), uint32(nonce))) 91 | ); 92 | } 93 | 94 | function computeCreate2Address(bytes32 salt, bytes32 initcodeHash, address deployer) 95 | internal 96 | pure 97 | virtual 98 | returns (address) 99 | { 100 | return addressFromLast20Bytes(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initcodeHash))); 101 | } 102 | 103 | function bytesToUint(bytes memory b) internal pure virtual returns (uint256) { 104 | require(b.length <= 32, "StdUtils bytesToUint(bytes): Bytes length exceeds 32."); 105 | return abi.decode(abi.encodePacked(new bytes(32 - b.length), b), (uint256)); 106 | } 107 | 108 | function addressFromLast20Bytes(bytes32 bytesValue) private pure returns (address) { 109 | return address(uint160(uint256(bytesValue))); 110 | } 111 | 112 | // Used to prevent the compilation of console, which shortens the compilation time when console is not used elsewhere. 113 | 114 | function console2_log(string memory p0, uint256 p1) private view { 115 | (bool status,) = address(CONSOLE2_ADDRESS).staticcall(abi.encodeWithSignature("log(string,uint256)", p0, p1)); 116 | status; 117 | } 118 | 119 | function console2_log(string memory p0, string memory p1) private view { 120 | (bool status,) = address(CONSOLE2_ADDRESS).staticcall(abi.encodeWithSignature("log(string,string)", p0, p1)); 121 | status; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /contracts/src/evm-verifier/EVMProofHelper.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {RLPReader} from "@eth-optimism/contracts-bedrock/src/libraries/rlp/RLPReader.sol"; 5 | import {Bytes} from "@eth-optimism/contracts-bedrock/src/libraries/Bytes.sol"; 6 | import {SecureMerkleTrie} from "./SecureMerkleTrie.sol"; 7 | 8 | struct StateProof { 9 | bytes[] stateTrieWitness; // Witness proving the `storageRoot` against a state root. 10 | bytes[][] storageProofs; // An array of proofs of individual storage elements 11 | } 12 | 13 | uint8 constant OP_CONSTANT = 0x00; 14 | uint8 constant OP_BACKREF = 0x20; 15 | uint8 constant FLAG_DYNAMIC = 0x01; 16 | 17 | library EVMProofHelper { 18 | using Bytes for bytes; 19 | 20 | error AccountNotFound(address); 21 | error UnknownOpcode(uint8); 22 | error InvalidSlotSize(uint256 size); 23 | 24 | /** 25 | * @notice Get the storage root for the provided merkle proof 26 | * @param stateRoot The state root the witness was generated against 27 | * @param target The address we are fetching a storage root for 28 | * @param witness A witness proving the value of the storage root for `target`. 29 | * @return The storage root retrieved from the provided state root 30 | */ 31 | function getStorageRoot(bytes32 stateRoot, address target, bytes[] memory witness) private pure returns (bytes32) { 32 | (bool exists, bytes memory encodedResolverAccount) = SecureMerkleTrie.get( 33 | abi.encodePacked(target), 34 | witness, 35 | stateRoot 36 | ); 37 | if(!exists) { 38 | revert AccountNotFound(target); 39 | } 40 | RLPReader.RLPItem[] memory accountState = RLPReader.readList(encodedResolverAccount); 41 | return bytes32(RLPReader.readBytes(accountState[2])); 42 | } 43 | 44 | /** 45 | * @notice Prove whether the provided storage slot is part of the storageRoot 46 | * @param storageRoot the storage root for the account that contains the storage slot 47 | * @param slot The storage key we are fetching the value of 48 | * @param witness the StorageProof struct containing the necessary proof data 49 | * @return The retrieved storage proof value or 0x if the storage slot is empty 50 | */ 51 | function getSingleStorageProof(bytes32 storageRoot, uint256 slot, bytes[] memory witness) private pure returns (bytes memory) { 52 | (bool exists, bytes memory retrievedValue) = SecureMerkleTrie.get( 53 | abi.encodePacked(slot), 54 | witness, 55 | storageRoot 56 | ); 57 | if(!exists) { 58 | // Nonexistent values are treated as zero. 59 | return ""; 60 | } 61 | return RLPReader.readBytes(retrievedValue); 62 | } 63 | 64 | function getFixedValue(bytes32 storageRoot, uint256 slot, bytes[] memory witness) private pure returns(bytes32) { 65 | bytes memory value = getSingleStorageProof(storageRoot, slot, witness); 66 | // RLP encoded storage slots are stored without leading 0 bytes. 67 | // Casting to bytes32 appends trailing 0 bytes, so we have to bit shift to get the 68 | // original fixed-length representation back. 69 | return bytes32(value) >> (256 - 8 * value.length); 70 | } 71 | 72 | function executeOperation(bytes1 operation, bytes[] memory constants, bytes[] memory values) private pure returns(bytes memory) { 73 | uint8 opcode = uint8(operation) & 0xe0; 74 | uint8 operand = uint8(operation) & 0x1f; 75 | 76 | if(opcode == OP_CONSTANT) { 77 | return constants[operand]; 78 | } else if(opcode == OP_BACKREF) { 79 | return values[operand]; 80 | } else { 81 | revert UnknownOpcode(opcode); 82 | } 83 | } 84 | 85 | function computeFirstSlot(bytes32 command, bytes[] memory constants, bytes[] memory values) private pure returns(bool isDynamic, uint256 slot) { 86 | uint8 flags = uint8(command[0]); 87 | isDynamic = (flags & FLAG_DYNAMIC) != 0; 88 | 89 | bytes memory slotData = executeOperation(command[1], constants, values); 90 | require(slotData.length == 32, "First path element must be 32 bytes"); 91 | slot = uint256(bytes32(slotData)); 92 | 93 | for(uint256 j = 2; j < 32 && command[j] != 0xff; j++) { 94 | bytes memory index = executeOperation(command[j], constants, values); 95 | slot = uint256(keccak256(abi.encodePacked(index, slot))); 96 | } 97 | } 98 | 99 | function getDynamicValue(bytes32 storageRoot, uint256 slot, StateProof memory proof, uint256 proofIdx) private pure returns(bytes memory value, uint256 newProofIdx) { 100 | uint256 firstValue = uint256(getFixedValue(storageRoot, slot, proof.storageProofs[proofIdx++])); 101 | if(firstValue & 0x01 == 0x01) { 102 | // Long value: first slot is `length * 2 + 1`, following slots are data. 103 | uint256 length = (firstValue - 1) / 2; 104 | value = ""; 105 | slot = uint256(keccak256(abi.encodePacked(slot))); 106 | // This is horribly inefficient - O(n^2). A better approach would be to build an array of words and concatenate them 107 | // all at once, but we're trying to avoid writing new library code. 108 | while(length > 0) { 109 | if(length < 32) { 110 | value = bytes.concat(value, getSingleStorageProof(storageRoot, slot++, proof.storageProofs[proofIdx++]).slice(0, length)); 111 | length = 0; 112 | } else { 113 | value = bytes.concat(value, getSingleStorageProof(storageRoot, slot++, proof.storageProofs[proofIdx++])); 114 | length -= 32; 115 | } 116 | } 117 | return (value, proofIdx); 118 | } else { 119 | // Short value: least significant byte is `length * 2`, other bytes are data. 120 | uint256 length = (firstValue & 0xFF) / 2; 121 | return (abi.encode(firstValue).slice(0, length), proofIdx); 122 | } 123 | } 124 | 125 | function getStorageValues(address target, bytes32[] memory commands, bytes[] memory constants, bytes32 stateRoot, StateProof memory proof) internal pure returns(bytes[] memory values) { 126 | bytes32 storageRoot = getStorageRoot(stateRoot, target, proof.stateTrieWitness); 127 | uint256 proofIdx = 0; 128 | values = new bytes[](commands.length); 129 | for(uint256 i = 0; i < commands.length; i++) { 130 | bytes32 command = commands[i]; 131 | (bool isDynamic, uint256 slot) = computeFirstSlot(command, constants, values); 132 | if(!isDynamic) { 133 | values[i] = abi.encode(getFixedValue(storageRoot, slot, proof.storageProofs[proofIdx++])); 134 | if(values[i].length > 32) { 135 | revert InvalidSlotSize(values[i].length); 136 | } 137 | } else { 138 | (values[i], proofIdx) = getDynamicValue(storageRoot, slot, proof, proofIdx); 139 | } 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/StdJson.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.0 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import {VmSafe} from "./Vm.sol"; 7 | 8 | // Helpers for parsing and writing JSON files 9 | // To parse: 10 | // ``` 11 | // using stdJson for string; 12 | // string memory json = vm.readFile("some_peth"); 13 | // json.parseUint(""); 14 | // ``` 15 | // To write: 16 | // ``` 17 | // using stdJson for string; 18 | // string memory json = "deploymentArtifact"; 19 | // Contract contract = new Contract(); 20 | // json.serialize("contractAddress", address(contract)); 21 | // json = json.serialize("deploymentTimes", uint(1)); 22 | // // store the stringified JSON to the 'json' variable we have been using as a key 23 | // // as we won't need it any longer 24 | // string memory json2 = "finalArtifact"; 25 | // string memory final = json2.serialize("depArtifact", json); 26 | // final.write(""); 27 | // ``` 28 | 29 | library stdJson { 30 | VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); 31 | 32 | function parseRaw(string memory json, string memory key) internal pure returns (bytes memory) { 33 | return vm.parseJson(json, key); 34 | } 35 | 36 | function readUint(string memory json, string memory key) internal pure returns (uint256) { 37 | return abi.decode(vm.parseJson(json, key), (uint256)); 38 | } 39 | 40 | function readUintArray(string memory json, string memory key) internal pure returns (uint256[] memory) { 41 | return abi.decode(vm.parseJson(json, key), (uint256[])); 42 | } 43 | 44 | function readInt(string memory json, string memory key) internal pure returns (int256) { 45 | return abi.decode(vm.parseJson(json, key), (int256)); 46 | } 47 | 48 | function readIntArray(string memory json, string memory key) internal pure returns (int256[] memory) { 49 | return abi.decode(vm.parseJson(json, key), (int256[])); 50 | } 51 | 52 | function readBytes32(string memory json, string memory key) internal pure returns (bytes32) { 53 | return abi.decode(vm.parseJson(json, key), (bytes32)); 54 | } 55 | 56 | function readBytes32Array(string memory json, string memory key) internal pure returns (bytes32[] memory) { 57 | return abi.decode(vm.parseJson(json, key), (bytes32[])); 58 | } 59 | 60 | function readString(string memory json, string memory key) internal pure returns (string memory) { 61 | return abi.decode(vm.parseJson(json, key), (string)); 62 | } 63 | 64 | function readStringArray(string memory json, string memory key) internal pure returns (string[] memory) { 65 | return abi.decode(vm.parseJson(json, key), (string[])); 66 | } 67 | 68 | function readAddress(string memory json, string memory key) internal pure returns (address) { 69 | return abi.decode(vm.parseJson(json, key), (address)); 70 | } 71 | 72 | function readAddressArray(string memory json, string memory key) internal pure returns (address[] memory) { 73 | return abi.decode(vm.parseJson(json, key), (address[])); 74 | } 75 | 76 | function readBool(string memory json, string memory key) internal pure returns (bool) { 77 | return abi.decode(vm.parseJson(json, key), (bool)); 78 | } 79 | 80 | function readBoolArray(string memory json, string memory key) internal pure returns (bool[] memory) { 81 | return abi.decode(vm.parseJson(json, key), (bool[])); 82 | } 83 | 84 | function readBytes(string memory json, string memory key) internal pure returns (bytes memory) { 85 | return abi.decode(vm.parseJson(json, key), (bytes)); 86 | } 87 | 88 | function readBytesArray(string memory json, string memory key) internal pure returns (bytes[] memory) { 89 | return abi.decode(vm.parseJson(json, key), (bytes[])); 90 | } 91 | 92 | function serialize(string memory jsonKey, string memory key, bool value) internal returns (string memory) { 93 | return vm.serializeBool(jsonKey, key, value); 94 | } 95 | 96 | function serialize(string memory jsonKey, string memory key, bool[] memory value) 97 | internal 98 | returns (string memory) 99 | { 100 | return vm.serializeBool(jsonKey, key, value); 101 | } 102 | 103 | function serialize(string memory jsonKey, string memory key, uint256 value) internal returns (string memory) { 104 | return vm.serializeUint(jsonKey, key, value); 105 | } 106 | 107 | function serialize(string memory jsonKey, string memory key, uint256[] memory value) 108 | internal 109 | returns (string memory) 110 | { 111 | return vm.serializeUint(jsonKey, key, value); 112 | } 113 | 114 | function serialize(string memory jsonKey, string memory key, int256 value) internal returns (string memory) { 115 | return vm.serializeInt(jsonKey, key, value); 116 | } 117 | 118 | function serialize(string memory jsonKey, string memory key, int256[] memory value) 119 | internal 120 | returns (string memory) 121 | { 122 | return vm.serializeInt(jsonKey, key, value); 123 | } 124 | 125 | function serialize(string memory jsonKey, string memory key, address value) internal returns (string memory) { 126 | return vm.serializeAddress(jsonKey, key, value); 127 | } 128 | 129 | function serialize(string memory jsonKey, string memory key, address[] memory value) 130 | internal 131 | returns (string memory) 132 | { 133 | return vm.serializeAddress(jsonKey, key, value); 134 | } 135 | 136 | function serialize(string memory jsonKey, string memory key, bytes32 value) internal returns (string memory) { 137 | return vm.serializeBytes32(jsonKey, key, value); 138 | } 139 | 140 | function serialize(string memory jsonKey, string memory key, bytes32[] memory value) 141 | internal 142 | returns (string memory) 143 | { 144 | return vm.serializeBytes32(jsonKey, key, value); 145 | } 146 | 147 | function serialize(string memory jsonKey, string memory key, bytes memory value) internal returns (string memory) { 148 | return vm.serializeBytes(jsonKey, key, value); 149 | } 150 | 151 | function serialize(string memory jsonKey, string memory key, bytes[] memory value) 152 | internal 153 | returns (string memory) 154 | { 155 | return vm.serializeBytes(jsonKey, key, value); 156 | } 157 | 158 | function serialize(string memory jsonKey, string memory key, string memory value) 159 | internal 160 | returns (string memory) 161 | { 162 | return vm.serializeString(jsonKey, key, value); 163 | } 164 | 165 | function serialize(string memory jsonKey, string memory key, string[] memory value) 166 | internal 167 | returns (string memory) 168 | { 169 | return vm.serializeString(jsonKey, key, value); 170 | } 171 | 172 | function write(string memory jsonKey, string memory path) internal { 173 | vm.writeJson(jsonKey, path); 174 | } 175 | 176 | function write(string memory jsonKey, string memory path, string memory valueKey) internal { 177 | vm.writeJson(jsonKey, path, valueKey); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OP-Goerli Passport NFT (evmgateway example app) 2 | 3 | Screenshot 2023-11-07 at 11 52 37 PM 4 | 5 | 6 | This is an example app that uses Ethereum Name Service (ENS)'s [evmgateway](https://github.com/ensdomains/evmgateway). 7 | 8 | Check it out at https://evmgateway-starter.vercel.app/ 9 | 10 | ## What is OP-Goerli Passport NFT 11 | 12 | The Passport NFT is as non-transferrable NFT on L1 (Goerli) that dynamically updates based on some of the owner's actions on L2 (OP Goerli). The NFT's `tokenURI` function performs two subsequent CCIP-Reads, and returns a svg string that encodes the read results. 13 | 14 | ## What is [evmgateway](https://github.com/ensdomains/evmgateway)? 15 | 16 | EVM Gateway is a [CCIP-Read](https://eips.ethereum.org/EIPS/eip-3668) gateway that allows L1 smart contracts to fetch and verify state from L2s. Read more about it [here](https://github.com/ensdomains/evmgateway#evm-ccip-read-gateway). 17 | 18 | ## Who is this for? 19 | 20 | - Hackers hacking on [evmgateway](https://github.com/ensdomains/evmgateway) 21 | - Hackers interested in learning more about how an OP Stack chain works 22 | - Hackers interested in learning more about how CCIP-Read works 23 | 24 | ## How does it work? 25 | 26 | Check out the contract [here](https://github.com/ethereum-optimism/evmgateway-starter/blob/main/contracts/src/L1PassportNFT.sol) 27 | 28 | - `L2TestCoin` is deployed on OP Goerli 29 | - `L2TestNFT` is deployed on OP Goerli 30 | - `L1PassportNFT` is deployed on Goerli 31 | 32 | 1. `tokenURI` function on the `L1PassportNFT` contract performs a CCIP-Read on the `L2TestCoin`'s `totalSupply` and `balanceOf` storage slots for the current owner of the NFT. 33 | 2. When the CCIP-Read in step 1 succeeds, `L1PassportNFT`'s `tokenURIFetchL2TestCoinBalanceCallback` is called 34 | 3. `tokenURIFetchL2TestCoinBalanceCallback` performs another CCIP-Read on `L2TestNFT`'s `_balanceOf` 35 | 4. When the CCIP-Read in step 3 succeeds, `L1PassportNFT`'s `tokenURIFetchL2TestNFTBalanceCallback` is called 36 | 5. `tokenURIFetchL2TestNFTBalanceCallback` takes the fetch results from the last 2 CCIP-Read calls, and then generates an svg string that displays the user's `L2TestCoin` and `L2TestNFT` balance. 37 | 38 | ## Deployments 39 | 40 | ### OP-Gateway and OP-Verifier deployments 41 | 42 | The following are deployed versions of the [op-gateway](https://github.com/ensdomains/evmgateway/tree/main/op-gateway) with `delay = 0` and their corresponding [op-verifier](https://github.com/ensdomains/evmgateway/tree/main/op-verifier) contracts 43 | 44 | | **chain** | **op-gateway service** | op-verifier contract (on Goerli) | 45 | | ----------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | 46 | | OP Goerli | https://op-goerli.op-gateway.transientdomain.xyz/{sender}/{data}.json | [0xe58448bfc2fa097953e800e0af0b0a5257ecc4b1](https://goerli.etherscan.io/address/0xe58448bfc2fa097953e800e0af0b0a5257ecc4b1) | 47 | | Base Goerli | https://base-goerli.op-gateway.transientdomain.xyz/{sender}/{data}.json | [0x7e2f9c4a1467e8a41e1e8283ba3ba72e3d92f6b8](https://goerli.etherscan.io/address/0x7e2f9c4a1467e8a41e1e8283ba3ba72e3d92f6b8) | 48 | 49 | ### Contract deployments 50 | 51 | | **contract** | **chain** | **address** | 52 | | ------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------- | 53 | | L1PassportNFT | Goerli | [0x0e24f4af1d5cd7fac0a96649511a15439d7e0c04](https://goerli.etherscan.io/address/0x0e24f4af1d5cd7fac0a96649511a15439d7e0c04) | 54 | | L2TestNFT | OP Goerli | [0x22299910e573ecd436f4987c75f093894904d107](https://goerli-optimism.etherscan.io/address/0x22299910e573ecd436f4987c75f093894904d107) | 55 | | L2TestCoin | OP Goerli | [0x5a81f1f4d30f4153150848c31fabd0311946ed72](https://goerli-optimism.etherscan.io/address/0x5a81f1f4d30f4153150848c31fabd0311946ed72) | 56 | 57 | ## Local development 58 | 59 | ### Set up environment 60 | 61 | #### Fork the repo 62 | 63 | ```sh 64 | git clone https://github.com/ethereum-optimism/evmgateway-starter.git 65 | ``` 66 | 67 | ```sh 68 | cd evmgateway-starter 69 | ``` 70 | 71 | 72 | #### Specify .env 73 | 74 | 1. Copy `.env.example` to `.env`. 75 | 76 | ```sh 77 | cp .env.example .env 78 | ``` 79 | 80 | 2. Edit your `.env` to specify the environment variables. 81 | 82 | - `VITE_RPC_URL_GOERLI`: HTTP RPC URL for Goerli 83 | 84 | - `VITE_RPC_URL_OP_GOERLI`: HTTP RPC URL for OP-Goerli 85 | 86 | - `VITE_WALLETCONNECT_PROJECT_ID`: WalletConnect v2 requires a project ID. You can obtain it from your WC dashboard: https://cloud.walletconnect.com/sign-in 87 | 88 | #### Start the application 89 | 90 | 1. Install the necessary node packages: 91 | 92 | ```sh 93 | npm install 94 | ``` 95 | 96 | 2. Start the frontend with `npm run dev` 97 | 98 | ```sh 99 | npm run dev 100 | ``` 101 | 102 | 3. Open [localhost:5173](http://localhost:5173) in your browser. 103 | 104 | ### Deploying Contracts 105 | 106 | #### Devnet vs. Testnet 107 | 108 | Using a testnet is recommended. You can use the existing deployment for `op-verifier` contract and the `op-gateway` service on Goerli (listed above). 109 | 110 | For a local development environment, you will need to do the following 111 | 112 | - run an [Optimism Devnet](https://community.optimism.io/docs/developers/build/dev-node/) 113 | - run an [op-gateway service](https://github.com/ensdomains/evmgateway/tree/main/op-gateway) against the devnet 114 | - deploy [op-verifier](https://github.com/ensdomains/evmgateway/tree/main/op-verifier) contract on the devnet that uses the op-gateway service 115 | 116 | #### Install Foundry 117 | 118 | You will need to install [Foundry](https://book.getfoundry.sh/getting-started/installation) to build your smart contracts. 119 | 120 | 1. Run the following command: 121 | 122 | ```sh 123 | curl -L https://foundry.paradigm.xyz | bash 124 | ``` 125 | 126 | 1. Source your environment as requested by Foundry. 127 | 128 | 1. Run `foundryup`. 129 | 130 | #### Get an Etherscan key 131 | 132 | 1. Register for [Etherscan](https://explorer.optimism.io/register). 133 | Add the api key to `ETHERSCAN_API_KEY_GOERLI` in your `.env` file 134 | 135 | 2. Register for [Etherscan on Optimism](https://explorer.optimism.io/register). 136 | This account is different from your normal Etherscan account. Add this api key to `ETHERSCAN_API_KEY_OP_GOERLI` in your `.env` file 137 | 138 | #### Deploy contracts 139 | 140 | 1. Deploy the `L2TestCoin` contract on OP Goerli 141 | 142 | ```sh 143 | npm run deploy:l2-test-coin 144 | ``` 145 | 146 | 1. Deploy the `L2TestNFT` contract on OP Goerli 147 | 148 | ```sh 149 | npm run deploy:l2-test-nft 150 | ``` 151 | 152 | 1. Update the `VITE_L2TESTNFT_CONTRACT_ADDRESS_OP_GOERLI` and `VITE_L2TESTCOIN_CONTRACT_ADDRESS_OP_GOERLI` with the addresses of the newly deployed contracts. This will be used as inputs for the `L1PassportNFT` contract 153 | 154 | 1. Deploy the `L1PassportNFT` contract on Goerli 155 | ```sh 156 | npm run deploy:l1-passport-nft 157 | ``` 158 | 159 | 160 | ## Built using 161 | - [thorin (ENS Design System)](https://thorin.ens.domains/) 162 | - [wagmi](https://wagmi.sh/) 163 | - [rainbowkit](https://www.rainbowkit.com/) 164 | - [vite](https://vitejs.dev/) 165 | - [foundry](https://getfoundry.sh/) 166 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/interfaces/IERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2; 3 | 4 | import "./IERC165.sol"; 5 | 6 | /// @title ERC-1155 Multi Token Standard 7 | /// @dev See https://eips.ethereum.org/EIPS/eip-1155 8 | /// Note: The ERC-165 identifier for this interface is 0xd9b67a26. 9 | interface IERC1155 is IERC165 { 10 | /// @dev 11 | /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). 12 | /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). 13 | /// - The `_from` argument MUST be the address of the holder whose balance is decreased. 14 | /// - The `_to` argument MUST be the address of the recipient whose balance is increased. 15 | /// - The `_id` argument MUST be the token type being transferred. 16 | /// - The `_value` argument MUST be the number of tokens the holder balance is decreased by and match what the recipient balance is increased by. 17 | /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). 18 | /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). 19 | event TransferSingle( 20 | address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value 21 | ); 22 | 23 | /// @dev 24 | /// - Either `TransferSingle` or `TransferBatch` MUST emit when tokens are transferred, including zero value transfers as well as minting or burning (see "Safe Transfer Rules" section of the standard). 25 | /// - The `_operator` argument MUST be the address of an account/contract that is approved to make the transfer (SHOULD be msg.sender). 26 | /// - The `_from` argument MUST be the address of the holder whose balance is decreased. 27 | /// - The `_to` argument MUST be the address of the recipient whose balance is increased. 28 | /// - The `_ids` argument MUST be the list of tokens being transferred. 29 | /// - The `_values` argument MUST be the list of number of tokens (matching the list and order of tokens specified in _ids) the holder balance is decreased by and match what the recipient balance is increased by. 30 | /// - When minting/creating tokens, the `_from` argument MUST be set to `0x0` (i.e. zero address). 31 | /// - When burning/destroying tokens, the `_to` argument MUST be set to `0x0` (i.e. zero address). 32 | event TransferBatch( 33 | address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values 34 | ); 35 | 36 | /// @dev MUST emit when approval for a second party/operator address to manage all tokens for an owner address is enabled or disabled (absence of an event assumes disabled). 37 | event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); 38 | 39 | /// @dev MUST emit when the URI is updated for a token ID. URIs are defined in RFC 3986. 40 | /// The URI MUST point to a JSON file that conforms to the "ERC-1155 Metadata URI JSON Schema". 41 | event URI(string _value, uint256 indexed _id); 42 | 43 | /// @notice Transfers `_value` amount of an `_id` from the `_from` address to the `_to` address specified (with safety call). 44 | /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). 45 | /// - MUST revert if `_to` is the zero address. 46 | /// - MUST revert if balance of holder for token `_id` is lower than the `_value` sent. 47 | /// - MUST revert on any other error. 48 | /// - MUST emit the `TransferSingle` event to reflect the balance change (see "Safe Transfer Rules" section of the standard). 49 | /// - After the above conditions are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call `onERC1155Received` on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). 50 | /// @param _from Source address 51 | /// @param _to Target address 52 | /// @param _id ID of the token type 53 | /// @param _value Transfer amount 54 | /// @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` 55 | function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external; 56 | 57 | /// @notice Transfers `_values` amount(s) of `_ids` from the `_from` address to the `_to` address specified (with safety call). 58 | /// @dev Caller must be approved to manage the tokens being transferred out of the `_from` account (see "Approval" section of the standard). 59 | /// - MUST revert if `_to` is the zero address. 60 | /// - MUST revert if length of `_ids` is not the same as length of `_values`. 61 | /// - MUST revert if any of the balance(s) of the holder(s) for token(s) in `_ids` is lower than the respective amount(s) in `_values` sent to the recipient. 62 | /// - MUST revert on any other error. 63 | /// - MUST emit `TransferSingle` or `TransferBatch` event(s) such that all the balance changes are reflected (see "Safe Transfer Rules" section of the standard). 64 | /// - Balance changes and events MUST follow the ordering of the arrays (_ids[0]/_values[0] before _ids[1]/_values[1], etc). 65 | /// - After the above conditions for the transfer(s) in the batch are met, this function MUST check if `_to` is a smart contract (e.g. code size > 0). If so, it MUST call the relevant `ERC1155TokenReceiver` hook(s) on `_to` and act appropriately (see "Safe Transfer Rules" section of the standard). 66 | /// @param _from Source address 67 | /// @param _to Target address 68 | /// @param _ids IDs of each token type (order and length must match _values array) 69 | /// @param _values Transfer amounts per token type (order and length must match _ids array) 70 | /// @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to` 71 | function safeBatchTransferFrom( 72 | address _from, 73 | address _to, 74 | uint256[] calldata _ids, 75 | uint256[] calldata _values, 76 | bytes calldata _data 77 | ) external; 78 | 79 | /// @notice Get the balance of an account's tokens. 80 | /// @param _owner The address of the token holder 81 | /// @param _id ID of the token 82 | /// @return The _owner's balance of the token type requested 83 | function balanceOf(address _owner, uint256 _id) external view returns (uint256); 84 | 85 | /// @notice Get the balance of multiple account/token pairs 86 | /// @param _owners The addresses of the token holders 87 | /// @param _ids ID of the tokens 88 | /// @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair) 89 | function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) 90 | external 91 | view 92 | returns (uint256[] memory); 93 | 94 | /// @notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens. 95 | /// @dev MUST emit the ApprovalForAll event on success. 96 | /// @param _operator Address to add to the set of authorized operators 97 | /// @param _approved True if the operator is approved, false to revoke approval 98 | function setApprovalForAll(address _operator, bool _approved) external; 99 | 100 | /// @notice Queries the approval status of an operator for a given owner. 101 | /// @param _owner The owner of the tokens 102 | /// @param _operator Address of authorized operator 103 | /// @return True if the operator is approved, false if not 104 | function isApprovedForAll(address _owner, address _operator) external view returns (bool); 105 | } 106 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/lib/ds-test/demo/demo.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0-or-later 2 | pragma solidity >=0.5.0; 3 | 4 | import "../src/test.sol"; 5 | 6 | contract DemoTest is DSTest { 7 | function test_this() public pure { 8 | require(true); 9 | } 10 | function test_logs() public { 11 | emit log("-- log(string)"); 12 | emit log("a string"); 13 | 14 | emit log("-- log_named_uint(string, uint)"); 15 | emit log_named_uint("uint", 512); 16 | 17 | emit log("-- log_named_int(string, int)"); 18 | emit log_named_int("int", -512); 19 | 20 | emit log("-- log_named_address(string, address)"); 21 | emit log_named_address("address", address(this)); 22 | 23 | emit log("-- log_named_bytes32(string, bytes32)"); 24 | emit log_named_bytes32("bytes32", "a string"); 25 | 26 | emit log("-- log_named_bytes(string, bytes)"); 27 | emit log_named_bytes("bytes", hex"cafefe"); 28 | 29 | emit log("-- log_named_string(string, string)"); 30 | emit log_named_string("string", "a string"); 31 | 32 | emit log("-- log_named_decimal_uint(string, uint, uint)"); 33 | emit log_named_decimal_uint("decimal uint", 1.0e18, 18); 34 | 35 | emit log("-- log_named_decimal_int(string, int, uint)"); 36 | emit log_named_decimal_int("decimal int", -1.0e18, 18); 37 | } 38 | event log_old_named_uint(bytes32,uint); 39 | function test_old_logs() public { 40 | emit log_old_named_uint("key", 500); 41 | emit log_named_bytes32("bkey", "val"); 42 | } 43 | function test_trace() public view { 44 | this.echo("string 1", "string 2"); 45 | } 46 | function test_multiline() public { 47 | emit log("a multiline\\nstring"); 48 | emit log("a multiline string"); 49 | emit log_bytes("a string"); 50 | emit log_bytes("a multiline\nstring"); 51 | emit log_bytes("a multiline\\nstring"); 52 | emit logs(hex"0000"); 53 | emit log_named_bytes("0x0000", hex"0000"); 54 | emit logs(hex"ff"); 55 | } 56 | function echo(string memory s1, string memory s2) public pure 57 | returns (string memory, string memory) 58 | { 59 | return (s1, s2); 60 | } 61 | 62 | function prove_this(uint x) public { 63 | emit log_named_uint("sym x", x); 64 | assertGt(x + 1, 0); 65 | } 66 | 67 | function test_logn() public { 68 | assembly { 69 | log0(0x01, 0x02) 70 | log1(0x01, 0x02, 0x03) 71 | log2(0x01, 0x02, 0x03, 0x04) 72 | log3(0x01, 0x02, 0x03, 0x04, 0x05) 73 | } 74 | } 75 | 76 | event MyEvent(uint, uint indexed, uint, uint indexed); 77 | function test_events() public { 78 | emit MyEvent(1, 2, 3, 4); 79 | } 80 | 81 | function test_asserts() public { 82 | string memory err = "this test has failed!"; 83 | emit log("## assertTrue(bool)\n"); 84 | assertTrue(false); 85 | emit log("\n"); 86 | assertTrue(false, err); 87 | 88 | emit log("\n## assertEq(address,address)\n"); 89 | assertEq(address(this), msg.sender); 90 | emit log("\n"); 91 | assertEq(address(this), msg.sender, err); 92 | 93 | emit log("\n## assertEq32(bytes32,bytes32)\n"); 94 | assertEq32("bytes 1", "bytes 2"); 95 | emit log("\n"); 96 | assertEq32("bytes 1", "bytes 2", err); 97 | 98 | emit log("\n## assertEq(bytes32,bytes32)\n"); 99 | assertEq32("bytes 1", "bytes 2"); 100 | emit log("\n"); 101 | assertEq32("bytes 1", "bytes 2", err); 102 | 103 | emit log("\n## assertEq(uint,uint)\n"); 104 | assertEq(uint(0), 1); 105 | emit log("\n"); 106 | assertEq(uint(0), 1, err); 107 | 108 | emit log("\n## assertEq(int,int)\n"); 109 | assertEq(-1, -2); 110 | emit log("\n"); 111 | assertEq(-1, -2, err); 112 | 113 | emit log("\n## assertEqDecimal(int,int,uint)\n"); 114 | assertEqDecimal(-1.0e18, -1.1e18, 18); 115 | emit log("\n"); 116 | assertEqDecimal(-1.0e18, -1.1e18, 18, err); 117 | 118 | emit log("\n## assertEqDecimal(uint,uint,uint)\n"); 119 | assertEqDecimal(uint(1.0e18), 1.1e18, 18); 120 | emit log("\n"); 121 | assertEqDecimal(uint(1.0e18), 1.1e18, 18, err); 122 | 123 | emit log("\n## assertGt(uint,uint)\n"); 124 | assertGt(uint(0), 0); 125 | emit log("\n"); 126 | assertGt(uint(0), 0, err); 127 | 128 | emit log("\n## assertGt(int,int)\n"); 129 | assertGt(-1, -1); 130 | emit log("\n"); 131 | assertGt(-1, -1, err); 132 | 133 | emit log("\n## assertGtDecimal(int,int,uint)\n"); 134 | assertGtDecimal(-2.0e18, -1.1e18, 18); 135 | emit log("\n"); 136 | assertGtDecimal(-2.0e18, -1.1e18, 18, err); 137 | 138 | emit log("\n## assertGtDecimal(uint,uint,uint)\n"); 139 | assertGtDecimal(uint(1.0e18), 1.1e18, 18); 140 | emit log("\n"); 141 | assertGtDecimal(uint(1.0e18), 1.1e18, 18, err); 142 | 143 | emit log("\n## assertGe(uint,uint)\n"); 144 | assertGe(uint(0), 1); 145 | emit log("\n"); 146 | assertGe(uint(0), 1, err); 147 | 148 | emit log("\n## assertGe(int,int)\n"); 149 | assertGe(-1, 0); 150 | emit log("\n"); 151 | assertGe(-1, 0, err); 152 | 153 | emit log("\n## assertGeDecimal(int,int,uint)\n"); 154 | assertGeDecimal(-2.0e18, -1.1e18, 18); 155 | emit log("\n"); 156 | assertGeDecimal(-2.0e18, -1.1e18, 18, err); 157 | 158 | emit log("\n## assertGeDecimal(uint,uint,uint)\n"); 159 | assertGeDecimal(uint(1.0e18), 1.1e18, 18); 160 | emit log("\n"); 161 | assertGeDecimal(uint(1.0e18), 1.1e18, 18, err); 162 | 163 | emit log("\n## assertLt(uint,uint)\n"); 164 | assertLt(uint(0), 0); 165 | emit log("\n"); 166 | assertLt(uint(0), 0, err); 167 | 168 | emit log("\n## assertLt(int,int)\n"); 169 | assertLt(-1, -1); 170 | emit log("\n"); 171 | assertLt(-1, -1, err); 172 | 173 | emit log("\n## assertLtDecimal(int,int,uint)\n"); 174 | assertLtDecimal(-1.0e18, -1.1e18, 18); 175 | emit log("\n"); 176 | assertLtDecimal(-1.0e18, -1.1e18, 18, err); 177 | 178 | emit log("\n## assertLtDecimal(uint,uint,uint)\n"); 179 | assertLtDecimal(uint(2.0e18), 1.1e18, 18); 180 | emit log("\n"); 181 | assertLtDecimal(uint(2.0e18), 1.1e18, 18, err); 182 | 183 | emit log("\n## assertLe(uint,uint)\n"); 184 | assertLe(uint(1), 0); 185 | emit log("\n"); 186 | assertLe(uint(1), 0, err); 187 | 188 | emit log("\n## assertLe(int,int)\n"); 189 | assertLe(0, -1); 190 | emit log("\n"); 191 | assertLe(0, -1, err); 192 | 193 | emit log("\n## assertLeDecimal(int,int,uint)\n"); 194 | assertLeDecimal(-1.0e18, -1.1e18, 18); 195 | emit log("\n"); 196 | assertLeDecimal(-1.0e18, -1.1e18, 18, err); 197 | 198 | emit log("\n## assertLeDecimal(uint,uint,uint)\n"); 199 | assertLeDecimal(uint(2.0e18), 1.1e18, 18); 200 | emit log("\n"); 201 | assertLeDecimal(uint(2.0e18), 1.1e18, 18, err); 202 | 203 | emit log("\n## assertEq(string,string)\n"); 204 | string memory s1 = "string 1"; 205 | string memory s2 = "string 2"; 206 | assertEq(s1, s2); 207 | emit log("\n"); 208 | assertEq(s1, s2, err); 209 | 210 | emit log("\n## assertEq0(bytes,bytes)\n"); 211 | assertEq0(hex"abcdef01", hex"abcdef02"); 212 | emit log("\n"); 213 | assertEq0(hex"abcdef01", hex"abcdef02", err); 214 | } 215 | } 216 | 217 | contract DemoTestWithSetUp { 218 | function setUp() public { 219 | } 220 | function test_pass() public pure { 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/constants/L2TestCoinAbi.ts: -------------------------------------------------------------------------------- 1 | export const L2TestCoinAbi = [ 2 | { 3 | inputs: [ 4 | { 5 | internalType: "string", 6 | name: "_name", 7 | type: "string", 8 | }, 9 | { 10 | internalType: "string", 11 | name: "_symbol", 12 | type: "string", 13 | }, 14 | { 15 | internalType: "uint8", 16 | name: "_decimals", 17 | type: "uint8", 18 | }, 19 | ], 20 | stateMutability: "nonpayable", 21 | type: "constructor", 22 | }, 23 | { 24 | anonymous: false, 25 | inputs: [ 26 | { 27 | indexed: true, 28 | internalType: "address", 29 | name: "owner", 30 | type: "address", 31 | }, 32 | { 33 | indexed: true, 34 | internalType: "address", 35 | name: "spender", 36 | type: "address", 37 | }, 38 | { 39 | indexed: false, 40 | internalType: "uint256", 41 | name: "amount", 42 | type: "uint256", 43 | }, 44 | ], 45 | name: "Approval", 46 | type: "event", 47 | }, 48 | { 49 | anonymous: false, 50 | inputs: [ 51 | { 52 | indexed: true, 53 | internalType: "address", 54 | name: "from", 55 | type: "address", 56 | }, 57 | { 58 | indexed: true, 59 | internalType: "address", 60 | name: "to", 61 | type: "address", 62 | }, 63 | { 64 | indexed: false, 65 | internalType: "uint256", 66 | name: "amount", 67 | type: "uint256", 68 | }, 69 | ], 70 | name: "Transfer", 71 | type: "event", 72 | }, 73 | { 74 | inputs: [], 75 | name: "DOMAIN_SEPARATOR", 76 | outputs: [ 77 | { 78 | internalType: "bytes32", 79 | name: "", 80 | type: "bytes32", 81 | }, 82 | ], 83 | stateMutability: "view", 84 | type: "function", 85 | }, 86 | { 87 | inputs: [ 88 | { 89 | internalType: "address", 90 | name: "", 91 | type: "address", 92 | }, 93 | { 94 | internalType: "address", 95 | name: "", 96 | type: "address", 97 | }, 98 | ], 99 | name: "allowance", 100 | outputs: [ 101 | { 102 | internalType: "uint256", 103 | name: "", 104 | type: "uint256", 105 | }, 106 | ], 107 | stateMutability: "view", 108 | type: "function", 109 | }, 110 | { 111 | inputs: [ 112 | { 113 | internalType: "address", 114 | name: "spender", 115 | type: "address", 116 | }, 117 | { 118 | internalType: "uint256", 119 | name: "amount", 120 | type: "uint256", 121 | }, 122 | ], 123 | name: "approve", 124 | outputs: [ 125 | { 126 | internalType: "bool", 127 | name: "", 128 | type: "bool", 129 | }, 130 | ], 131 | stateMutability: "nonpayable", 132 | type: "function", 133 | }, 134 | { 135 | inputs: [ 136 | { 137 | internalType: "address", 138 | name: "", 139 | type: "address", 140 | }, 141 | ], 142 | name: "balanceOf", 143 | outputs: [ 144 | { 145 | internalType: "uint256", 146 | name: "", 147 | type: "uint256", 148 | }, 149 | ], 150 | stateMutability: "view", 151 | type: "function", 152 | }, 153 | { 154 | inputs: [], 155 | name: "decimals", 156 | outputs: [ 157 | { 158 | internalType: "uint8", 159 | name: "", 160 | type: "uint8", 161 | }, 162 | ], 163 | stateMutability: "view", 164 | type: "function", 165 | }, 166 | { 167 | inputs: [], 168 | name: "getBalanceOfStorageSlot", 169 | outputs: [ 170 | { 171 | internalType: "uint256", 172 | name: "slot", 173 | type: "uint256", 174 | }, 175 | ], 176 | stateMutability: "pure", 177 | type: "function", 178 | }, 179 | { 180 | inputs: [], 181 | name: "getTotalSupplyStorageSlot", 182 | outputs: [ 183 | { 184 | internalType: "uint256", 185 | name: "slot", 186 | type: "uint256", 187 | }, 188 | ], 189 | stateMutability: "pure", 190 | type: "function", 191 | }, 192 | { 193 | inputs: [ 194 | { 195 | internalType: "address", 196 | name: "_to", 197 | type: "address", 198 | }, 199 | { 200 | internalType: "uint256", 201 | name: "_amount", 202 | type: "uint256", 203 | }, 204 | ], 205 | name: "mintTo", 206 | outputs: [], 207 | stateMutability: "nonpayable", 208 | type: "function", 209 | }, 210 | { 211 | inputs: [], 212 | name: "name", 213 | outputs: [ 214 | { 215 | internalType: "string", 216 | name: "", 217 | type: "string", 218 | }, 219 | ], 220 | stateMutability: "view", 221 | type: "function", 222 | }, 223 | { 224 | inputs: [ 225 | { 226 | internalType: "address", 227 | name: "", 228 | type: "address", 229 | }, 230 | ], 231 | name: "nonces", 232 | outputs: [ 233 | { 234 | internalType: "uint256", 235 | name: "", 236 | type: "uint256", 237 | }, 238 | ], 239 | stateMutability: "view", 240 | type: "function", 241 | }, 242 | { 243 | inputs: [ 244 | { 245 | internalType: "address", 246 | name: "owner", 247 | type: "address", 248 | }, 249 | { 250 | internalType: "address", 251 | name: "spender", 252 | type: "address", 253 | }, 254 | { 255 | internalType: "uint256", 256 | name: "value", 257 | type: "uint256", 258 | }, 259 | { 260 | internalType: "uint256", 261 | name: "deadline", 262 | type: "uint256", 263 | }, 264 | { 265 | internalType: "uint8", 266 | name: "v", 267 | type: "uint8", 268 | }, 269 | { 270 | internalType: "bytes32", 271 | name: "r", 272 | type: "bytes32", 273 | }, 274 | { 275 | internalType: "bytes32", 276 | name: "s", 277 | type: "bytes32", 278 | }, 279 | ], 280 | name: "permit", 281 | outputs: [], 282 | stateMutability: "nonpayable", 283 | type: "function", 284 | }, 285 | { 286 | inputs: [], 287 | name: "symbol", 288 | outputs: [ 289 | { 290 | internalType: "string", 291 | name: "", 292 | type: "string", 293 | }, 294 | ], 295 | stateMutability: "view", 296 | type: "function", 297 | }, 298 | { 299 | inputs: [], 300 | name: "totalSupply", 301 | outputs: [ 302 | { 303 | internalType: "uint256", 304 | name: "", 305 | type: "uint256", 306 | }, 307 | ], 308 | stateMutability: "view", 309 | type: "function", 310 | }, 311 | { 312 | inputs: [ 313 | { 314 | internalType: "address", 315 | name: "to", 316 | type: "address", 317 | }, 318 | { 319 | internalType: "uint256", 320 | name: "amount", 321 | type: "uint256", 322 | }, 323 | ], 324 | name: "transfer", 325 | outputs: [ 326 | { 327 | internalType: "bool", 328 | name: "", 329 | type: "bool", 330 | }, 331 | ], 332 | stateMutability: "nonpayable", 333 | type: "function", 334 | }, 335 | { 336 | inputs: [ 337 | { 338 | internalType: "address", 339 | name: "from", 340 | type: "address", 341 | }, 342 | { 343 | internalType: "address", 344 | name: "to", 345 | type: "address", 346 | }, 347 | { 348 | internalType: "uint256", 349 | name: "amount", 350 | type: "uint256", 351 | }, 352 | ], 353 | name: "transferFrom", 354 | outputs: [ 355 | { 356 | internalType: "bool", 357 | name: "", 358 | type: "bool", 359 | }, 360 | ], 361 | stateMutability: "nonpayable", 362 | type: "function", 363 | }, 364 | ] as const; 365 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/test/StdUtils.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.7.0 <0.9.0; 3 | 4 | import "../src/Test.sol"; 5 | 6 | contract StdUtilsTest is Test { 7 | function testBound() public { 8 | assertEq(bound(uint256(5), 0, 4), 0); 9 | assertEq(bound(uint256(0), 69, 69), 69); 10 | assertEq(bound(uint256(0), 68, 69), 68); 11 | assertEq(bound(uint256(10), 150, 190), 174); 12 | assertEq(bound(uint256(300), 2800, 3200), 3107); 13 | assertEq(bound(uint256(9999), 1337, 6666), 4669); 14 | } 15 | 16 | function testBound_WithinRange() public { 17 | assertEq(bound(uint256(51), 50, 150), 51); 18 | assertEq(bound(uint256(51), 50, 150), bound(bound(uint256(51), 50, 150), 50, 150)); 19 | assertEq(bound(uint256(149), 50, 150), 149); 20 | assertEq(bound(uint256(149), 50, 150), bound(bound(uint256(149), 50, 150), 50, 150)); 21 | } 22 | 23 | function testBound_EdgeCoverage() public { 24 | assertEq(bound(uint256(0), 50, 150), 50); 25 | assertEq(bound(uint256(1), 50, 150), 51); 26 | assertEq(bound(uint256(2), 50, 150), 52); 27 | assertEq(bound(uint256(3), 50, 150), 53); 28 | assertEq(bound(type(uint256).max, 50, 150), 150); 29 | assertEq(bound(type(uint256).max - 1, 50, 150), 149); 30 | assertEq(bound(type(uint256).max - 2, 50, 150), 148); 31 | assertEq(bound(type(uint256).max - 3, 50, 150), 147); 32 | } 33 | 34 | function testBound_DistributionIsEven(uint256 min, uint256 size) public { 35 | size = size % 100 + 1; 36 | min = bound(min, UINT256_MAX / 2, UINT256_MAX / 2 + size); 37 | uint256 max = min + size - 1; 38 | uint256 result; 39 | 40 | for (uint256 i = 1; i <= size * 4; ++i) { 41 | // x > max 42 | result = bound(max + i, min, max); 43 | assertEq(result, min + (i - 1) % size); 44 | // x < min 45 | result = bound(min - i, min, max); 46 | assertEq(result, max - (i - 1) % size); 47 | } 48 | } 49 | 50 | function testBound(uint256 num, uint256 min, uint256 max) public { 51 | if (min > max) (min, max) = (max, min); 52 | 53 | uint256 result = bound(num, min, max); 54 | 55 | assertGe(result, min); 56 | assertLe(result, max); 57 | assertEq(result, bound(result, min, max)); 58 | if (num >= min && num <= max) assertEq(result, num); 59 | } 60 | 61 | function testBoundUint256Max() public { 62 | assertEq(bound(0, type(uint256).max - 1, type(uint256).max), type(uint256).max - 1); 63 | assertEq(bound(1, type(uint256).max - 1, type(uint256).max), type(uint256).max); 64 | } 65 | 66 | function testCannotBoundMaxLessThanMin() public { 67 | vm.expectRevert(bytes("StdUtils bound(uint256,uint256,uint256): Max is less than min.")); 68 | bound(uint256(5), 100, 10); 69 | } 70 | 71 | function testCannotBoundMaxLessThanMin(uint256 num, uint256 min, uint256 max) public { 72 | vm.assume(min > max); 73 | vm.expectRevert(bytes("StdUtils bound(uint256,uint256,uint256): Max is less than min.")); 74 | bound(num, min, max); 75 | } 76 | 77 | function testBoundInt() public { 78 | assertEq(bound(-3, 0, 4), 2); 79 | assertEq(bound(0, -69, -69), -69); 80 | assertEq(bound(0, -69, -68), -68); 81 | assertEq(bound(-10, 150, 190), 154); 82 | assertEq(bound(-300, 2800, 3200), 2908); 83 | assertEq(bound(9999, -1337, 6666), 1995); 84 | } 85 | 86 | function testBoundInt_WithinRange() public { 87 | assertEq(bound(51, -50, 150), 51); 88 | assertEq(bound(51, -50, 150), bound(bound(51, -50, 150), -50, 150)); 89 | assertEq(bound(149, -50, 150), 149); 90 | assertEq(bound(149, -50, 150), bound(bound(149, -50, 150), -50, 150)); 91 | } 92 | 93 | function testBoundInt_EdgeCoverage() public { 94 | assertEq(bound(type(int256).min, -50, 150), -50); 95 | assertEq(bound(type(int256).min + 1, -50, 150), -49); 96 | assertEq(bound(type(int256).min + 2, -50, 150), -48); 97 | assertEq(bound(type(int256).min + 3, -50, 150), -47); 98 | assertEq(bound(type(int256).min, 10, 150), 10); 99 | assertEq(bound(type(int256).min + 1, 10, 150), 11); 100 | assertEq(bound(type(int256).min + 2, 10, 150), 12); 101 | assertEq(bound(type(int256).min + 3, 10, 150), 13); 102 | 103 | assertEq(bound(type(int256).max, -50, 150), 150); 104 | assertEq(bound(type(int256).max - 1, -50, 150), 149); 105 | assertEq(bound(type(int256).max - 2, -50, 150), 148); 106 | assertEq(bound(type(int256).max - 3, -50, 150), 147); 107 | assertEq(bound(type(int256).max, -50, -10), -10); 108 | assertEq(bound(type(int256).max - 1, -50, -10), -11); 109 | assertEq(bound(type(int256).max - 2, -50, -10), -12); 110 | assertEq(bound(type(int256).max - 3, -50, -10), -13); 111 | } 112 | 113 | function testBoundInt_DistributionIsEven(int256 min, uint256 size) public { 114 | size = size % 100 + 1; 115 | min = bound(min, -int256(size / 2), int256(size - size / 2)); 116 | int256 max = min + int256(size) - 1; 117 | int256 result; 118 | 119 | for (uint256 i = 1; i <= size * 4; ++i) { 120 | // x > max 121 | result = bound(max + int256(i), min, max); 122 | assertEq(result, min + int256((i - 1) % size)); 123 | // x < min 124 | result = bound(min - int256(i), min, max); 125 | assertEq(result, max - int256((i - 1) % size)); 126 | } 127 | } 128 | 129 | function testBoundInt(int256 num, int256 min, int256 max) public { 130 | if (min > max) (min, max) = (max, min); 131 | 132 | int256 result = bound(num, min, max); 133 | 134 | assertGe(result, min); 135 | assertLe(result, max); 136 | assertEq(result, bound(result, min, max)); 137 | if (num >= min && num <= max) assertEq(result, num); 138 | } 139 | 140 | function testBoundIntInt256Max() public { 141 | assertEq(bound(0, type(int256).max - 1, type(int256).max), type(int256).max - 1); 142 | assertEq(bound(1, type(int256).max - 1, type(int256).max), type(int256).max); 143 | } 144 | 145 | function testBoundIntInt256Min() public { 146 | assertEq(bound(0, type(int256).min, type(int256).min + 1), type(int256).min); 147 | assertEq(bound(1, type(int256).min, type(int256).min + 1), type(int256).min + 1); 148 | } 149 | 150 | function testCannotBoundIntMaxLessThanMin() public { 151 | vm.expectRevert(bytes("StdUtils bound(int256,int256,int256): Max is less than min.")); 152 | bound(-5, 100, 10); 153 | } 154 | 155 | function testCannotBoundIntMaxLessThanMin(int256 num, int256 min, int256 max) public { 156 | vm.assume(min > max); 157 | vm.expectRevert(bytes("StdUtils bound(int256,int256,int256): Max is less than min.")); 158 | bound(num, min, max); 159 | } 160 | 161 | function testGenerateCreateAddress() external { 162 | address deployer = 0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9; 163 | uint256 nonce = 14; 164 | address createAddress = computeCreateAddress(deployer, nonce); 165 | assertEq(createAddress, 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45); 166 | } 167 | 168 | function testGenerateCreate2Address() external { 169 | bytes32 salt = bytes32(uint256(31415)); 170 | bytes32 initcodeHash = keccak256(abi.encode(0x6080)); 171 | address deployer = 0x6C9FC64A53c1b71FB3f9Af64d1ae3A4931A5f4E9; 172 | address create2Address = computeCreate2Address(salt, initcodeHash, deployer); 173 | assertEq(create2Address, 0xB147a5d25748fda14b463EB04B111027C290f4d3); 174 | } 175 | 176 | function testBytesToUint() external { 177 | bytes memory maxUint = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 178 | bytes memory two = hex"02"; 179 | bytes memory millionEther = hex"d3c21bcecceda1000000"; 180 | 181 | assertEq(bytesToUint(maxUint), type(uint256).max); 182 | assertEq(bytesToUint(two), 2); 183 | assertEq(bytesToUint(millionEther), 1_000_000 ether); 184 | } 185 | 186 | function testCannotConvertGT32Bytes() external { 187 | bytes memory thirty3Bytes = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; 188 | vm.expectRevert("StdUtils bytesToUint(bytes): Bytes length exceeds 32."); 189 | bytesToUint(thirty3Bytes); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/constants/L2OutputOracleAbi.ts: -------------------------------------------------------------------------------- 1 | export const L2OutputOracleAbi = [ 2 | { 3 | inputs: [ 4 | { internalType: "uint256", name: "_submissionInterval", type: "uint256" }, 5 | { internalType: "uint256", name: "_l2BlockTime", type: "uint256" }, 6 | { 7 | internalType: "uint256", 8 | name: "_finalizationPeriodSeconds", 9 | type: "uint256", 10 | }, 11 | ], 12 | stateMutability: "nonpayable", 13 | type: "constructor", 14 | }, 15 | { 16 | anonymous: false, 17 | inputs: [ 18 | { indexed: false, internalType: "uint8", name: "version", type: "uint8" }, 19 | ], 20 | name: "Initialized", 21 | type: "event", 22 | }, 23 | { 24 | anonymous: false, 25 | inputs: [ 26 | { 27 | indexed: true, 28 | internalType: "bytes32", 29 | name: "outputRoot", 30 | type: "bytes32", 31 | }, 32 | { 33 | indexed: true, 34 | internalType: "uint256", 35 | name: "l2OutputIndex", 36 | type: "uint256", 37 | }, 38 | { 39 | indexed: true, 40 | internalType: "uint256", 41 | name: "l2BlockNumber", 42 | type: "uint256", 43 | }, 44 | { 45 | indexed: false, 46 | internalType: "uint256", 47 | name: "l1Timestamp", 48 | type: "uint256", 49 | }, 50 | ], 51 | name: "OutputProposed", 52 | type: "event", 53 | }, 54 | { 55 | anonymous: false, 56 | inputs: [ 57 | { 58 | indexed: true, 59 | internalType: "uint256", 60 | name: "prevNextOutputIndex", 61 | type: "uint256", 62 | }, 63 | { 64 | indexed: true, 65 | internalType: "uint256", 66 | name: "newNextOutputIndex", 67 | type: "uint256", 68 | }, 69 | ], 70 | name: "OutputsDeleted", 71 | type: "event", 72 | }, 73 | { 74 | inputs: [], 75 | name: "CHALLENGER", 76 | outputs: [{ internalType: "address", name: "", type: "address" }], 77 | stateMutability: "view", 78 | type: "function", 79 | }, 80 | { 81 | inputs: [], 82 | name: "FINALIZATION_PERIOD_SECONDS", 83 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 84 | stateMutability: "view", 85 | type: "function", 86 | }, 87 | { 88 | inputs: [], 89 | name: "L2_BLOCK_TIME", 90 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 91 | stateMutability: "view", 92 | type: "function", 93 | }, 94 | { 95 | inputs: [], 96 | name: "PROPOSER", 97 | outputs: [{ internalType: "address", name: "", type: "address" }], 98 | stateMutability: "view", 99 | type: "function", 100 | }, 101 | { 102 | inputs: [], 103 | name: "SUBMISSION_INTERVAL", 104 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 105 | stateMutability: "view", 106 | type: "function", 107 | }, 108 | { 109 | inputs: [], 110 | name: "challenger", 111 | outputs: [{ internalType: "address", name: "", type: "address" }], 112 | stateMutability: "view", 113 | type: "function", 114 | }, 115 | { 116 | inputs: [ 117 | { internalType: "uint256", name: "_l2BlockNumber", type: "uint256" }, 118 | ], 119 | name: "computeL2Timestamp", 120 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 121 | stateMutability: "view", 122 | type: "function", 123 | }, 124 | { 125 | inputs: [ 126 | { internalType: "uint256", name: "_l2OutputIndex", type: "uint256" }, 127 | ], 128 | name: "deleteL2Outputs", 129 | outputs: [], 130 | stateMutability: "nonpayable", 131 | type: "function", 132 | }, 133 | { 134 | inputs: [], 135 | name: "finalizationPeriodSeconds", 136 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 137 | stateMutability: "view", 138 | type: "function", 139 | }, 140 | { 141 | inputs: [ 142 | { internalType: "uint256", name: "_l2OutputIndex", type: "uint256" }, 143 | ], 144 | name: "getL2Output", 145 | outputs: [ 146 | { 147 | components: [ 148 | { internalType: "bytes32", name: "outputRoot", type: "bytes32" }, 149 | { internalType: "uint128", name: "timestamp", type: "uint128" }, 150 | { internalType: "uint128", name: "l2BlockNumber", type: "uint128" }, 151 | ], 152 | internalType: "struct Types.OutputProposal", 153 | name: "", 154 | type: "tuple", 155 | }, 156 | ], 157 | stateMutability: "view", 158 | type: "function", 159 | }, 160 | { 161 | inputs: [ 162 | { internalType: "uint256", name: "_l2BlockNumber", type: "uint256" }, 163 | ], 164 | name: "getL2OutputAfter", 165 | outputs: [ 166 | { 167 | components: [ 168 | { internalType: "bytes32", name: "outputRoot", type: "bytes32" }, 169 | { internalType: "uint128", name: "timestamp", type: "uint128" }, 170 | { internalType: "uint128", name: "l2BlockNumber", type: "uint128" }, 171 | ], 172 | internalType: "struct Types.OutputProposal", 173 | name: "", 174 | type: "tuple", 175 | }, 176 | ], 177 | stateMutability: "view", 178 | type: "function", 179 | }, 180 | { 181 | inputs: [ 182 | { internalType: "uint256", name: "_l2BlockNumber", type: "uint256" }, 183 | ], 184 | name: "getL2OutputIndexAfter", 185 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 186 | stateMutability: "view", 187 | type: "function", 188 | }, 189 | { 190 | inputs: [ 191 | { 192 | internalType: "uint256", 193 | name: "_startingBlockNumber", 194 | type: "uint256", 195 | }, 196 | { internalType: "uint256", name: "_startingTimestamp", type: "uint256" }, 197 | { internalType: "address", name: "_proposer", type: "address" }, 198 | { internalType: "address", name: "_challenger", type: "address" }, 199 | ], 200 | name: "initialize", 201 | outputs: [], 202 | stateMutability: "nonpayable", 203 | type: "function", 204 | }, 205 | { 206 | inputs: [], 207 | name: "l2BlockTime", 208 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 209 | stateMutability: "view", 210 | type: "function", 211 | }, 212 | { 213 | inputs: [], 214 | name: "latestBlockNumber", 215 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 216 | stateMutability: "view", 217 | type: "function", 218 | }, 219 | { 220 | inputs: [], 221 | name: "latestOutputIndex", 222 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 223 | stateMutability: "view", 224 | type: "function", 225 | }, 226 | { 227 | inputs: [], 228 | name: "nextBlockNumber", 229 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 230 | stateMutability: "view", 231 | type: "function", 232 | }, 233 | { 234 | inputs: [], 235 | name: "nextOutputIndex", 236 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 237 | stateMutability: "view", 238 | type: "function", 239 | }, 240 | { 241 | inputs: [ 242 | { internalType: "bytes32", name: "_outputRoot", type: "bytes32" }, 243 | { internalType: "uint256", name: "_l2BlockNumber", type: "uint256" }, 244 | { internalType: "bytes32", name: "_l1BlockHash", type: "bytes32" }, 245 | { internalType: "uint256", name: "_l1BlockNumber", type: "uint256" }, 246 | ], 247 | name: "proposeL2Output", 248 | outputs: [], 249 | stateMutability: "payable", 250 | type: "function", 251 | }, 252 | { 253 | inputs: [], 254 | name: "proposer", 255 | outputs: [{ internalType: "address", name: "", type: "address" }], 256 | stateMutability: "view", 257 | type: "function", 258 | }, 259 | { 260 | inputs: [], 261 | name: "startingBlockNumber", 262 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 263 | stateMutability: "view", 264 | type: "function", 265 | }, 266 | { 267 | inputs: [], 268 | name: "startingTimestamp", 269 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 270 | stateMutability: "view", 271 | type: "function", 272 | }, 273 | { 274 | inputs: [], 275 | name: "submissionInterval", 276 | outputs: [{ internalType: "uint256", name: "", type: "uint256" }], 277 | stateMutability: "view", 278 | type: "function", 279 | }, 280 | { 281 | inputs: [], 282 | name: "version", 283 | outputs: [{ internalType: "string", name: "", type: "string" }], 284 | stateMutability: "view", 285 | type: "function", 286 | }, 287 | ] as const; 288 | -------------------------------------------------------------------------------- /src/constants/L2TestNFTAbi.ts: -------------------------------------------------------------------------------- 1 | export const L2TestNFTAbi = [ 2 | { 3 | inputs: [ 4 | { 5 | internalType: "string", 6 | name: "_name", 7 | type: "string", 8 | }, 9 | { 10 | internalType: "string", 11 | name: "_symbol", 12 | type: "string", 13 | }, 14 | ], 15 | stateMutability: "nonpayable", 16 | type: "constructor", 17 | }, 18 | { 19 | anonymous: false, 20 | inputs: [ 21 | { 22 | indexed: true, 23 | internalType: "address", 24 | name: "owner", 25 | type: "address", 26 | }, 27 | { 28 | indexed: true, 29 | internalType: "address", 30 | name: "spender", 31 | type: "address", 32 | }, 33 | { 34 | indexed: true, 35 | internalType: "uint256", 36 | name: "id", 37 | type: "uint256", 38 | }, 39 | ], 40 | name: "Approval", 41 | type: "event", 42 | }, 43 | { 44 | anonymous: false, 45 | inputs: [ 46 | { 47 | indexed: true, 48 | internalType: "address", 49 | name: "owner", 50 | type: "address", 51 | }, 52 | { 53 | indexed: true, 54 | internalType: "address", 55 | name: "operator", 56 | type: "address", 57 | }, 58 | { 59 | indexed: false, 60 | internalType: "bool", 61 | name: "approved", 62 | type: "bool", 63 | }, 64 | ], 65 | name: "ApprovalForAll", 66 | type: "event", 67 | }, 68 | { 69 | anonymous: false, 70 | inputs: [ 71 | { 72 | indexed: true, 73 | internalType: "address", 74 | name: "from", 75 | type: "address", 76 | }, 77 | { 78 | indexed: true, 79 | internalType: "address", 80 | name: "to", 81 | type: "address", 82 | }, 83 | { 84 | indexed: true, 85 | internalType: "uint256", 86 | name: "id", 87 | type: "uint256", 88 | }, 89 | ], 90 | name: "Transfer", 91 | type: "event", 92 | }, 93 | { 94 | inputs: [ 95 | { 96 | internalType: "address", 97 | name: "spender", 98 | type: "address", 99 | }, 100 | { 101 | internalType: "uint256", 102 | name: "id", 103 | type: "uint256", 104 | }, 105 | ], 106 | name: "approve", 107 | outputs: [], 108 | stateMutability: "nonpayable", 109 | type: "function", 110 | }, 111 | { 112 | inputs: [ 113 | { 114 | internalType: "address", 115 | name: "owner", 116 | type: "address", 117 | }, 118 | ], 119 | name: "balanceOf", 120 | outputs: [ 121 | { 122 | internalType: "uint256", 123 | name: "", 124 | type: "uint256", 125 | }, 126 | ], 127 | stateMutability: "view", 128 | type: "function", 129 | }, 130 | { 131 | inputs: [], 132 | name: "currentTokenId", 133 | outputs: [ 134 | { 135 | internalType: "uint256", 136 | name: "", 137 | type: "uint256", 138 | }, 139 | ], 140 | stateMutability: "view", 141 | type: "function", 142 | }, 143 | { 144 | inputs: [ 145 | { 146 | internalType: "uint256", 147 | name: "", 148 | type: "uint256", 149 | }, 150 | ], 151 | name: "getApproved", 152 | outputs: [ 153 | { 154 | internalType: "address", 155 | name: "", 156 | type: "address", 157 | }, 158 | ], 159 | stateMutability: "view", 160 | type: "function", 161 | }, 162 | { 163 | inputs: [], 164 | name: "getBalanceOfStorageSlot", 165 | outputs: [ 166 | { 167 | internalType: "uint256", 168 | name: "slot", 169 | type: "uint256", 170 | }, 171 | ], 172 | stateMutability: "pure", 173 | type: "function", 174 | }, 175 | { 176 | inputs: [ 177 | { 178 | internalType: "address", 179 | name: "", 180 | type: "address", 181 | }, 182 | { 183 | internalType: "address", 184 | name: "", 185 | type: "address", 186 | }, 187 | ], 188 | name: "isApprovedForAll", 189 | outputs: [ 190 | { 191 | internalType: "bool", 192 | name: "", 193 | type: "bool", 194 | }, 195 | ], 196 | stateMutability: "view", 197 | type: "function", 198 | }, 199 | { 200 | inputs: [ 201 | { 202 | internalType: "address", 203 | name: "_to", 204 | type: "address", 205 | }, 206 | ], 207 | name: "mintTo", 208 | outputs: [ 209 | { 210 | internalType: "uint256", 211 | name: "", 212 | type: "uint256", 213 | }, 214 | ], 215 | stateMutability: "nonpayable", 216 | type: "function", 217 | }, 218 | { 219 | inputs: [], 220 | name: "name", 221 | outputs: [ 222 | { 223 | internalType: "string", 224 | name: "", 225 | type: "string", 226 | }, 227 | ], 228 | stateMutability: "view", 229 | type: "function", 230 | }, 231 | { 232 | inputs: [ 233 | { 234 | internalType: "uint256", 235 | name: "id", 236 | type: "uint256", 237 | }, 238 | ], 239 | name: "ownerOf", 240 | outputs: [ 241 | { 242 | internalType: "address", 243 | name: "owner", 244 | type: "address", 245 | }, 246 | ], 247 | stateMutability: "view", 248 | type: "function", 249 | }, 250 | { 251 | inputs: [ 252 | { 253 | internalType: "address", 254 | name: "from", 255 | type: "address", 256 | }, 257 | { 258 | internalType: "address", 259 | name: "to", 260 | type: "address", 261 | }, 262 | { 263 | internalType: "uint256", 264 | name: "id", 265 | type: "uint256", 266 | }, 267 | ], 268 | name: "safeTransferFrom", 269 | outputs: [], 270 | stateMutability: "nonpayable", 271 | type: "function", 272 | }, 273 | { 274 | inputs: [ 275 | { 276 | internalType: "address", 277 | name: "from", 278 | type: "address", 279 | }, 280 | { 281 | internalType: "address", 282 | name: "to", 283 | type: "address", 284 | }, 285 | { 286 | internalType: "uint256", 287 | name: "id", 288 | type: "uint256", 289 | }, 290 | { 291 | internalType: "bytes", 292 | name: "data", 293 | type: "bytes", 294 | }, 295 | ], 296 | name: "safeTransferFrom", 297 | outputs: [], 298 | stateMutability: "nonpayable", 299 | type: "function", 300 | }, 301 | { 302 | inputs: [ 303 | { 304 | internalType: "address", 305 | name: "operator", 306 | type: "address", 307 | }, 308 | { 309 | internalType: "bool", 310 | name: "approved", 311 | type: "bool", 312 | }, 313 | ], 314 | name: "setApprovalForAll", 315 | outputs: [], 316 | stateMutability: "nonpayable", 317 | type: "function", 318 | }, 319 | { 320 | inputs: [ 321 | { 322 | internalType: "bytes4", 323 | name: "interfaceId", 324 | type: "bytes4", 325 | }, 326 | ], 327 | name: "supportsInterface", 328 | outputs: [ 329 | { 330 | internalType: "bool", 331 | name: "", 332 | type: "bool", 333 | }, 334 | ], 335 | stateMutability: "view", 336 | type: "function", 337 | }, 338 | { 339 | inputs: [], 340 | name: "symbol", 341 | outputs: [ 342 | { 343 | internalType: "string", 344 | name: "", 345 | type: "string", 346 | }, 347 | ], 348 | stateMutability: "view", 349 | type: "function", 350 | }, 351 | { 352 | inputs: [ 353 | { 354 | internalType: "uint256", 355 | name: "id", 356 | type: "uint256", 357 | }, 358 | ], 359 | name: "tokenURI", 360 | outputs: [ 361 | { 362 | internalType: "string", 363 | name: "", 364 | type: "string", 365 | }, 366 | ], 367 | stateMutability: "pure", 368 | type: "function", 369 | }, 370 | { 371 | inputs: [ 372 | { 373 | internalType: "address", 374 | name: "from", 375 | type: "address", 376 | }, 377 | { 378 | internalType: "address", 379 | name: "to", 380 | type: "address", 381 | }, 382 | { 383 | internalType: "uint256", 384 | name: "id", 385 | type: "uint256", 386 | }, 387 | ], 388 | name: "transferFrom", 389 | outputs: [], 390 | stateMutability: "nonpayable", 391 | type: "function", 392 | }, 393 | ] as const; 394 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/test/StdMath.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.8.0 <0.9.0; 3 | 4 | import "../src/StdMath.sol"; 5 | import "../src/Test.sol"; 6 | 7 | contract StdMathTest is Test { 8 | function testGetAbs() external { 9 | assertEq(stdMath.abs(-50), 50); 10 | assertEq(stdMath.abs(50), 50); 11 | assertEq(stdMath.abs(-1337), 1337); 12 | assertEq(stdMath.abs(0), 0); 13 | 14 | assertEq(stdMath.abs(type(int256).min), (type(uint256).max >> 1) + 1); 15 | assertEq(stdMath.abs(type(int256).max), (type(uint256).max >> 1)); 16 | } 17 | 18 | function testGetAbs_Fuzz(int256 a) external { 19 | uint256 manualAbs = getAbs(a); 20 | 21 | uint256 abs = stdMath.abs(a); 22 | 23 | assertEq(abs, manualAbs); 24 | } 25 | 26 | function testGetDelta_Uint() external { 27 | assertEq(stdMath.delta(uint256(0), uint256(0)), 0); 28 | assertEq(stdMath.delta(uint256(0), uint256(1337)), 1337); 29 | assertEq(stdMath.delta(uint256(0), type(uint64).max), type(uint64).max); 30 | assertEq(stdMath.delta(uint256(0), type(uint128).max), type(uint128).max); 31 | assertEq(stdMath.delta(uint256(0), type(uint256).max), type(uint256).max); 32 | 33 | assertEq(stdMath.delta(0, uint256(0)), 0); 34 | assertEq(stdMath.delta(1337, uint256(0)), 1337); 35 | assertEq(stdMath.delta(type(uint64).max, uint256(0)), type(uint64).max); 36 | assertEq(stdMath.delta(type(uint128).max, uint256(0)), type(uint128).max); 37 | assertEq(stdMath.delta(type(uint256).max, uint256(0)), type(uint256).max); 38 | 39 | assertEq(stdMath.delta(1337, uint256(1337)), 0); 40 | assertEq(stdMath.delta(type(uint256).max, type(uint256).max), 0); 41 | assertEq(stdMath.delta(5000, uint256(1250)), 3750); 42 | } 43 | 44 | function testGetDelta_Uint_Fuzz(uint256 a, uint256 b) external { 45 | uint256 manualDelta; 46 | if (a > b) { 47 | manualDelta = a - b; 48 | } else { 49 | manualDelta = b - a; 50 | } 51 | 52 | uint256 delta = stdMath.delta(a, b); 53 | 54 | assertEq(delta, manualDelta); 55 | } 56 | 57 | function testGetDelta_Int() external { 58 | assertEq(stdMath.delta(int256(0), int256(0)), 0); 59 | assertEq(stdMath.delta(int256(0), int256(1337)), 1337); 60 | assertEq(stdMath.delta(int256(0), type(int64).max), type(uint64).max >> 1); 61 | assertEq(stdMath.delta(int256(0), type(int128).max), type(uint128).max >> 1); 62 | assertEq(stdMath.delta(int256(0), type(int256).max), type(uint256).max >> 1); 63 | 64 | assertEq(stdMath.delta(0, int256(0)), 0); 65 | assertEq(stdMath.delta(1337, int256(0)), 1337); 66 | assertEq(stdMath.delta(type(int64).max, int256(0)), type(uint64).max >> 1); 67 | assertEq(stdMath.delta(type(int128).max, int256(0)), type(uint128).max >> 1); 68 | assertEq(stdMath.delta(type(int256).max, int256(0)), type(uint256).max >> 1); 69 | 70 | assertEq(stdMath.delta(-0, int256(0)), 0); 71 | assertEq(stdMath.delta(-1337, int256(0)), 1337); 72 | assertEq(stdMath.delta(type(int64).min, int256(0)), (type(uint64).max >> 1) + 1); 73 | assertEq(stdMath.delta(type(int128).min, int256(0)), (type(uint128).max >> 1) + 1); 74 | assertEq(stdMath.delta(type(int256).min, int256(0)), (type(uint256).max >> 1) + 1); 75 | 76 | assertEq(stdMath.delta(int256(0), -0), 0); 77 | assertEq(stdMath.delta(int256(0), -1337), 1337); 78 | assertEq(stdMath.delta(int256(0), type(int64).min), (type(uint64).max >> 1) + 1); 79 | assertEq(stdMath.delta(int256(0), type(int128).min), (type(uint128).max >> 1) + 1); 80 | assertEq(stdMath.delta(int256(0), type(int256).min), (type(uint256).max >> 1) + 1); 81 | 82 | assertEq(stdMath.delta(1337, int256(1337)), 0); 83 | assertEq(stdMath.delta(type(int256).max, type(int256).max), 0); 84 | assertEq(stdMath.delta(type(int256).min, type(int256).min), 0); 85 | assertEq(stdMath.delta(type(int256).min, type(int256).max), type(uint256).max); 86 | assertEq(stdMath.delta(5000, int256(1250)), 3750); 87 | } 88 | 89 | function testGetDelta_Int_Fuzz(int256 a, int256 b) external { 90 | uint256 absA = getAbs(a); 91 | uint256 absB = getAbs(b); 92 | uint256 absDelta = absA > absB ? absA - absB : absB - absA; 93 | 94 | uint256 manualDelta; 95 | if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { 96 | manualDelta = absDelta; 97 | } 98 | // (a < 0 && b >= 0) || (a >= 0 && b < 0) 99 | else { 100 | manualDelta = absA + absB; 101 | } 102 | 103 | uint256 delta = stdMath.delta(a, b); 104 | 105 | assertEq(delta, manualDelta); 106 | } 107 | 108 | function testGetPercentDelta_Uint() external { 109 | assertEq(stdMath.percentDelta(uint256(0), uint256(1337)), 1e18); 110 | assertEq(stdMath.percentDelta(uint256(0), type(uint64).max), 1e18); 111 | assertEq(stdMath.percentDelta(uint256(0), type(uint128).max), 1e18); 112 | assertEq(stdMath.percentDelta(uint256(0), type(uint192).max), 1e18); 113 | 114 | assertEq(stdMath.percentDelta(1337, uint256(1337)), 0); 115 | assertEq(stdMath.percentDelta(type(uint192).max, type(uint192).max), 0); 116 | assertEq(stdMath.percentDelta(0, uint256(2500)), 1e18); 117 | assertEq(stdMath.percentDelta(2500, uint256(2500)), 0); 118 | assertEq(stdMath.percentDelta(5000, uint256(2500)), 1e18); 119 | assertEq(stdMath.percentDelta(7500, uint256(2500)), 2e18); 120 | 121 | vm.expectRevert(stdError.divisionError); 122 | stdMath.percentDelta(uint256(1), 0); 123 | } 124 | 125 | function testGetPercentDelta_Uint_Fuzz(uint192 a, uint192 b) external { 126 | vm.assume(b != 0); 127 | uint256 manualDelta; 128 | if (a > b) { 129 | manualDelta = a - b; 130 | } else { 131 | manualDelta = b - a; 132 | } 133 | 134 | uint256 manualPercentDelta = manualDelta * 1e18 / b; 135 | uint256 percentDelta = stdMath.percentDelta(a, b); 136 | 137 | assertEq(percentDelta, manualPercentDelta); 138 | } 139 | 140 | function testGetPercentDelta_Int() external { 141 | assertEq(stdMath.percentDelta(int256(0), int256(1337)), 1e18); 142 | assertEq(stdMath.percentDelta(int256(0), -1337), 1e18); 143 | assertEq(stdMath.percentDelta(int256(0), type(int64).min), 1e18); 144 | assertEq(stdMath.percentDelta(int256(0), type(int128).min), 1e18); 145 | assertEq(stdMath.percentDelta(int256(0), type(int192).min), 1e18); 146 | assertEq(stdMath.percentDelta(int256(0), type(int64).max), 1e18); 147 | assertEq(stdMath.percentDelta(int256(0), type(int128).max), 1e18); 148 | assertEq(stdMath.percentDelta(int256(0), type(int192).max), 1e18); 149 | 150 | assertEq(stdMath.percentDelta(1337, int256(1337)), 0); 151 | assertEq(stdMath.percentDelta(type(int192).max, type(int192).max), 0); 152 | assertEq(stdMath.percentDelta(type(int192).min, type(int192).min), 0); 153 | 154 | assertEq(stdMath.percentDelta(type(int192).min, type(int192).max), 2e18); // rounds the 1 wei diff down 155 | assertEq(stdMath.percentDelta(type(int192).max, type(int192).min), 2e18 - 1); // rounds the 1 wei diff down 156 | assertEq(stdMath.percentDelta(0, int256(2500)), 1e18); 157 | assertEq(stdMath.percentDelta(2500, int256(2500)), 0); 158 | assertEq(stdMath.percentDelta(5000, int256(2500)), 1e18); 159 | assertEq(stdMath.percentDelta(7500, int256(2500)), 2e18); 160 | 161 | vm.expectRevert(stdError.divisionError); 162 | stdMath.percentDelta(int256(1), 0); 163 | } 164 | 165 | function testGetPercentDelta_Int_Fuzz(int192 a, int192 b) external { 166 | vm.assume(b != 0); 167 | uint256 absA = getAbs(a); 168 | uint256 absB = getAbs(b); 169 | uint256 absDelta = absA > absB ? absA - absB : absB - absA; 170 | 171 | uint256 manualDelta; 172 | if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { 173 | manualDelta = absDelta; 174 | } 175 | // (a < 0 && b >= 0) || (a >= 0 && b < 0) 176 | else { 177 | manualDelta = absA + absB; 178 | } 179 | 180 | uint256 manualPercentDelta = manualDelta * 1e18 / absB; 181 | uint256 percentDelta = stdMath.percentDelta(a, b); 182 | 183 | assertEq(percentDelta, manualPercentDelta); 184 | } 185 | 186 | /*////////////////////////////////////////////////////////////////////////// 187 | HELPERS 188 | //////////////////////////////////////////////////////////////////////////*/ 189 | 190 | function getAbs(int256 a) private pure returns (uint256) { 191 | if (a < 0) { 192 | return a == type(int256).min ? uint256(type(int256).max) + 1 : uint256(-a); 193 | } 194 | 195 | return uint256(a); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/README.md: -------------------------------------------------------------------------------- 1 | # Forge Standard Library • [![CI status](https://github.com/foundry-rs/forge-std/actions/workflows/ci.yml/badge.svg)](https://github.com/foundry-rs/forge-std/actions/workflows/ci.yml) 2 | 3 | Forge Standard Library is a collection of helpful contracts and libraries for use with [Forge and Foundry](https://github.com/foundry-rs/foundry). It leverages Forge's cheatcodes to make writing tests easier and faster, while improving the UX of cheatcodes. 4 | 5 | **Learn how to use Forge-Std with the [📖 Foundry Book (Forge-Std Guide)](https://book.getfoundry.sh/forge/forge-std.html).** 6 | 7 | ## Install 8 | 9 | ```bash 10 | forge install foundry-rs/forge-std 11 | ``` 12 | 13 | ## Contracts 14 | 15 | ### stdError 16 | 17 | This is a helper contract for errors and reverts. In Forge, this contract is particularly helpful for the `expectRevert` cheatcode, as it provides all compiler builtin errors. 18 | 19 | See the contract itself for all error codes. 20 | 21 | #### Example usage 22 | 23 | ```solidity 24 | 25 | import "forge-std/Test.sol"; 26 | 27 | contract TestContract is Test { 28 | ErrorsTest test; 29 | 30 | function setUp() public { 31 | test = new ErrorsTest(); 32 | } 33 | 34 | function testExpectArithmetic() public { 35 | vm.expectRevert(stdError.arithmeticError); 36 | test.arithmeticError(10); 37 | } 38 | } 39 | 40 | contract ErrorsTest { 41 | function arithmeticError(uint256 a) public { 42 | uint256 a = a - 100; 43 | } 44 | } 45 | ``` 46 | 47 | ### stdStorage 48 | 49 | This is a rather large contract due to all of the overloading to make the UX decent. Primarily, it is a wrapper around the `record` and `accesses` cheatcodes. It can _always_ find and write the storage slot(s) associated with a particular variable without knowing the storage layout. The one _major_ caveat to this is while a slot can be found for packed storage variables, we can't write to that variable safely. If a user tries to write to a packed slot, the execution throws an error, unless it is uninitialized (`bytes32(0)`). 50 | 51 | This works by recording all `SLOAD`s and `SSTORE`s during a function call. If there is a single slot read or written to, it immediately returns the slot. Otherwise, behind the scenes, we iterate through and check each one (assuming the user passed in a `depth` parameter). If the variable is a struct, you can pass in a `depth` parameter which is basically the field depth. 52 | 53 | I.e.: 54 | 55 | ```solidity 56 | struct T { 57 | // depth 0 58 | uint256 a; 59 | // depth 1 60 | uint256 b; 61 | } 62 | ``` 63 | 64 | #### Example usage 65 | 66 | ```solidity 67 | import "forge-std/Test.sol"; 68 | 69 | contract TestContract is Test { 70 | using stdStorage for StdStorage; 71 | 72 | Storage test; 73 | 74 | function setUp() public { 75 | test = new Storage(); 76 | } 77 | 78 | function testFindExists() public { 79 | // Lets say we want to find the slot for the public 80 | // variable `exists`. We just pass in the function selector 81 | // to the `find` command 82 | uint256 slot = stdstore.target(address(test)).sig("exists()").find(); 83 | assertEq(slot, 0); 84 | } 85 | 86 | function testWriteExists() public { 87 | // Lets say we want to write to the slot for the public 88 | // variable `exists`. We just pass in the function selector 89 | // to the `checked_write` command 90 | stdstore.target(address(test)).sig("exists()").checked_write(100); 91 | assertEq(test.exists(), 100); 92 | } 93 | 94 | // It supports arbitrary storage layouts, like assembly based storage locations 95 | function testFindHidden() public { 96 | // `hidden` is a random hash of a bytes, iteration through slots would 97 | // not find it. Our mechanism does 98 | // Also, you can use the selector instead of a string 99 | uint256 slot = stdstore.target(address(test)).sig(test.hidden.selector).find(); 100 | assertEq(slot, uint256(keccak256("my.random.var"))); 101 | } 102 | 103 | // If targeting a mapping, you have to pass in the keys necessary to perform the find 104 | // i.e.: 105 | function testFindMapping() public { 106 | uint256 slot = stdstore 107 | .target(address(test)) 108 | .sig(test.map_addr.selector) 109 | .with_key(address(this)) 110 | .find(); 111 | // in the `Storage` constructor, we wrote that this address' value was 1 in the map 112 | // so when we load the slot, we expect it to be 1 113 | assertEq(uint(vm.load(address(test), bytes32(slot))), 1); 114 | } 115 | 116 | // If the target is a struct, you can specify the field depth: 117 | function testFindStruct() public { 118 | // NOTE: see the depth parameter - 0 means 0th field, 1 means 1st field, etc. 119 | uint256 slot_for_a_field = stdstore 120 | .target(address(test)) 121 | .sig(test.basicStruct.selector) 122 | .depth(0) 123 | .find(); 124 | 125 | uint256 slot_for_b_field = stdstore 126 | .target(address(test)) 127 | .sig(test.basicStruct.selector) 128 | .depth(1) 129 | .find(); 130 | 131 | assertEq(uint(vm.load(address(test), bytes32(slot_for_a_field))), 1); 132 | assertEq(uint(vm.load(address(test), bytes32(slot_for_b_field))), 2); 133 | } 134 | } 135 | 136 | // A complex storage contract 137 | contract Storage { 138 | struct UnpackedStruct { 139 | uint256 a; 140 | uint256 b; 141 | } 142 | 143 | constructor() { 144 | map_addr[msg.sender] = 1; 145 | } 146 | 147 | uint256 public exists = 1; 148 | mapping(address => uint256) public map_addr; 149 | // mapping(address => Packed) public map_packed; 150 | mapping(address => UnpackedStruct) public map_struct; 151 | mapping(address => mapping(address => uint256)) public deep_map; 152 | mapping(address => mapping(address => UnpackedStruct)) public deep_map_struct; 153 | UnpackedStruct public basicStruct = UnpackedStruct({ 154 | a: 1, 155 | b: 2 156 | }); 157 | 158 | function hidden() public view returns (bytes32 t) { 159 | // an extremely hidden storage slot 160 | bytes32 slot = keccak256("my.random.var"); 161 | assembly { 162 | t := sload(slot) 163 | } 164 | } 165 | } 166 | ``` 167 | 168 | ### stdCheats 169 | 170 | This is a wrapper over miscellaneous cheatcodes that need wrappers to be more dev friendly. Currently there are only functions related to `prank`. In general, users may expect ETH to be put into an address on `prank`, but this is not the case for safety reasons. Explicitly this `hoax` function should only be used for address that have expected balances as it will get overwritten. If an address already has ETH, you should just use `prank`. If you want to change that balance explicitly, just use `deal`. If you want to do both, `hoax` is also right for you. 171 | 172 | #### Example usage: 173 | 174 | ```solidity 175 | 176 | // SPDX-License-Identifier: MIT 177 | pragma solidity ^0.8.0; 178 | 179 | import "forge-std/Test.sol"; 180 | 181 | // Inherit the stdCheats 182 | contract StdCheatsTest is Test { 183 | Bar test; 184 | function setUp() public { 185 | test = new Bar(); 186 | } 187 | 188 | function testHoax() public { 189 | // we call `hoax`, which gives the target address 190 | // eth and then calls `prank` 191 | hoax(address(1337)); 192 | test.bar{value: 100}(address(1337)); 193 | 194 | // overloaded to allow you to specify how much eth to 195 | // initialize the address with 196 | hoax(address(1337), 1); 197 | test.bar{value: 1}(address(1337)); 198 | } 199 | 200 | function testStartHoax() public { 201 | // we call `startHoax`, which gives the target address 202 | // eth and then calls `startPrank` 203 | // 204 | // it is also overloaded so that you can specify an eth amount 205 | startHoax(address(1337)); 206 | test.bar{value: 100}(address(1337)); 207 | test.bar{value: 100}(address(1337)); 208 | vm.stopPrank(); 209 | test.bar(address(this)); 210 | } 211 | } 212 | 213 | contract Bar { 214 | function bar(address expectedSender) public payable { 215 | require(msg.sender == expectedSender, "!prank"); 216 | } 217 | } 218 | ``` 219 | 220 | ### Std Assertions 221 | 222 | Expand upon the assertion functions from the `DSTest` library. 223 | 224 | ### `console.log` 225 | 226 | Usage follows the same format as [Hardhat](https://hardhat.org/hardhat-network/reference/#console-log). 227 | It's recommended to use `console2.sol` as shown below, as this will show the decoded logs in Forge traces. 228 | 229 | ```solidity 230 | // import it indirectly via Test.sol 231 | import "forge-std/Test.sol"; 232 | // or directly import it 233 | import "forge-std/console2.sol"; 234 | ... 235 | console2.log(someValue); 236 | ``` 237 | 238 | If you need compatibility with Hardhat, you must use the standard `console.sol` instead. 239 | Due to a bug in `console.sol`, logs that use `uint256` or `int256` types will not be properly decoded in Forge traces. 240 | 241 | ```solidity 242 | // import it indirectly via Test.sol 243 | import "forge-std/Test.sol"; 244 | // or directly import it 245 | import "forge-std/console.sol"; 246 | ... 247 | console.log(someValue); 248 | ``` 249 | 250 | ## License 251 | 252 | Forge Standard Library is offered under either [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE) license. 253 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/interfaces/IERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2; 3 | 4 | import "./IERC165.sol"; 5 | 6 | /// @title ERC-721 Non-Fungible Token Standard 7 | /// @dev See https://eips.ethereum.org/EIPS/eip-721 8 | /// Note: the ERC-165 identifier for this interface is 0x80ac58cd. 9 | interface IERC721 is IERC165 { 10 | /// @dev This emits when ownership of any NFT changes by any mechanism. 11 | /// This event emits when NFTs are created (`from` == 0) and destroyed 12 | /// (`to` == 0). Exception: during contract creation, any number of NFTs 13 | /// may be created and assigned without emitting Transfer. At the time of 14 | /// any transfer, the approved address for that NFT (if any) is reset to none. 15 | event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); 16 | 17 | /// @dev This emits when the approved address for an NFT is changed or 18 | /// reaffirmed. The zero address indicates there is no approved address. 19 | /// When a Transfer event emits, this also indicates that the approved 20 | /// address for that NFT (if any) is reset to none. 21 | event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); 22 | 23 | /// @dev This emits when an operator is enabled or disabled for an owner. 24 | /// The operator can manage all NFTs of the owner. 25 | event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); 26 | 27 | /// @notice Count all NFTs assigned to an owner 28 | /// @dev NFTs assigned to the zero address are considered invalid, and this 29 | /// function throws for queries about the zero address. 30 | /// @param _owner An address for whom to query the balance 31 | /// @return The number of NFTs owned by `_owner`, possibly zero 32 | function balanceOf(address _owner) external view returns (uint256); 33 | 34 | /// @notice Find the owner of an NFT 35 | /// @dev NFTs assigned to zero address are considered invalid, and queries 36 | /// about them do throw. 37 | /// @param _tokenId The identifier for an NFT 38 | /// @return The address of the owner of the NFT 39 | function ownerOf(uint256 _tokenId) external view returns (address); 40 | 41 | /// @notice Transfers the ownership of an NFT from one address to another address 42 | /// @dev Throws unless `msg.sender` is the current owner, an authorized 43 | /// operator, or the approved address for this NFT. Throws if `_from` is 44 | /// not the current owner. Throws if `_to` is the zero address. Throws if 45 | /// `_tokenId` is not a valid NFT. When transfer is complete, this function 46 | /// checks if `_to` is a smart contract (code size > 0). If so, it calls 47 | /// `onERC721Received` on `_to` and throws if the return value is not 48 | /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`. 49 | /// @param _from The current owner of the NFT 50 | /// @param _to The new owner 51 | /// @param _tokenId The NFT to transfer 52 | /// @param data Additional data with no specified format, sent in call to `_to` 53 | function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external payable; 54 | 55 | /// @notice Transfers the ownership of an NFT from one address to another address 56 | /// @dev This works identically to the other function with an extra data parameter, 57 | /// except this function just sets data to "". 58 | /// @param _from The current owner of the NFT 59 | /// @param _to The new owner 60 | /// @param _tokenId The NFT to transfer 61 | function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; 62 | 63 | /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE 64 | /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE 65 | /// THEY MAY BE PERMANENTLY LOST 66 | /// @dev Throws unless `msg.sender` is the current owner, an authorized 67 | /// operator, or the approved address for this NFT. Throws if `_from` is 68 | /// not the current owner. Throws if `_to` is the zero address. Throws if 69 | /// `_tokenId` is not a valid NFT. 70 | /// @param _from The current owner of the NFT 71 | /// @param _to The new owner 72 | /// @param _tokenId The NFT to transfer 73 | function transferFrom(address _from, address _to, uint256 _tokenId) external payable; 74 | 75 | /// @notice Change or reaffirm the approved address for an NFT 76 | /// @dev The zero address indicates there is no approved address. 77 | /// Throws unless `msg.sender` is the current NFT owner, or an authorized 78 | /// operator of the current owner. 79 | /// @param _approved The new approved NFT controller 80 | /// @param _tokenId The NFT to approve 81 | function approve(address _approved, uint256 _tokenId) external payable; 82 | 83 | /// @notice Enable or disable approval for a third party ("operator") to manage 84 | /// all of `msg.sender`'s assets 85 | /// @dev Emits the ApprovalForAll event. The contract MUST allow 86 | /// multiple operators per owner. 87 | /// @param _operator Address to add to the set of authorized operators 88 | /// @param _approved True if the operator is approved, false to revoke approval 89 | function setApprovalForAll(address _operator, bool _approved) external; 90 | 91 | /// @notice Get the approved address for a single NFT 92 | /// @dev Throws if `_tokenId` is not a valid NFT. 93 | /// @param _tokenId The NFT to find the approved address for 94 | /// @return The approved address for this NFT, or the zero address if there is none 95 | function getApproved(uint256 _tokenId) external view returns (address); 96 | 97 | /// @notice Query if an address is an authorized operator for another address 98 | /// @param _owner The address that owns the NFTs 99 | /// @param _operator The address that acts on behalf of the owner 100 | /// @return True if `_operator` is an approved operator for `_owner`, false otherwise 101 | function isApprovedForAll(address _owner, address _operator) external view returns (bool); 102 | } 103 | 104 | /// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02. 105 | interface IERC721TokenReceiver { 106 | /// @notice Handle the receipt of an NFT 107 | /// @dev The ERC721 smart contract calls this function on the recipient 108 | /// after a `transfer`. This function MAY throw to revert and reject the 109 | /// transfer. Return of other than the magic value MUST result in the 110 | /// transaction being reverted. 111 | /// Note: the contract address is always the message sender. 112 | /// @param _operator The address which called `safeTransferFrom` function 113 | /// @param _from The address which previously owned the token 114 | /// @param _tokenId The NFT identifier which is being transferred 115 | /// @param _data Additional data with no specified format 116 | /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` 117 | /// unless throwing 118 | function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) 119 | external 120 | returns (bytes4); 121 | } 122 | 123 | /// @title ERC-721 Non-Fungible Token Standard, optional metadata extension 124 | /// @dev See https://eips.ethereum.org/EIPS/eip-721 125 | /// Note: the ERC-165 identifier for this interface is 0x5b5e139f. 126 | interface IERC721Metadata is IERC721 { 127 | /// @notice A descriptive name for a collection of NFTs in this contract 128 | function name() external view returns (string memory _name); 129 | 130 | /// @notice An abbreviated name for NFTs in this contract 131 | function symbol() external view returns (string memory _symbol); 132 | 133 | /// @notice A distinct Uniform Resource Identifier (URI) for a given asset. 134 | /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC 135 | /// 3986. The URI may point to a JSON file that conforms to the "ERC721 136 | /// Metadata JSON Schema". 137 | function tokenURI(uint256 _tokenId) external view returns (string memory); 138 | } 139 | 140 | /// @title ERC-721 Non-Fungible Token Standard, optional enumeration extension 141 | /// @dev See https://eips.ethereum.org/EIPS/eip-721 142 | /// Note: the ERC-165 identifier for this interface is 0x780e9d63. 143 | interface IERC721Enumerable is IERC721 { 144 | /// @notice Count NFTs tracked by this contract 145 | /// @return A count of valid NFTs tracked by this contract, where each one of 146 | /// them has an assigned and queryable owner not equal to the zero address 147 | function totalSupply() external view returns (uint256); 148 | 149 | /// @notice Enumerate valid NFTs 150 | /// @dev Throws if `_index` >= `totalSupply()`. 151 | /// @param _index A counter less than `totalSupply()` 152 | /// @return The token identifier for the `_index`th NFT, 153 | /// (sort order not specified) 154 | function tokenByIndex(uint256 _index) external view returns (uint256); 155 | 156 | /// @notice Enumerate NFTs assigned to an owner 157 | /// @dev Throws if `_index` >= `balanceOf(_owner)` or if 158 | /// `_owner` is the zero address, representing invalid NFTs. 159 | /// @param _owner An address where we are interested in NFTs owned by them 160 | /// @param _index A counter less than `balanceOf(_owner)` 161 | /// @return The token identifier for the `_index`th NFT assigned to `_owner`, 162 | /// (sort order not specified) 163 | function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256); 164 | } 165 | -------------------------------------------------------------------------------- /contracts/lib/forge-std/src/StdChains.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.6.2 <0.9.0; 3 | 4 | pragma experimental ABIEncoderV2; 5 | 6 | import {VmSafe} from "./Vm.sol"; 7 | 8 | /** 9 | * StdChains provides information about EVM compatible chains that can be used in scripts/tests. 10 | * For each chain, the chain's name, chain ID, and a default RPC URL are provided. Chains are 11 | * identified by their alias, which is the same as the alias in the `[rpc_endpoints]` section of 12 | * the `foundry.toml` file. For best UX, ensure the alias in the `foundry.toml` file match the 13 | * alias used in this contract, which can be found as the first argument to the 14 | * `setChainWithDefaultRpcUrl` call in the `initialize` function. 15 | * 16 | * There are two main ways to use this contract: 17 | * 1. Set a chain with `setChain(string memory chainAlias, Chain memory chain)` 18 | * 2. Get a chain with `getChain(string memory chainAlias)` or `getChain(uint256 chainId)`. 19 | * 20 | * The first time either of those are used, chains are initialized with the default set of RPC URLs. 21 | * This is done in `initialize`, which uses `setChainWithDefaultRpcUrl`. Defaults are recorded in 22 | * `defaultRpcUrls`. 23 | * 24 | * The `setChain` function is straightforward, and it simply saves off the given chain data. 25 | * 26 | * The `getChain` methods use `getChainWithUpdatedRpcUrl` to return a chain. For example, let's say 27 | * we want to retrieve `mainnet`'s RPC URL: 28 | * - If you haven't set any mainnet chain info with `setChain` and you haven't specified that 29 | * chain in `foundry.toml`, the default data and RPC URL will be returned. 30 | * - If you have set a mainnet RPC URL in `foundry.toml` it will return that, if valid (e.g. if 31 | * a URL is given or if an environment variable is given and that environment variable exists). 32 | * Otherwise, the default data is returned. 33 | * - If you specified data with `setChain` it will return that. 34 | * 35 | * Summarizing the above, the prioritization hierarchy is `setChain` -> `foundry.toml` -> defaults. 36 | */ 37 | abstract contract StdChains { 38 | VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code"))))); 39 | 40 | bool private initialized; 41 | 42 | struct Chain { 43 | // The chain name. 44 | string name; 45 | // The chain's Chain ID. 46 | uint256 chainId; 47 | // A default RPC endpoint for this chain. 48 | // NOTE: This default RPC URL is included for convenience to facilitate quick tests and 49 | // experimentation. Do not use this RPC URL for production test suites, CI, or other heavy 50 | // usage as you will be throttled and this is a disservice to others who need this endpoint. 51 | string rpcUrl; 52 | } 53 | 54 | // Maps from the chain's alias (matching the alias in the `foundry.toml` file) to chain data. 55 | mapping(string => Chain) private chains; 56 | // Maps from the chain's alias to it's default RPC URL. 57 | mapping(string => string) private defaultRpcUrls; 58 | // Maps from a chain ID to it's alias. 59 | mapping(uint256 => string) private idToAlias; 60 | 61 | // The RPC URL will be fetched from config or defaultRpcUrls if possible. 62 | function getChain(string memory chainAlias) internal virtual returns (Chain memory chain) { 63 | require(bytes(chainAlias).length != 0, "StdChains getChain(string): Chain alias cannot be the empty string."); 64 | 65 | initialize(); 66 | chain = chains[chainAlias]; 67 | require( 68 | chain.chainId != 0, 69 | string(abi.encodePacked("StdChains getChain(string): Chain with alias \"", chainAlias, "\" not found.")) 70 | ); 71 | 72 | chain = getChainWithUpdatedRpcUrl(chainAlias, chain); 73 | } 74 | 75 | function getChain(uint256 chainId) internal virtual returns (Chain memory chain) { 76 | require(chainId != 0, "StdChains getChain(uint256): Chain ID cannot be 0."); 77 | initialize(); 78 | string memory chainAlias = idToAlias[chainId]; 79 | 80 | chain = chains[chainAlias]; 81 | 82 | require( 83 | chain.chainId != 0, 84 | string(abi.encodePacked("StdChains getChain(uint256): Chain with ID ", vm.toString(chainId), " not found.")) 85 | ); 86 | 87 | chain = getChainWithUpdatedRpcUrl(chainAlias, chain); 88 | } 89 | 90 | // set chain info, with priority to argument's rpcUrl field. 91 | function setChain(string memory chainAlias, Chain memory chain) internal virtual { 92 | require( 93 | bytes(chainAlias).length != 0, "StdChains setChain(string,Chain): Chain alias cannot be the empty string." 94 | ); 95 | 96 | require(chain.chainId != 0, "StdChains setChain(string,Chain): Chain ID cannot be 0."); 97 | 98 | initialize(); 99 | string memory foundAlias = idToAlias[chain.chainId]; 100 | 101 | require( 102 | bytes(foundAlias).length == 0 || keccak256(bytes(foundAlias)) == keccak256(bytes(chainAlias)), 103 | string( 104 | abi.encodePacked( 105 | "StdChains setChain(string,Chain): Chain ID ", 106 | vm.toString(chain.chainId), 107 | " already used by \"", 108 | foundAlias, 109 | "\"." 110 | ) 111 | ) 112 | ); 113 | 114 | uint256 oldChainId = chains[chainAlias].chainId; 115 | delete idToAlias[oldChainId]; 116 | 117 | chains[chainAlias] = chain; 118 | idToAlias[chain.chainId] = chainAlias; 119 | } 120 | 121 | // lookup rpcUrl, in descending order of priority: 122 | // current -> config (foundry.toml) -> default 123 | function getChainWithUpdatedRpcUrl(string memory chainAlias, Chain memory chain) 124 | private 125 | view 126 | returns (Chain memory) 127 | { 128 | if (bytes(chain.rpcUrl).length == 0) { 129 | try vm.rpcUrl(chainAlias) returns (string memory configRpcUrl) { 130 | chain.rpcUrl = configRpcUrl; 131 | } catch (bytes memory err) { 132 | chain.rpcUrl = defaultRpcUrls[chainAlias]; 133 | // distinguish 'not found' from 'cannot read' 134 | bytes memory notFoundError = 135 | abi.encodeWithSignature("CheatCodeError", string(abi.encodePacked("invalid rpc url ", chainAlias))); 136 | if (keccak256(notFoundError) != keccak256(err) || bytes(chain.rpcUrl).length == 0) { 137 | /// @solidity memory-safe-assembly 138 | assembly { 139 | revert(add(32, err), mload(err)) 140 | } 141 | } 142 | } 143 | } 144 | return chain; 145 | } 146 | 147 | function initialize() private { 148 | if (initialized) return; 149 | 150 | initialized = true; 151 | 152 | // If adding an RPC here, make sure to test the default RPC URL in `testRpcs` 153 | setChainWithDefaultRpcUrl("anvil", Chain("Anvil", 31337, "http://127.0.0.1:8545")); 154 | setChainWithDefaultRpcUrl( 155 | "mainnet", Chain("Mainnet", 1, "https://mainnet.infura.io/v3/6770454bc6ea42c58aac12978531b93f") 156 | ); 157 | setChainWithDefaultRpcUrl( 158 | "goerli", Chain("Goerli", 5, "https://goerli.infura.io/v3/6770454bc6ea42c58aac12978531b93f") 159 | ); 160 | setChainWithDefaultRpcUrl( 161 | "sepolia", Chain("Sepolia", 11155111, "https://sepolia.infura.io/v3/6770454bc6ea42c58aac12978531b93f") 162 | ); 163 | setChainWithDefaultRpcUrl("optimism", Chain("Optimism", 10, "https://mainnet.optimism.io")); 164 | setChainWithDefaultRpcUrl("optimism_goerli", Chain("Optimism Goerli", 420, "https://goerli.optimism.io")); 165 | setChainWithDefaultRpcUrl("arbitrum_one", Chain("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc")); 166 | setChainWithDefaultRpcUrl( 167 | "arbitrum_one_goerli", Chain("Arbitrum One Goerli", 421613, "https://goerli-rollup.arbitrum.io/rpc") 168 | ); 169 | setChainWithDefaultRpcUrl("arbitrum_nova", Chain("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc")); 170 | setChainWithDefaultRpcUrl("polygon", Chain("Polygon", 137, "https://polygon-rpc.com")); 171 | setChainWithDefaultRpcUrl("polygon_mumbai", Chain("Polygon Mumbai", 80001, "https://rpc-mumbai.maticvigil.com")); 172 | setChainWithDefaultRpcUrl("avalanche", Chain("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc")); 173 | setChainWithDefaultRpcUrl( 174 | "avalanche_fuji", Chain("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc") 175 | ); 176 | setChainWithDefaultRpcUrl("bnb_smart_chain", Chain("BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org")); 177 | setChainWithDefaultRpcUrl("bnb_smart_chain_testnet", Chain("BNB Smart Chain Testnet", 97, "https://data-seed-prebsc-1-s1.binance.org:8545"));// forgefmt: disable-line 178 | setChainWithDefaultRpcUrl("gnosis_chain", Chain("Gnosis Chain", 100, "https://rpc.gnosischain.com")); 179 | } 180 | 181 | // set chain info, with priority to chainAlias' rpc url in foundry.toml 182 | function setChainWithDefaultRpcUrl(string memory chainAlias, Chain memory chain) private { 183 | string memory rpcUrl = chain.rpcUrl; 184 | defaultRpcUrls[chainAlias] = rpcUrl; 185 | chain.rpcUrl = ""; 186 | setChain(chainAlias, chain); 187 | chain.rpcUrl = rpcUrl; // restore argument 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /contracts/src/evm-verifier/EVMFetcher.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { IEVMVerifier } from './IEVMVerifier.sol'; 5 | import { EVMFetchTarget } from './EVMFetchTarget.sol'; 6 | import { Address } from '@openzeppelin/contracts/utils/Address.sol'; 7 | 8 | interface IEVMGateway { 9 | function getStorageSlots(address addr, bytes32[] memory commands, bytes[] memory constants) external pure returns(bytes memory witness); 10 | } 11 | 12 | uint8 constant FLAG_DYNAMIC = 0x01; 13 | uint8 constant OP_CONSTANT = 0x00; 14 | uint8 constant OP_BACKREF = 0x20; 15 | uint8 constant OP_END = 0xff; 16 | 17 | /** 18 | * @dev A library to facilitate requesting storage data proofs from contracts, possibly on a different chain. 19 | * See l1-verifier/test/TestL1.sol for example usage. 20 | */ 21 | library EVMFetcher { 22 | uint256 constant MAX_COMMANDS = 32; 23 | uint256 constant MAX_CONSTANTS = 32; // Must not be greater than 32 24 | 25 | using Address for address; 26 | 27 | error TooManyCommands(uint256 max); 28 | error CommandTooLong(); 29 | error InvalidReference(uint256 value, uint256 max); 30 | error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData); 31 | 32 | struct EVMFetchRequest { 33 | IEVMVerifier verifier; 34 | address target; 35 | bytes32[] commands; 36 | uint256 operationIdx; 37 | bytes[] constants; 38 | } 39 | 40 | /** 41 | * @dev Creates a request to fetch the value of multiple storage slots from a contract via CCIP-Read, possibly from 42 | * another chain. 43 | * Supports dynamic length values and slot numbers derived from other retrieved values. 44 | * @param verifier An instance of a verifier contract that can provide and verify the storage slot information. 45 | * @param target The address of the contract to fetch storage proofs for. 46 | */ 47 | function newFetchRequest(IEVMVerifier verifier, address target) internal pure returns (EVMFetchRequest memory) { 48 | bytes32[] memory commands = new bytes32[](MAX_COMMANDS); 49 | bytes[] memory constants = new bytes[](MAX_CONSTANTS); 50 | assembly { 51 | mstore(commands, 0) // Set current array length to 0 52 | mstore(constants, 0) 53 | } 54 | return EVMFetchRequest(verifier, target, commands, 0, constants); 55 | } 56 | 57 | /** 58 | * @dev Starts describing a new fetch request. 59 | * Paths specify a series of hashing operations to derive the final slot ID. 60 | * See https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html for details on how Solidity 61 | * lays out storage variables. 62 | * @param request The request object being operated on. 63 | * @param baseSlot The base slot ID that forms the root of the path. 64 | */ 65 | function getStatic(EVMFetchRequest memory request, uint256 baseSlot) internal pure returns (EVMFetchRequest memory) { 66 | bytes32[] memory commands = request.commands; 67 | uint256 commandIdx = commands.length; 68 | if(commandIdx > 0 && request.operationIdx < 32) { 69 | // Terminate previous command 70 | _addOperation(request, OP_END); 71 | } 72 | assembly { 73 | mstore(commands, add(commandIdx, 1)) // Increment command array length 74 | } 75 | if(request.commands.length > MAX_COMMANDS) { 76 | revert TooManyCommands(MAX_COMMANDS); 77 | } 78 | request.operationIdx = 0; 79 | _addOperation(request, 0); 80 | _addOperation(request, _addConstant(request, abi.encode(baseSlot))); 81 | return request; 82 | } 83 | 84 | /** 85 | * @dev Starts describing a new fetch request. 86 | * Paths specify a series of hashing operations to derive the final slot ID. 87 | * See https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html for details on how Solidity 88 | * lays out storage variables. 89 | * @param request The request object being operated on. 90 | * @param baseSlot The base slot ID that forms the root of the path. 91 | */ 92 | function getDynamic(EVMFetchRequest memory request, uint256 baseSlot) internal pure returns (EVMFetchRequest memory) { 93 | bytes32[] memory commands = request.commands; 94 | uint256 commandIdx = commands.length; 95 | if(commandIdx > 0 && request.operationIdx < 32) { 96 | // Terminate previous command 97 | _addOperation(request, OP_END); 98 | } 99 | assembly { 100 | mstore(commands, add(commandIdx, 1)) // Increment command array length 101 | } 102 | if(request.commands.length > MAX_COMMANDS) { 103 | revert TooManyCommands(MAX_COMMANDS); 104 | } 105 | request.operationIdx = 0; 106 | _addOperation(request, FLAG_DYNAMIC); 107 | _addOperation(request, _addConstant(request, abi.encode(baseSlot))); 108 | return request; 109 | } 110 | 111 | /** 112 | * @dev Adds a `uint256` element to the current path. 113 | * @param request The request object being operated on. 114 | * @param el The element to add. 115 | */ 116 | function element(EVMFetchRequest memory request, uint256 el) internal pure returns (EVMFetchRequest memory) { 117 | if(request.operationIdx >= 32) { 118 | revert CommandTooLong(); 119 | } 120 | _addOperation(request, _addConstant(request, abi.encode(el))); 121 | return request; 122 | } 123 | 124 | /** 125 | * @dev Adds a `bytes32` element to the current path. 126 | * @param request The request object being operated on. 127 | * @param el The element to add. 128 | */ 129 | function element(EVMFetchRequest memory request, bytes32 el) internal pure returns (EVMFetchRequest memory) { 130 | if(request.operationIdx >= 32) { 131 | revert CommandTooLong(); 132 | } 133 | _addOperation(request, _addConstant(request, abi.encode(el))); 134 | return request; 135 | } 136 | 137 | /** 138 | * @dev Adds an `address` element to the current path. 139 | * @param request The request object being operated on. 140 | * @param el The element to add. 141 | */ 142 | function element(EVMFetchRequest memory request, address el) internal pure returns (EVMFetchRequest memory) { 143 | if(request.operationIdx >= 32) { 144 | revert CommandTooLong(); 145 | } 146 | _addOperation(request, _addConstant(request, abi.encode(el))); 147 | return request; 148 | } 149 | 150 | /** 151 | * @dev Adds a `bytes` element to the current path. 152 | * @param request The request object being operated on. 153 | * @param el The element to add. 154 | */ 155 | function element(EVMFetchRequest memory request, bytes memory el) internal pure returns (EVMFetchRequest memory) { 156 | if(request.operationIdx >= 32) { 157 | revert CommandTooLong(); 158 | } 159 | _addOperation(request, _addConstant(request, el)); 160 | return request; 161 | } 162 | 163 | /** 164 | * @dev Adds a `string` element to the current path. 165 | * @param request The request object being operated on. 166 | * @param el The element to add. 167 | */ 168 | function element(EVMFetchRequest memory request, string memory el) internal pure returns (EVMFetchRequest memory) { 169 | if(request.operationIdx >= 32) { 170 | revert CommandTooLong(); 171 | } 172 | _addOperation(request, _addConstant(request, bytes(el))); 173 | return request; 174 | } 175 | 176 | /** 177 | * @dev Adds a reference to a previous fetch to the current path. 178 | * @param request The request object being operated on. 179 | * @param idx The index of the previous fetch request, starting at 0. 180 | */ 181 | function ref(EVMFetchRequest memory request, uint8 idx) internal pure returns (EVMFetchRequest memory) { 182 | if(request.operationIdx >= 32) { 183 | revert CommandTooLong(); 184 | } 185 | if(idx > request.commands.length || idx > 31) { 186 | revert InvalidReference(idx, request.commands.length); 187 | } 188 | _addOperation(request, OP_BACKREF | idx); 189 | return request; 190 | } 191 | 192 | /** 193 | * @dev Initiates the fetch request. 194 | * Calling this function terminates execution; clients that implement CCIP-Read will make a callback to 195 | * `callback` with the results of the operation. 196 | * @param callbackId A callback function selector on this contract that will be invoked via CCIP-Read with the result of the lookup. 197 | * The function must have a signature matching `(bytes[] memory values, bytes callbackData)` with a return type matching the call in which 198 | * this function was invoked. Its return data will be returned as the return value of the entire CCIP-read operation. 199 | * @param callbackData Extra data to supply to the callback. 200 | */ 201 | function fetch(EVMFetchRequest memory request, bytes4 callbackId, bytes memory callbackData) internal view { 202 | if(request.commands.length > 0 && request.operationIdx < 32) { 203 | // Terminate last command 204 | _addOperation(request, OP_END); 205 | } 206 | revert OffchainLookup( 207 | address(this), 208 | request.verifier.gatewayURLs(), 209 | abi.encodeCall(IEVMGateway.getStorageSlots, (request.target, request.commands, request.constants)), 210 | EVMFetchTarget.getStorageSlotsCallback.selector, 211 | abi.encode(request.verifier, request.target, request.commands, request.constants, callbackId, callbackData) 212 | ); 213 | } 214 | 215 | function _addConstant(EVMFetchRequest memory request, bytes memory value) private pure returns(uint8 idx) { 216 | bytes[] memory constants = request.constants; 217 | idx = uint8(constants.length); 218 | assembly { 219 | mstore(constants, add(idx, 1)) // Increment constant array length 220 | } 221 | constants[idx] = value; 222 | } 223 | 224 | function _addOperation(EVMFetchRequest memory request, uint8 op) private pure { 225 | uint256 commandIdx = request.commands.length - 1; 226 | request.commands[commandIdx] = request.commands[commandIdx] | (bytes32(bytes1(op)) >> (8 * request.operationIdx++)); 227 | } 228 | } 229 | --------------------------------------------------------------------------------