├── 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 | switchNetwork?.(chainId)}
21 | disabled={isLoading || !switchNetwork}
22 | loading={isLoading}
23 | >
24 | Switch to {chainName} to mint
25 |
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 |
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 | {
62 | write?.();
63 | }}
64 | >
65 | Mint L2TestCoin (1000)
66 |
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 | {
62 | write?.();
63 | }}
64 | >
65 | Mint L2TestNFT
66 |
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 |
{
68 | write?.();
69 | }}
70 | >
71 | Mint L1PassportNFT
72 |
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 |
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 • [](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 |
--------------------------------------------------------------------------------