├── packages ├── .gitignore ├── contracts │ ├── contracts │ │ ├── l2 │ │ │ ├── Test.sol │ │ │ └── OptimismResolver.sol │ │ └── l1 │ │ │ ├── dependencies.sol │ │ │ ├── Helper_SimpleProxy.sol │ │ │ └── OptimismResolverStub.sol │ ├── .npmignore │ ├── scripts │ │ ├── constants.js │ │ ├── arguments.js │ │ ├── deployL2.js │ │ ├── deploy.js │ │ └── deployL1.js │ ├── test │ │ ├── l1 │ │ │ ├── helpers │ │ │ │ ├── utils.js │ │ │ │ ├── constants.js │ │ │ │ └── trie-test-generator.js │ │ │ └── optimism-resolver-stub-test.js │ │ └── l2 │ │ │ └── resolver-test.js │ ├── package.json │ ├── hardhat.config.js │ └── cache │ │ └── solidity-files-cache.json ├── gateway │ ├── secret.yaml.org │ ├── goeril.app.yml │ ├── .gcloudignore │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts └── client │ ├── package.json │ ├── tsconfig.json │ └── src │ └── index.ts ├── .gitignore ├── package.json ├── LICENSE.md └── README.md /packages/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | gateway/build/* 3 | -------------------------------------------------------------------------------- /packages/contracts/contracts/l2/Test.sol: -------------------------------------------------------------------------------- 1 | contract Test {} 2 | -------------------------------------------------------------------------------- /packages/contracts/.npmignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | node_modules/ 3 | 4 | #Hardhat files 5 | cache/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *node_modules 2 | packages/contracts/artifacts* 3 | packages/contracts/cache* 4 | packages/gateway/build/* 5 | packages/gateway/secret.yaml 6 | *dist* 7 | -------------------------------------------------------------------------------- /packages/gateway/secret.yaml.org: -------------------------------------------------------------------------------- 1 | env_variables: 2 | L1_PROVIDER_URL: "https://eth-goerli.g.alchemy.com/v2/q5D9_dtbdeOEfdXSzWs1DyVter172KF-" 3 | L2_PROVIDER_URL: "https://opt-goerli.g.alchemy.com/v2/oSCIVXdCPv0CSHRAECX_Y9ANvTsl-HC2" -------------------------------------------------------------------------------- /packages/contracts/scripts/constants.js: -------------------------------------------------------------------------------- 1 | const OVM_ADDRESS_MANAGERS = { 2 | mainnet: '0xdE1FCfB0851916CA5101820A69b13a4E276bd81F', 3 | goerli:'0xa6f73589243a6A7a9023b1Fa0651b1d89c177111' 4 | } 5 | module.exports = { 6 | OVM_ADDRESS_MANAGERS 7 | } -------------------------------------------------------------------------------- /packages/contracts/contracts/l1/dependencies.sol: -------------------------------------------------------------------------------- 1 | import {CanonicalTransactionChain} from "@eth-optimism/contracts/L1/rollup/CanonicalTransactionChain.sol"; 2 | import {StateCommitmentChain} from "@eth-optimism/contracts/L1/rollup/StateCommitmentChain.sol"; 3 | import {ENSRegistry} from "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol"; 4 | -------------------------------------------------------------------------------- /packages/contracts/test/l1/helpers/utils.js: -------------------------------------------------------------------------------- 1 | exports.toHexString = (buf) => { 2 | return '0x' + exports.fromHexString(buf).toString('hex') 3 | } 4 | 5 | exports.fromHexString = (str) => { 6 | if (typeof str === 'string' && str.startsWith('0x')) { 7 | return Buffer.from(str.slice(2), 'hex') 8 | } 9 | 10 | return Buffer.from(str) 11 | } 12 | -------------------------------------------------------------------------------- /packages/contracts/scripts/arguments.js: -------------------------------------------------------------------------------- 1 | const CONSTANTS = require('./constants') 2 | const hre = require("hardhat"); 3 | let RESOLVER_ADDRESS 4 | if(process.env.RESOLVER_ADDRESS){ 5 | RESOLVER_ADDRESS = process.env.RESOLVER_ADDRESS 6 | }else{ 7 | throw('Set RESOLVER_ADDRESS=') 8 | } 9 | module.exports = [ 10 | CONSTANTS.OVM_ADDRESS_MANAGERS[hre.network.name], 11 | [ hre.network.config.gatewayurl ], 12 | RESOLVER_ADDRESS 13 | ] -------------------------------------------------------------------------------- /packages/gateway/goeril.app.yml: -------------------------------------------------------------------------------- 1 | runtime: nodejs14 2 | entrypoint: 'node dist/index.js --l1_provider_url $L1_PROVIDER_URL --port $PORT --l2_provider_url $L2_PROVIDER_URL --l2_resolver_address $L2_RESOLVER_ADDRESS --l1_chain_id $L1_CHAIN_ID --l2_chain_id $L2_CHAIN_ID' 3 | env_variables: 4 | PORT: 8080 5 | L2_RESOLVER_ADDRESS: '0x470B48eE90Cec0eb6834B986fEA4F3698C986AC4' 6 | L1_CHAIN_ID: 5 7 | L2_CHAIN_ID: 420 8 | includes: 9 | - secret.yaml 10 | -------------------------------------------------------------------------------- /packages/contracts/test/l1/helpers/constants.js: -------------------------------------------------------------------------------- 1 | exports.NULL_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; 2 | 3 | exports.DUMMY_BATCH_HEADERS = [ 4 | { 5 | batchIndex: 0, 6 | batchRoot: exports.NULL_BYTES32, 7 | batchSize: 0, 8 | prevTotalElements: 0, 9 | extraData: exports.NULL_BYTES32, 10 | }, 11 | ] 12 | 13 | exports.DUMMY_BATCH_PROOFS = [ 14 | { 15 | index: 0, 16 | siblings: [exports.NULL_BYTES32], 17 | }, 18 | ] 19 | -------------------------------------------------------------------------------- /packages/gateway/.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud Platform 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | .gcloudignore 10 | # If you would like to upload your .git directory, .gitignore file or files 11 | # from your .gitignore file, remove the corresponding line 12 | # below: 13 | .git 14 | .gitignore 15 | 16 | # Node.js dependencies: 17 | node_modules/ -------------------------------------------------------------------------------- /packages/contracts/test/l2/resolver-test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | 3 | const NODE = "0xeb4f647bea6caa36333c816d7b46fdcb05f9466ecacc140ea8c66faf15b3d9f1"; // namehash('test.eth') 4 | 5 | describe("OptimismResolver", function() { 6 | it("Should return an address once set", async function() { 7 | const accounts = await ethers.getSigners(); 8 | const address = await accounts[0].getAddress(); 9 | 10 | const Resolver = await ethers.getContractFactory("OptimismResolver"); 11 | const resolver = await Resolver.deploy(); 12 | await resolver.deployed(); 13 | 14 | await resolver.setAddr(NODE, address); 15 | expect(await resolver['addr(bytes32)'](NODE)).to.equal(address); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/contracts/contracts/l2/OptimismResolver.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; 4 | 5 | contract OptimismResolver is Ownable { 6 | mapping(bytes32 => address) addresses; 7 | 8 | event AddrChanged(bytes32 indexed node, address a); 9 | 10 | function setAddr(bytes32 node, address addr) public onlyOwner { 11 | addresses[node] = addr; 12 | emit AddrChanged(node, addr); 13 | } 14 | 15 | function addr(bytes32 node) public view returns (address) { 16 | return addresses[node]; 17 | } 18 | 19 | function addr(bytes32 node, uint256) public view returns (address) { 20 | return addresses[node]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ensdomains/op-resolver", 3 | "version": "0.0.1", 4 | "description": "Packages for an ENS arbitrum resolver", 5 | "main": "index.js", 6 | "repository": "git@github.com:ensdomains/op-resolver.git", 7 | "author": "Makoto Inoue ", 8 | "license": "MIT", 9 | "workspaces": [ 10 | "packages/contracts", 11 | "packages/gateway", 12 | "packages/client" 13 | ], 14 | "private": true, 15 | "scripts": { 16 | "start:gateway": "yarn workspace @ensdomains/op-resolver-gateway start", 17 | "start:client": "yarn workspace @ensdomains/op-resolver-client start", 18 | "test": "yarn workspaces run test", 19 | "lint": "yarn workspaces run lint", 20 | "build": "yarn workspaces run build", 21 | "clean": "rm -fr node_modules && yarn workspaces run clean" 22 | }, 23 | "dependencies": { 24 | "@ensdomains/ens-contracts": "^0.0.12" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/contracts/contracts/l1/Helper_SimpleProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | contract Helper_SimpleProxy { 5 | address internal owner; 6 | address internal target; 7 | 8 | constructor() { 9 | owner = msg.sender; 10 | } 11 | 12 | fallback() external { 13 | makeExternalCall(target, msg.data); 14 | } 15 | 16 | function setTarget(address _target) public { 17 | if (msg.sender == owner) { 18 | target = _target; 19 | } else { 20 | makeExternalCall(target, msg.data); 21 | } 22 | } 23 | 24 | function makeExternalCall(address _target, bytes memory _calldata) 25 | internal 26 | { 27 | (bool success, bytes memory returndata) = _target.call(_calldata); 28 | 29 | if (success) { 30 | assembly { 31 | return(add(returndata, 0x20), mload(returndata)) 32 | } 33 | } else { 34 | assembly { 35 | revert(add(returndata, 0x20), mload(returndata)) 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ensdomains/op-resolver-contracts", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "hardhat test", 8 | "prepublishOnly": "yarn build", 9 | "build": "npx hardhat compile" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@nomiclabs/hardhat-ethers": "^2.2.1", 15 | "@nomiclabs/hardhat-etherscan": "^3.1.2", 16 | "chai": "^4.3.6", 17 | "eth-ens-namehash": "^2.0.8", 18 | "ethers": "^5.7.2", 19 | "hardhat": "^2.12.1", 20 | "isomorphic-fetch": "^3.0.0", 21 | "merkle-patricia-tree": "^4.2.4", 22 | "random-bytes-seed": "^1.0.3", 23 | "rlp": "^3.0.0" 24 | }, 25 | "dependencies": { 26 | "@defi-wonderland/smock": "^2.3.0", 27 | "@ensdomains/ens-contracts": "^0.0.13", 28 | "@eth-optimism/contracts": "^0.5.37", 29 | "@eth-optimism/plugins": "^1.0.0-alpha.3", 30 | "@eth-optimism/smock": "^1.1.10", 31 | "@nomiclabs/ethereumjs-vm": "^4", 32 | "@nomiclabs/hardhat-etherscan": "^3.1.2", 33 | "@openzeppelin/contracts": "^4.7.3" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ethereum Name Service (ENS) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/contracts/scripts/deployL2.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const {ethers} = hre; 3 | const namehash = require('eth-ens-namehash'); 4 | let TEST_NAME 5 | if(process.env.TEST_NAME){ 6 | TEST_NAME = process.env.TEST_NAME 7 | }else{ 8 | console.log(hre.network.name) 9 | if(hre.network.name === 'optimismLocalhost'){ 10 | TEST_NAME = 'test.test' 11 | } else { 12 | throw('Set TEST_NAME=') 13 | } 14 | } 15 | const TEST_NODE = namehash.hash(TEST_NAME); 16 | 17 | async function main() { 18 | /************************************ 19 | * L2 deploy 20 | ************************************/ 21 | // Deploy L2 resolver and set addr record for test.test 22 | const l2accounts = await ethers.getSigners(); 23 | const OptimismResolver = await ethers.getContractFactory("OptimismResolver"); 24 | const resolver = await OptimismResolver.deploy(); 25 | await resolver.deployed(); 26 | console.log(`OptimismResolver deployed to ${resolver.address}`); 27 | await (await resolver.functions.setAddr(TEST_NODE, l2accounts[0].address)).wait(); 28 | console.log({ 29 | TEST_NAME, 30 | TEST_NODE 31 | }) 32 | console.log('Address set to', await resolver['addr(bytes32)'](TEST_NODE)); 33 | 34 | /************************************ 35 | * L1 deploy 36 | ************************************/ 37 | const accounts = await ethers.getSigners(); 38 | } 39 | 40 | // We recommend this pattern to be able to use async/await everywhere 41 | // and properly handle errors. 42 | main() 43 | .then(() => process.exit(0)) 44 | .catch(error => { 45 | console.error(error); 46 | process.exit(1); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.1", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "scripts": { 14 | "start": "node dist/index.js", 15 | "build": "tsdx build", 16 | "lint": "tsdx lint", 17 | "prepare": "tsdx build", 18 | "size": "size-limit", 19 | "analyze": "size-limit --why", 20 | "clean": "rm -fr node_modules dist", 21 | "test": "echo No tests" 22 | }, 23 | "peerDependencies": {}, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "tsdx lint" 27 | } 28 | }, 29 | "prettier": { 30 | "printWidth": 80, 31 | "semi": true, 32 | "singleQuote": true, 33 | "trailingComma": "es5" 34 | }, 35 | "name": "@ensdomains/op-resolver-client", 36 | "author": "Makoto Inoue", 37 | "module": "dist/client.esm.js", 38 | "size-limit": [ 39 | { 40 | "path": "dist/client.cjs.production.min.js", 41 | "limit": "10 KB" 42 | }, 43 | { 44 | "path": "dist/client.esm.js", 45 | "limit": "10 KB" 46 | } 47 | ], 48 | "devDependencies": { 49 | "@size-limit/preset-small-lib": "^7.0.5", 50 | "husky": "^7.0.4", 51 | "size-limit": "^7.0.5", 52 | "tsdx": "^0.14.1", 53 | "tslib": "^2.3.1", 54 | "typescript": "^4.5.4" 55 | }, 56 | "dependencies": { 57 | "@ensdomains/address-encoder": "^0.2.17", 58 | "commander": "^8.3.0", 59 | "eth-ens-namehash": "^2.0.8", 60 | "ethers": "^5.7.2", 61 | "isomorphic-fetch": "^3.0.0", 62 | "node-fetch": "^3.2.10" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/gateway/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | "resolveJsonModule": true, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/contracts/hardhat.config.js: -------------------------------------------------------------------------------- 1 | require("@nomiclabs/hardhat-ethers"); 2 | require('@eth-optimism/plugins/hardhat/compiler'); 3 | require("@nomiclabs/hardhat-etherscan"); 4 | 5 | const localGateway = 'http://localhost:8081/{sender}/{data}.json' 6 | module.exports = { 7 | networks: { 8 | hardhat: { 9 | // throwOnCallFailures: false, 10 | chainId: 31337, 11 | gatewayurl:localGateway, 12 | }, 13 | localhost: { 14 | // throwOnCallFailures: false, 15 | url: 'http://localhost:9545', 16 | chainId: 31337, 17 | accounts: { 18 | mnemonic: "test test test test test test test test test test test junk" 19 | }, 20 | gatewayurl:localGateway, 21 | }, 22 | optimismLocalhost: { 23 | url: 'http://localhost:8545', 24 | accounts: { 25 | mnemonic: "test test test test test test test test test test test junk" 26 | } 27 | }, 28 | goerli: { 29 | url: process.env.L1_PROVIDER_URL || 'http://localhost:9545', 30 | accounts: [process.env.PRIVATE_KEY || '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' ], 31 | chainId: 5, 32 | gatewayurl:'https://op-resolver-example.uc.r.appspot.com/{sender}/{data}.json', 33 | }, 34 | optimismGoerli: { 35 | url: process.env.L2_PROVIDER_URL || 'http://localhost:8545', 36 | accounts: [process.env.PRIVATE_KEY || '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'] 37 | } 38 | }, 39 | etherscan: { 40 | apiKey: process.env.ETHERSCAN_API_KEY 41 | }, 42 | namedAccounts: { 43 | deployer: { 44 | default: 0 45 | } 46 | }, 47 | solidity: { 48 | version: "0.8.9", 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /packages/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "ES2015", 6 | // "lib": ["dom", "commonjs"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | // "noUnusedLocals": true, 21 | // "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | "resolveJsonModule": true, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.1", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "scripts": { 14 | "start": "node dist/index.js", 15 | "build": "tsdx build", 16 | "gcp-build": "tsdx build", 17 | "test": "tsdx test", 18 | "lint": "tsdx lint", 19 | "prepare": "NODE_ENV=development tsdx build", 20 | "size": "size-limit", 21 | "analyze": "size-limit --why", 22 | "clean": "rm -fr node_modules dist" 23 | }, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "tsdx lint" 27 | } 28 | }, 29 | "prettier": { 30 | "printWidth": 80, 31 | "semi": true, 32 | "singleQuote": true, 33 | "trailingComma": "es5" 34 | }, 35 | "name": "@ensdomains/op-resolver-gateway", 36 | "author": "Makoto Inoue", 37 | "module": "dist/gateway.esm.js", 38 | "size-limit": [ 39 | { 40 | "path": "dist/gateway.cjs.production.min.js", 41 | "limit": "10 KB" 42 | }, 43 | { 44 | "path": "dist/gateway.esm.js", 45 | "limit": "10 KB" 46 | } 47 | ], 48 | "devDependencies": { 49 | "@eth-optimism/smock": "^0.2.1-alpha.0", 50 | "@nomiclabs/hardhat-ethers": "^2.0.2", 51 | "@nomiclabs/hardhat-waffle": "^2.0.1", 52 | "chai": "^4.2.0", 53 | "eth-ens-namehash": "^2.0.8", 54 | "ethereum-waffle": "^3.2.1", 55 | "ethers": "^5.0.25", 56 | "hardhat": "^2.1.1", 57 | "merkle-patricia-tree": "^4.0.0", 58 | "random-bytes-seed": "^1.0.3", 59 | "rlp": "^2.2.6" 60 | }, 61 | "dependencies": { 62 | "@chainlink/ccip-read-server": "^0.2.1", 63 | "@defi-wonderland/smock": "^2.3.0", 64 | "@ensdomains/ens-contracts": "^0.0.13", 65 | "@ensdomains/op-resolver-contracts": "^0.0.1", 66 | "@eth-optimism/contracts": "^0.5.37", 67 | "@eth-optimism/plugins": "^1.0.0-alpha.3", 68 | "@eth-optimism/sdk": "0.0.0-2022927214235", 69 | "@openzeppelin/contracts": "^4.7.3", 70 | "tsdx": "^0.14.1" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/contracts/scripts/deploy.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const {ethers, l2ethers} = hre; 3 | const namehash = require('eth-ens-namehash'); 4 | 5 | const OVM_ADDRESS_MANAGER = "0xfA5b622409E1782597952a4A78c1D34CF32fF5e2"; 6 | const TEST_NODE = namehash.hash('test.test'); 7 | 8 | async function main() { 9 | console.log(1, hre.network.config.l2url) 10 | /************************************ 11 | * L2 deploy 12 | ************************************/ 13 | // Replace the l2 provider with one that points at the l2 node 14 | l2ethers.provider = new ethers.providers.JsonRpcProvider(hre.network.config.l2url); 15 | console.log(2) 16 | // Deploy L2 resolver and set addr record for test.test 17 | const l2accounts = await l2ethers.getSigners(); 18 | const OptimismResolver = await l2ethers.getContractFactory("OptimismResolver"); 19 | console.log(3) 20 | const resolver = await OptimismResolver.deploy(); 21 | console.log(4) 22 | await resolver.deployed(); 23 | console.log(`OptimismResolver deployed to ${resolver.address}`); 24 | console.log(5) 25 | await (await resolver.functions.setAddr(TEST_NODE, l2accounts[0].address)).wait(); 26 | console.log('Address set'); 27 | 28 | /************************************ 29 | * L1 deploy 30 | ************************************/ 31 | const accounts = await ethers.getSigners(); 32 | 33 | // Deploy the ENS registry 34 | console.log(10) 35 | const ENS = await ethers.getContractFactory("ENSRegistry"); 36 | console.log(11) 37 | const ens = await ENS.deploy(); 38 | console.log(12) 39 | await ens.deployed(); 40 | console.log(`ENS registry deployed at ${ens.address}`); 41 | console.log(13) 42 | // Create test.test owned by us 43 | await ens.setSubnodeOwner('0x' + '00'.repeat(32), ethers.utils.keccak256(ethers.utils.toUtf8Bytes('test')), accounts[0].address); 44 | console.log(14) 45 | await ens.setSubnodeOwner(namehash.hash('test'), ethers.utils.keccak256(ethers.utils.toUtf8Bytes('test')), accounts[0].address); 46 | console.log(15) 47 | // Deploy the resolver stub 48 | const OptimismResolverStub = await ethers.getContractFactory("OptimismResolverStub"); 49 | console.log(16) 50 | const stub = await OptimismResolverStub.deploy(OVM_ADDRESS_MANAGER, "http://localhost:8081/query", resolver.address); 51 | console.log(17) 52 | await stub.deployed(); 53 | console.log(18) 54 | // Set the stub as the resolver for test.test 55 | await ens.setResolver(namehash.hash('test.test'), stub.address); 56 | console.log(19) 57 | console.log(`OptimismResolverStub deployed at ${stub.address}`); 58 | } 59 | 60 | // We recommend this pattern to be able to use async/await everywhere 61 | // and properly handle errors. 62 | main() 63 | .then(() => process.exit(0)) 64 | .catch(error => { 65 | console.error(error); 66 | process.exit(1); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/contracts/scripts/deployL1.js: -------------------------------------------------------------------------------- 1 | const hre = require("hardhat"); 2 | const {ethers } = hre; 3 | const namehash = require('eth-ens-namehash'); 4 | const abi = require('../artifacts/@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol/ENSRegistry.json').abi 5 | const ResolverAbi = require('../../contracts/artifacts/contracts/l1/OptimismResolverStub.sol/OptimismResolverStub.json').abi 6 | const CONSTANTS = require('./constants') 7 | require('isomorphic-fetch'); 8 | 9 | let RESOLVER_ADDRESS 10 | async function main() { 11 | console.log(1, hre.network, CONSTANTS.OVM_ADDRESS_MANAGERS) 12 | let OVM_ADDRESS_MANAGER 13 | if(hre.network.name == 'localhost'){ 14 | const metadata = await (await fetch('http://localhost:8080/addresses.json')).json() 15 | console.log(metadata) 16 | OVM_ADDRESS_MANAGER = metadata.AddressManager 17 | }else{ 18 | OVM_ADDRESS_MANAGER = CONSTANTS.OVM_ADDRESS_MANAGERS[hre.network.name] 19 | } 20 | if(process.env.RESOLVER_ADDRESS){ 21 | RESOLVER_ADDRESS = process.env.RESOLVER_ADDRESS 22 | }else{ 23 | throw('Set RESOLVER_ADDRESS=') 24 | } 25 | /************************************ 26 | * L1 deploy 27 | ************************************/ 28 | const accounts = await ethers.getSigners(); 29 | 30 | // Deploy the resolver stub 31 | console.log(2) 32 | const OptimismResolverStub = await ethers.getContractFactory("OptimismResolverStub"); 33 | console.log(3, OVM_ADDRESS_MANAGER, [hre.network.config.gatewayurl], RESOLVER_ADDRESS) 34 | const stub = await OptimismResolverStub.deploy(OVM_ADDRESS_MANAGER, [hre.network.config.gatewayurl], RESOLVER_ADDRESS); 35 | console.log(4) 36 | await stub.deployed(); 37 | console.log(`OptimismResolverStub deployed at ${stub.address}`); 38 | 39 | // Create test.test owned by us 40 | if(hre.network.name === 'localhost'){ 41 | // Deploy the ENS registry 42 | const ENS = await ethers.getContractFactory("ENSRegistry"); 43 | const ens = await ENS.deploy(); 44 | await ens.deployed(); 45 | console.log(`ENS registry deployed at ${ens.address}`); 46 | 47 | let tx = await ens.setSubnodeOwner('0x' + '00'.repeat(32), ethers.utils.keccak256(ethers.utils.toUtf8Bytes('test')), accounts[0].address); 48 | let rcpt = await tx.wait() 49 | tx = await ens.setSubnodeOwner(namehash.hash('test'), ethers.utils.keccak256(ethers.utils.toUtf8Bytes('test')), accounts[0].address); 50 | rcpt = await tx.wait() 51 | console.log(18) 52 | // Set the stub as the resolver for test.test 53 | tx = await ens.setResolver(namehash.hash('test.test'), stub.address); 54 | rcpt = await tx.wait() 55 | console.log(19, ens.address) 56 | console.log(await ens.owner(namehash.hash('test.test'))) 57 | console.log(await ens.resolver(namehash.hash('test.test'))) 58 | console.log(hre.network.config, ens.address) 59 | provider = new ethers.providers.JsonRpcProvider(hre.network.config.url, { 60 | chainId: hre.network.config.chainId, 61 | name: 'unknown', 62 | ensAddress:ens.address 63 | }); 64 | const ens2 = new ethers.Contract(ens.address, abi, provider); 65 | console.log(await ens2.resolver(namehash.hash('test.test'))) 66 | } 67 | } 68 | 69 | // We recommend this pattern to be able to use async/await everywhere 70 | // and properly handle errors. 71 | main() 72 | .then(() => process.exit(0)) 73 | .catch(error => { 74 | console.error(error); 75 | process.exit(1); 76 | }); 77 | -------------------------------------------------------------------------------- /packages/gateway/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Server } from '@chainlink/ccip-read-server'; 2 | import { Command } from 'commander'; 3 | import { ethers } from 'ethers'; 4 | 5 | const optimismSDK = require("@eth-optimism/sdk") 6 | // const StubAbi = require('../../contracts/artifacts/contracts/l1/OptimismResolverStub.sol/OptimismResolverStub.json').abi 7 | // const OptimismResolverAbi = require('../../contracts/artifacts/contracts/l2/OptimismResolver.sol/OptimismResolver.json').abi 8 | const IResolverAbi = require('@ensdomains/op-resolver-contracts/artifacts/contracts/l1/OptimismResolverStub.sol/IResolverService.json').abi 9 | 10 | // const namehash = require('eth-ens-namehash'); 11 | 12 | const program = new Command(); 13 | program 14 | .option('-l1p --l1_provider_url ', 'L1_PROVIDER_URL', 'http://localhost:9545') 15 | .option('-l2p --l2_provider_url ', 'L2_PROVIDER_URL', 'http://localhost:8545') 16 | .option('-l1c --l1_chain_id ', 'L1_CHAIN_ID', '31337') 17 | .option('-l2c --l2_chain_id ', 'L2_CHAIN_ID', '17') 18 | .option('-r --l2_resolver_address
', 'L2_PROVIDER_URL') 19 | .option('-d --debug', 'debug', false) 20 | .option('-v --verification_option ', 'VERIFICATION_OPTION', 'fewhoursold') 21 | .option('-p --port ', 'Port number to serve on', '8081'); 22 | program.parse(process.argv); 23 | const options = program.opts(); 24 | console.log({options}) 25 | const {l1_provider_url , l2_provider_url , l2_resolver_address, l1_chain_id, l2_chain_id, debug, verification_option } = options 26 | const l1_provider = new ethers.providers.JsonRpcProvider(l1_provider_url); 27 | const l2_provider = new ethers.providers.JsonRpcProvider(l2_provider_url); 28 | const server = new Server(); 29 | let storageOption:any, storageProof:any 30 | switch (verification_option) { 31 | case 'latest': 32 | case 'finalized': 33 | storageOption = { 34 | blockTag:verification_option 35 | } 36 | break; 37 | default: 38 | storageOption = { 39 | l1BlocksAgo: 2000 40 | } 41 | } 42 | if(debug) console.log({storageOption}) 43 | 44 | server.add(IResolverAbi, [ 45 | { 46 | type: 'addr(bytes32)', 47 | func: async ([node], {to, data:_callData}) => { 48 | const addrSlot = ethers.utils.keccak256(node + '00'.repeat(31) + '01'); 49 | if(debug){ 50 | console.log(1, {node, to, _callData, l1_provider_url , l2_provider_url , l2_resolver_address, l1_chain_id, l2_chain_id}) 51 | const blockNumber = (await l2_provider.getBlock('latest')).number 52 | console.log(2, {blockNumber, addrSlot}) 53 | let addressData 54 | try{ 55 | addressData = await l2_provider.getStorageAt(l2_resolver_address, addrSlot) 56 | }catch(e){ 57 | console.log(3, {e}) 58 | } 59 | console.log(4,{ 60 | addressData 61 | }) 62 | } 63 | const crossChainMessenger = new optimismSDK.CrossChainMessenger({ 64 | l1ChainId: parseInt(l1_chain_id), 65 | l2ChainId: parseInt(l2_chain_id), 66 | l1SignerOrProvider: l1_provider, 67 | l2SignerOrProvider: l2_provider 68 | }) 69 | 70 | try{ 71 | storageProof = await crossChainMessenger.getStorageProof(l2_resolver_address, addrSlot, storageOption) 72 | if(debug) console.log({storageProof}) 73 | console.log(storageProof) 74 | }catch(e){ 75 | console.log(e) 76 | } 77 | return [storageProof] 78 | } 79 | } 80 | ]); 81 | const app = server.makeApp('/'); 82 | app.listen(options.port); 83 | -------------------------------------------------------------------------------- /packages/client/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander'; 2 | import { ethers } from 'ethers'; 3 | import 'isomorphic-fetch'; 4 | 5 | const namehash = require('eth-ens-namehash'); 6 | const StubAbi = require('../../contracts/artifacts/contracts/l1/OptimismResolverStub.sol/OptimismResolverStub.json').abi 7 | const IResolverAbi = require('../../contracts/artifacts/contracts/l1/OptimismResolverStub.sol/IResolverService.json').abi 8 | const program = new Command(); 9 | const { defaultAbiCoder, hexConcat } = require("ethers/lib/utils"); 10 | program 11 | .requiredOption('-r --registry
', 'ENS registry address') 12 | .option('-l1 --l1_provider_url ', 'L1_PROVIDER_URL', 'http://localhost:9545') 13 | .option('-l2 --l2_provider_url ', 'L2_PROVIDER_URL') 14 | .option('-i --chainId ', 'chainId', '31337') 15 | .option('-n --chainName ', 'chainName', 'unknown') 16 | .option('-d --debug', 'debug', false) 17 | .argument(''); 18 | 19 | program.parse(process.argv); 20 | const options = program.opts(); 21 | const ensAddress = options.registry; 22 | const chainId = parseInt(options.chainId); 23 | const { chainName, l1_provider_url, debug } = options 24 | console.log({l1_provider_url, ensAddress, chainId, chainName, debug}) 25 | let provider 26 | if(chainId && chainName){ 27 | provider = new ethers.providers.JsonRpcProvider(l1_provider_url, { 28 | chainId, 29 | name: chainName, 30 | ensAddress 31 | } 32 | ); 33 | } else { 34 | provider = new ethers.providers.JsonRpcProvider(options.l1_provider_url); 35 | } 36 | // provider.on("debug", console.log) 37 | const l2provider = new ethers.providers.JsonRpcProvider(options.l2_provider_url); 38 | 39 | (async () => { 40 | const l1ChainId = parseInt(await provider.send('eth_chainId', [])) 41 | const l2ChainId = parseInt(await l2provider.send('eth_chainId', [])) 42 | 43 | const name = program.args[0]; 44 | const node = namehash.hash(name) 45 | console.log({ l1ChainId, l2ChainId, name, node }) 46 | let r = await provider.getResolver(name); 47 | if(r){ 48 | const resolver = new ethers.Contract(r.address, StubAbi, provider); 49 | const iresolver = new ethers.Contract(r.address, IResolverAbi, provider); 50 | try{ 51 | if(debug){ 52 | // this will throw OffchainLookup error 53 | console.log(await resolver.callStatic['addr(bytes32)'](node)) 54 | }else{ 55 | const beforeTime = (new Date()).getTime() 56 | console.log('getAddress ', await r.getAddress()); 57 | const afterTime = (new Date()).getTime() 58 | console.log('(call time=', afterTime - beforeTime , ')') 59 | console.log('getAddress(60) ', await r.getAddress(60)); 60 | console.log('_fetchBytes ', await r._fetchBytes('0xf1cb7e06', '0x000000000000000000000000000000000000000000000000000000000000003c')) 61 | console.log('addr(bytes32) ', await resolver.callStatic['addr(bytes32)'](node, { ccipReadEnabled:true })) 62 | console.log('addr(bytes32,uint256)', await resolver.callStatic['addr(bytes32,uint256)'](node, 60, { ccipReadEnabled:true })) 63 | console.log('resolveName', await provider.resolveName(name)); 64 | } 65 | }catch(e){ 66 | // Manually calling the gateway 67 | console.log('error', e) 68 | if(e.errorArgs){ 69 | const {sender, urls, callData, callbackFunction, extraData } = e.errorArgs 70 | console.log(1, {sender, urls, callData, callbackFunction, extraData}) 71 | const url = urls[0].replace(/{sender}/, sender).replace(/{data}/, callData) 72 | console.log(2, {url}) 73 | const fetched = await fetch(url) 74 | const responseData:any = await (fetched).json() 75 | if(responseData){ 76 | try{ 77 | const encoded = defaultAbiCoder.encode([ "bytes", "bytes" ], [responseData.data, extraData]); 78 | const data = hexConcat([ callbackFunction, encoded ]) 79 | const result = await resolver.provider.call({ 80 | to: resolver.address, 81 | data 82 | }); 83 | console.log(4, {result}) 84 | const decodedResult = resolver.interface.decodeFunctionResult("addrWithProof", result); 85 | console.log(5, {decodedResult}) 86 | 87 | }catch(ee){ 88 | console.log(6, {ee}) 89 | } 90 | } 91 | } 92 | } 93 | } 94 | })(); 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Optimism Resolver 2 | 3 | This repository contains smart contracts and a node.js gateway server that together allow storing ENS names on Optimism using EIP 3668 and ENSIP 10. 4 | 5 | ## Overview 6 | 7 | ENS resolution requests to the resolver implemented in this repository are responded to with a directive to query a gateway server for the answer. The gateway server calls `crossChainMessenger.getStorageProof(l2resolverAddress, addrSlot, storageOption)` to fetch the storage proof from Optimism, which is sent back to the original resolver for decoding and verification. Full details of this request flow can be found in EIP 3668. 8 | 9 | Unlike [Offchain Resolver](https://github.com/ensdomains/offchain-resolver) that requires a trust assumption of gateway signing the response to attest the authenticity of the response data, Optimism resolver validates the cryptographic proof that the given storage was included in the part of the Optimism state. For more information about the overall architecture, please refer to [the blog post](https://medium.com/the-ethereum-name-service/mvp-of-ens-on-l2-with-optimism-demo-video-how-to-try-it-yourself-b44c390cbd67). 10 | 11 | ## Usage 12 | 13 | In a terminal window, download, build, and run Optimism repository. Read [here](https://community.optimism.io/docs/developers/build/dev-node/#setting-up-the-environment) for more detail 14 | 15 | ``` 16 | $ git clone https://github.com/ethereum-optimism/optimism.git 17 | $ cd optimism 18 | $ cd ops 19 | $ docker-compose pull 20 | $ docker-compose up 21 | ``` 22 | 23 | In a second terminal window, deploy our code to the L1 and L2 chains exposed by optimism-integration: 24 | 25 | ``` 26 | $ git clone git@github.com:ensdomains/op-resolver.git 27 | $ cd op-resolver/contracts 28 | $ yarn 29 | $ yarn hardhat --network optimismLocalhost run scripts/deployL2.js 30 | $ // take notes the resolver address 31 | $ RESOLVER_ADDRESS= yarn hardhat --network localhost run scripts/deployL1.js 32 | ``` 33 | 34 | Make note of the ENS registry address logged to the console. 35 | 36 | Now run the gateway service: 37 | 38 | ``` 39 | $ cd ../gateway 40 | $ yarn 41 | $ yarn start --l1_provider_url http://localhost:9545 --l2_provider_url http://localhost:8545 --l2_resolver_address L2_RESOLVER_ADDRESS 42 | ``` 43 | 44 | In a third console window, serve up the demo app: 45 | 46 | ``` 47 | $ cd ../client 48 | $ yarn start --registry L1_REGISTRY_ADDRESS test.test --l1_provider_url http://localhost:9545 49 | ``` 50 | 51 | If you want to see extra debugging info, pass `--debug` option to both command 52 | 53 | ## Notes on Gateway 54 | 55 | Due to the "Optimistic" nature of the rollup, the state is only finalised after 7 days. 56 | You can specify `-v` option to either be `latest`, `finalized`, or the default. 57 | 58 | - `latest` returns the state proof that was published into L1 (~20 minutes) 59 | - `finalized` return the proof that passed the challenge period (1 week) 60 | - The default sets sets `{l1BlocksAgo: 2000}` option when quering the proof from Optimism to wait for 2000 blocks (a few hours) 61 | 62 | ## How to deploy to public net (goerli for example) 63 | 64 | ### Deploy l2 contract 65 | 66 | L1_PROVIDER_URL=L1_PROVIDER_URL L2_PROVIDER_URL=L2_PROVIDER_URL PRIVATE_KEY=PRIVATE_KEY 67 | npx hardhat --network optimismGoerli run scripts/deployL2.js 68 | 69 | ### Deploy l1 contract 70 | 71 | L1_PROVIDER_URL=L1_PROVIDER_URL L2_PROVIDER_URL=L2_PROVIDER_URL PRIVATE_KEY=PRIVATE_KEY 72 | RESOLVER_ADDRESS=RESOLVER_ADDRESS yarn hardhat --network goerli run scripts/deployL1.js 73 | 74 | ### Verify l1 contract 75 | 76 | RESOLVER_ADDRESS= L1_PROVIDER_URL= ETHERSCAN_API_KEY= npx hardhat verify --network goerli --constructor-args scripts/arguments.js CONTRACT_ADDRESS 77 | 78 | ## Deployed contracts 79 | 80 | - op goerli resolver = 0x470B48eE90Cec0eb6834B986fEA4F3698C986AC4 81 | - goerli (gateway points to 'https://op-resolver-example.uc.r.appspot.com/{sender}/{data}.json' ) = [0x0AF7BfB9bC54E4ca0D48C30d6c0396B919c5abd7](https://goerli.etherscan.io/address/0x0AF7BfB9bC54E4ca0D48C30d6c0396B919c5abd7) 82 | - goerli test domain = [opresolver.eth](https://app.ens.domains/name/opresolver.eth/details) 83 | 84 | ## Deploy gateway 85 | 86 | Create secret.yaml and update credentials 87 | 88 | ``` 89 | cd gateway 90 | cp secret.yaml.org secret.yaml 91 | ``` 92 | 93 | Deploy to app engine 94 | 95 | ``` 96 | gcloud app deploy goeril.app.yml 97 | ``` 98 | 99 | ## Components 100 | 101 | ### [Client](client) 102 | 103 | A very simple script that tests if ccip-read integration is working 104 | 105 | ### [Contracts](contracts) 106 | 107 | `OptimismResolverStub` is a L1 (Ethereum) ENS resolver contract that implements the proposed protocol, with 108 | functions to return the gateway address and required prefix for a query, and to verify the response from the gateway. 109 | 110 | `OptimismResolver` is an L2 (Optimism) ENS resolver contract that stores and returns the data necessary to resolve an ENS name. 111 | 112 | ### [Gateway](gateway) 113 | 114 | A node-based gateway server that answers queries for l2 gateway function calls relating to Optimism-based L2 resolvers. 115 | -------------------------------------------------------------------------------- /packages/contracts/test/l1/optimism-resolver-stub-test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { ethers } = require('hardhat'); 3 | const { keccak256 } = require('ethers/lib/utils'); 4 | const { smock } = require('@defi-wonderland/smock'); 5 | 6 | const namehash = require('eth-ens-namehash'); 7 | const testNode = namehash.hash('test.eth'); 8 | const { defaultAbiCoder, hexConcat, Interface } = require("ethers/lib/utils"); 9 | const IResolverAbi = require('../../artifacts/contracts/l1/OptimismResolverStub.sol/IResolverService.json').abi 10 | const { 11 | DUMMY_BATCH_HEADERS, 12 | DUMMY_BATCH_PROOFS, 13 | } = require('./helpers/constants'); 14 | const { TrieTestGenerator } = require('./helpers/trie-test-generator'); 15 | const { toHexString } = require('./helpers/utils'); 16 | 17 | const RESOLVER_ADDR = "0x0123456789012345678901234567890123456789"; 18 | const GATEWAYS = ["http://localhost:8080/query/" + RESOLVER_ADDR]; 19 | 20 | const setProxyTarget = async (AddressManager, name, target) => { 21 | const SimpleProxy = await ( 22 | await ethers.getContractFactory('Helper_SimpleProxy') 23 | ).deploy() 24 | await SimpleProxy.setTarget(target.address) 25 | await AddressManager.setAddress(name, SimpleProxy.address) 26 | } 27 | 28 | const makeAddressManager = async () => { 29 | return (await ethers.getContractFactory('Lib_AddressManager')).deploy() 30 | } 31 | const proofInterface = new Interface(IResolverAbi) 32 | describe("OptimismResolverStub", function() { 33 | let signer; 34 | let account2; 35 | before(async () => { 36 | [signer, account2] = await ethers.getSigners() 37 | }); 38 | 39 | let addressManager 40 | before(async () => { 41 | addressManager = await makeAddressManager() 42 | }); 43 | 44 | let mock__CanonicalTransactionChain; 45 | let mock__StateCommitmentChain; 46 | before(async () => { 47 | mock__CanonicalTransactionChain = await smock.fake('CanonicalTransactionChain'); 48 | mock__StateCommitmentChain = await smock.fake('StateCommitmentChain'); 49 | 50 | await setProxyTarget( 51 | addressManager, 52 | 'CanonicalTransactionChain', 53 | mock__CanonicalTransactionChain 54 | ); 55 | await setProxyTarget( 56 | addressManager, 57 | 'StateCommitmentChain', 58 | mock__StateCommitmentChain 59 | ); 60 | }); 61 | 62 | let Factory__OptimismResolverStub; 63 | before(async () => { 64 | Factory__OptimismResolverStub = await ethers.getContractFactory( 65 | 'OptimismResolverStub' 66 | ); 67 | }); 68 | 69 | let stub, callbackFunction; 70 | beforeEach(async () => { 71 | stub = await Factory__OptimismResolverStub.deploy(addressManager.address, GATEWAYS, RESOLVER_ADDR); 72 | callbackFunction = stub.interface.getSighash('addrWithProof(bytes, bytes)') 73 | callData = stub.interface.encodeFunctionData("addr(bytes32)", [testNode]) 74 | await stub.deployed(); 75 | }); 76 | 77 | it("Should return the gateways and contract address from the constructor", async function() { 78 | expect(await stub.l2resolver()).to.equal(RESOLVER_ADDR); 79 | expect(await stub.gateways(0)).to.equal(GATEWAYS[0]); 80 | }); 81 | 82 | describe('addr', async () => { 83 | it('returns a CCIP-read error', async () => { 84 | try{ 85 | await stub["addr(bytes32)"](testNode) 86 | }catch(e){ 87 | expect(e.errorName).to.equal('OffchainLookup') 88 | expect(e.errorArgs.urls[0]).to.equal(GATEWAYS[0]) 89 | expect(e.errorArgs.callbackFunction).to.equal(callbackFunction) 90 | expect(e.errorArgs.callData).to.equal(callData) 91 | expect(e.errorArgs.extraData).to.equal(testNode) 92 | } 93 | }); 94 | }); 95 | 96 | describe("addrWithProof", () => { 97 | let testAddress; 98 | let proof; 99 | before(async () => { 100 | testAddress = await account2.getAddress(); 101 | const storageKey = keccak256( 102 | testNode + '00'.repeat(31) + '01' 103 | ) 104 | const storageGenerator = await TrieTestGenerator.fromNodes({ 105 | nodes: [ 106 | { 107 | key: storageKey, 108 | // 0x94 is the RLP prefix for a 20-byte string 109 | val: '0x94' + testAddress.substring(2), 110 | }, 111 | ], 112 | secure: true, 113 | }); 114 | 115 | const generator = await TrieTestGenerator.fromAccounts({ 116 | accounts: [ 117 | { 118 | address: RESOLVER_ADDR, 119 | nonce: 0, 120 | balance: 0, 121 | codeHash: keccak256('0x1234'), 122 | storageRoot: toHexString(storageGenerator._trie.root), 123 | }, 124 | ], 125 | secure: true, 126 | }); 127 | 128 | proof = { 129 | stateRoot: toHexString(generator._trie.root), 130 | stateRootBatchHeader: DUMMY_BATCH_HEADERS[0], 131 | stateRootProof: DUMMY_BATCH_PROOFS[0], 132 | stateTrieWitness: (await generator.makeAccountProofTest(RESOLVER_ADDR)) 133 | .accountTrieWitness, 134 | storageTrieWitness: ( 135 | await storageGenerator.makeInclusionProofTest(storageKey) 136 | ).proof, 137 | }; 138 | }) 139 | 140 | beforeEach(async () => { 141 | mock__StateCommitmentChain.verifyStateCommitment.returns(true) 142 | }) 143 | 144 | it("should verify proofs of resolution results", async function() { 145 | const responseData = proofInterface.encodeFunctionResult("addr(bytes32)", [proof]) 146 | expect(await stub.addrWithProof(responseData, testNode)).to.equal(testAddress); 147 | }); 148 | }); 149 | }); 150 | -------------------------------------------------------------------------------- /packages/contracts/test/l1/helpers/trie-test-generator.js: -------------------------------------------------------------------------------- 1 | /* External Imports */ 2 | const rlp = require('rlp'); 3 | const seedbytes = require('random-bytes-seed'); 4 | const { SecureTrie, BaseTrie } = require('merkle-patricia-tree'); 5 | 6 | /* Internal Imports */ 7 | const { fromHexString, toHexString } = require('./utils'); 8 | const { NULL_BYTES32 } = require('./constants'); 9 | 10 | const rlpEncodeAccount = (account) => { 11 | return toHexString( 12 | rlp.encode([ 13 | account.nonce, 14 | account.balance, 15 | account.storageRoot || NULL_BYTES32, 16 | account.codeHash || NULL_BYTES32, 17 | ]) 18 | ) 19 | } 20 | 21 | const rlpDecodeAccount = (encoded) => { 22 | const decoded = rlp.decode(fromHexString(encoded)) 23 | return { 24 | nonce: decoded[0].length ? parseInt(decoded[0], 16) : 0, 25 | balance: decoded[1].length ? parseInt(decoded[1], 16) : 0, 26 | storageRoot: decoded[2].length ? toHexString(decoded[2]) : NULL_BYTES32, 27 | codeHash: decoded[3].length ? toHexString(decoded[3]) : NULL_BYTES32, 28 | } 29 | } 30 | 31 | const makeTrie = async (nodes, secure) => { 32 | const TrieClass = secure ? SecureTrie : BaseTrie 33 | const trie = new TrieClass() 34 | 35 | for (const node of nodes) { 36 | await trie.put(fromHexString(node.key), fromHexString(node.val)) 37 | } 38 | 39 | return { 40 | trie, 41 | TrieClass, 42 | } 43 | } 44 | 45 | exports.TrieTestGenerator = class TrieTestGenerator { 46 | constructor( 47 | _TrieClass, 48 | _trie, 49 | _nodes, 50 | _subGenerators 51 | ) { 52 | this._TrieClass = _TrieClass; 53 | this._trie = _trie; 54 | this._nodes = _nodes; 55 | this._subGenerators = _subGenerators; 56 | } 57 | 58 | static async fromNodes(opts) { 59 | const { trie, TrieClass } = await makeTrie(opts.nodes, opts.secure) 60 | 61 | return new TrieTestGenerator(TrieClass, trie, opts.nodes) 62 | } 63 | 64 | static async fromRandom(opts) { 65 | const getRandomBytes = seedbytes(opts.seed) 66 | const nodes = [...Array(opts.nodeCount)].map(() => { 67 | return { 68 | key: toHexString(getRandomBytes(opts.keySize || 32)), 69 | val: toHexString(getRandomBytes(opts.valSize || 32)), 70 | } 71 | }) 72 | 73 | return TrieTestGenerator.fromNodes({ 74 | nodes, 75 | secure: opts.secure, 76 | }) 77 | } 78 | 79 | static async fromAccounts(opts) { 80 | const subGenerators = [] 81 | 82 | for (const account of opts.accounts) { 83 | if (account.storage) { 84 | const subGenerator = await TrieTestGenerator.fromNodes({ 85 | nodes: account.storage, 86 | secure: opts.secure, 87 | }) 88 | 89 | account.storageRoot = toHexString(subGenerator._trie.root) 90 | subGenerators.push(subGenerator) 91 | } 92 | } 93 | 94 | const nodes = opts.accounts.map((account) => { 95 | return { 96 | key: account.address, 97 | val: rlpEncodeAccount(account), 98 | } 99 | }) 100 | 101 | const { trie, TrieClass } = await makeTrie(nodes, opts.secure) 102 | 103 | return new TrieTestGenerator(TrieClass, trie, nodes, subGenerators) 104 | } 105 | 106 | async makeInclusionProofTest(key) { 107 | if (typeof key === 'number') { 108 | key = this._nodes[key].key 109 | } 110 | 111 | const trie = this._trie.copy() 112 | 113 | const proof = await this.prove(key) 114 | const val = await trie.get(fromHexString(key)) 115 | 116 | return { 117 | proof: toHexString(rlp.encode(proof)), 118 | key: toHexString(key), 119 | val: toHexString(val), 120 | root: toHexString(trie.root), 121 | } 122 | } 123 | 124 | async makeAllInclusionProofTests() { 125 | return Promise.all( 126 | this._nodes.map(async (node) => { 127 | return this.makeInclusionProofTest(node.key) 128 | }) 129 | ) 130 | } 131 | 132 | async makeNodeUpdateTest(key, val) { 133 | if (typeof key === 'number') { 134 | key = this._nodes[key].key 135 | } 136 | 137 | const trie = this._trie.copy() 138 | 139 | const proof = await this.prove(key) 140 | const oldRoot = trie.root 141 | 142 | await trie.put(fromHexString(key), fromHexString(val)) 143 | const newRoot = trie.root 144 | 145 | return { 146 | proof: toHexString(rlp.encode(proof)), 147 | key: toHexString(key), 148 | val: toHexString(val), 149 | root: toHexString(oldRoot), 150 | newRoot: toHexString(newRoot), 151 | } 152 | } 153 | 154 | async makeAccountProofTest(address) { 155 | if (typeof address === 'number') { 156 | address = this._nodes[address].key 157 | } 158 | 159 | const trie = this._trie.copy() 160 | 161 | const proof = await this.prove(address) 162 | const account = await trie.get(fromHexString(address)) 163 | 164 | return { 165 | address, 166 | account: rlpDecodeAccount(toHexString(account)), 167 | accountTrieWitness: toHexString(rlp.encode(proof)), 168 | accountTrieRoot: toHexString(trie.root), 169 | } 170 | } 171 | 172 | async makeAccountUpdateTest(address, account) { 173 | if (typeof address === 'number') { 174 | address = this._nodes[address].key 175 | } 176 | 177 | const trie = this._trie.copy() 178 | 179 | const proof = await this.prove(address) 180 | const oldRoot = trie.root 181 | 182 | await trie.put( 183 | fromHexString(address), 184 | fromHexString(rlpEncodeAccount(account)) 185 | ) 186 | const newRoot = trie.root 187 | 188 | return { 189 | address, 190 | account, 191 | accountTrieWitness: toHexString(rlp.encode(proof)), 192 | accountTrieRoot: toHexString(oldRoot), 193 | newAccountTrieRoot: toHexString(newRoot), 194 | } 195 | } 196 | 197 | async prove(key) { 198 | return this._TrieClass.prove(this._trie, fromHexString(key)) 199 | } 200 | } -------------------------------------------------------------------------------- /packages/contracts/contracts/l1/OptimismResolverStub.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | pragma abicoder v2; 3 | 4 | import {Lib_AddressResolver} from "@eth-optimism/contracts/libraries/resolver/Lib_AddressResolver.sol"; 5 | import {Lib_OVMCodec} from "@eth-optimism/contracts/libraries/codec/Lib_OVMCodec.sol"; 6 | import {Lib_SecureMerkleTrie} from "@eth-optimism/contracts/libraries/trie/Lib_SecureMerkleTrie.sol"; 7 | import {StateCommitmentChain} from "@eth-optimism/contracts/L1/rollup/StateCommitmentChain.sol"; 8 | import {Lib_RLPReader} from "@eth-optimism/contracts/libraries/rlp/Lib_RLPReader.sol"; 9 | import {Lib_BytesUtils} from "@eth-optimism/contracts/libraries/utils/Lib_BytesUtils.sol"; 10 | 11 | struct L2StateProof { 12 | bytes32 stateRoot; 13 | Lib_OVMCodec.ChainBatchHeader stateRootBatchHeader; 14 | Lib_OVMCodec.ChainInclusionProof stateRootProof; 15 | bytes stateTrieWitness; 16 | bytes storageTrieWitness; 17 | } 18 | 19 | interface IResolverService { 20 | function addr(bytes32 node) 21 | external 22 | view 23 | returns (L2StateProof memory proof); 24 | } 25 | 26 | contract OptimismResolverStub is Lib_AddressResolver { 27 | string[] public gateways; 28 | address public l2resolver; 29 | 30 | error OffchainLookup( 31 | address sender, 32 | string[] urls, 33 | bytes callData, 34 | bytes4 callbackFunction, 35 | bytes extraData 36 | ); 37 | 38 | constructor( 39 | address ovmAddressManager, 40 | string[] memory _gateways, 41 | address _l2resolver 42 | ) Lib_AddressResolver(ovmAddressManager) { 43 | gateways = _gateways; 44 | l2resolver = _l2resolver; 45 | } 46 | 47 | function getl2Resolver() external view returns (address) { 48 | return l2resolver; 49 | } 50 | 51 | function addr(bytes32 node) public view returns (address) { 52 | return _addr(node, OptimismResolverStub.addrWithProof.selector); 53 | } 54 | 55 | function addr(bytes32 node, uint256 coinType) 56 | public 57 | view 58 | returns (bytes memory) 59 | { 60 | if (coinType == 60) { 61 | return 62 | addressToBytes( 63 | _addr( 64 | node, 65 | OptimismResolverStub.bytesAddrWithProof.selector 66 | ) 67 | ); 68 | } else { 69 | return addressToBytes(address(0)); 70 | } 71 | } 72 | 73 | function _addr(bytes32 node, bytes4 selector) 74 | private 75 | view 76 | returns (address) 77 | { 78 | bytes memory callData = abi.encodeWithSelector( 79 | IResolverService.addr.selector, 80 | node 81 | ); 82 | revert OffchainLookup( 83 | address(this), 84 | gateways, 85 | callData, 86 | selector, 87 | abi.encode(node) 88 | ); 89 | } 90 | 91 | function addrWithProof(bytes calldata response, bytes calldata extraData) 92 | external 93 | view 94 | returns (address) 95 | { 96 | return _addrWithProof(response, extraData); 97 | } 98 | 99 | function bytesAddrWithProof( 100 | bytes calldata response, 101 | bytes calldata extraData 102 | ) external view returns (bytes memory) { 103 | return addressToBytes(_addrWithProof(response, extraData)); 104 | } 105 | 106 | function _addrWithProof(bytes calldata response, bytes calldata extraData) 107 | internal 108 | view 109 | returns (address) 110 | { 111 | L2StateProof memory proof = abi.decode(response, (L2StateProof)); 112 | bytes32 node = abi.decode(extraData, (bytes32)); 113 | require(verifyStateRootProof(proof), "Invalid state root"); 114 | bytes32 slot = keccak256(abi.encodePacked(node, uint256(1))); 115 | bytes32 value = getStorageValue(l2resolver, slot, proof); 116 | return address(uint160(uint256(value))); 117 | } 118 | 119 | function verifyStateRootProof(L2StateProof memory proof) 120 | internal 121 | view 122 | returns (bool) 123 | { 124 | StateCommitmentChain ovmStateCommitmentChain = StateCommitmentChain( 125 | resolve("StateCommitmentChain") 126 | ); 127 | return 128 | ovmStateCommitmentChain.verifyStateCommitment( 129 | proof.stateRoot, 130 | proof.stateRootBatchHeader, 131 | proof.stateRootProof 132 | ); 133 | } 134 | 135 | function getStorageValue( 136 | address target, 137 | bytes32 slot, 138 | L2StateProof memory proof 139 | ) internal view returns (bytes32) { 140 | ( 141 | bool exists, 142 | bytes memory encodedResolverAccount 143 | ) = Lib_SecureMerkleTrie.get( 144 | abi.encodePacked(target), 145 | proof.stateTrieWitness, 146 | proof.stateRoot 147 | ); 148 | require(exists, "Account does not exist"); 149 | Lib_OVMCodec.EVMAccount memory account = Lib_OVMCodec.decodeEVMAccount( 150 | encodedResolverAccount 151 | ); 152 | (bool storageExists, bytes memory retrievedValue) = Lib_SecureMerkleTrie 153 | .get( 154 | abi.encodePacked(slot), 155 | proof.storageTrieWitness, 156 | account.storageRoot 157 | ); 158 | require(storageExists, "Storage value does not exist"); 159 | return toBytes32PadLeft(Lib_RLPReader.readBytes(retrievedValue)); 160 | } 161 | 162 | // Ported old function from Lib_BytesUtils.sol 163 | function toBytes32PadLeft(bytes memory _bytes) 164 | internal 165 | pure 166 | returns (bytes32) 167 | { 168 | bytes32 ret; 169 | uint256 len = _bytes.length <= 32 ? _bytes.length : 32; 170 | assembly { 171 | ret := shr(mul(sub(32, len), 8), mload(add(_bytes, 32))) 172 | } 173 | return ret; 174 | } 175 | 176 | function addressToBytes(address a) internal pure returns (bytes memory b) { 177 | b = new bytes(20); 178 | assembly { 179 | mstore(add(b, 32), mul(a, exp(256, 12))) 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /packages/contracts/cache/solidity-files-cache.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-cache-2", 3 | "files": { 4 | "/Users/makoto/work/ens/op-resolver/packages/contracts/contracts/l1/Helper_SimpleProxy.sol": { 5 | "lastModificationDate": 1666955053473, 6 | "contentHash": "277aee069d3430d87ed3fcb50cc9a663", 7 | "sourceName": "contracts/l1/Helper_SimpleProxy.sol", 8 | "solcConfig": { 9 | "version": "0.8.9", 10 | "settings": { 11 | "optimizer": { 12 | "enabled": false, 13 | "runs": 200 14 | }, 15 | "outputSelection": { 16 | "*": { 17 | "*": [ 18 | "abi", 19 | "evm.bytecode", 20 | "evm.deployedBytecode", 21 | "evm.methodIdentifiers", 22 | "metadata" 23 | ], 24 | "": [ 25 | "ast" 26 | ] 27 | } 28 | } 29 | } 30 | }, 31 | "imports": [], 32 | "versionPragmas": [ 33 | "^0.8.0" 34 | ], 35 | "artifacts": [ 36 | "Helper_SimpleProxy" 37 | ] 38 | }, 39 | "/Users/makoto/work/ens/op-resolver/packages/contracts/contracts/l1/OptimismResolverStub.sol": { 40 | "lastModificationDate": 1667217949093, 41 | "contentHash": "98b567a5046e4c80db98da8eb4755e40", 42 | "sourceName": "contracts/l1/OptimismResolverStub.sol", 43 | "solcConfig": { 44 | "version": "0.8.9", 45 | "settings": { 46 | "optimizer": { 47 | "enabled": false, 48 | "runs": 200 49 | }, 50 | "outputSelection": { 51 | "*": { 52 | "*": [ 53 | "abi", 54 | "evm.bytecode", 55 | "evm.deployedBytecode", 56 | "evm.methodIdentifiers", 57 | "metadata" 58 | ], 59 | "": [ 60 | "ast" 61 | ] 62 | } 63 | } 64 | } 65 | }, 66 | "imports": [ 67 | "@eth-optimism/contracts/libraries/resolver/Lib_AddressResolver.sol", 68 | "@eth-optimism/contracts/libraries/codec/Lib_OVMCodec.sol", 69 | "@eth-optimism/contracts/libraries/trie/Lib_SecureMerkleTrie.sol", 70 | "@eth-optimism/contracts/L1/rollup/StateCommitmentChain.sol", 71 | "@eth-optimism/contracts/libraries/rlp/Lib_RLPReader.sol", 72 | "@eth-optimism/contracts/libraries/utils/Lib_BytesUtils.sol", 73 | "hardhat/console.sol" 74 | ], 75 | "versionPragmas": [ 76 | "^0.8.0" 77 | ], 78 | "artifacts": [ 79 | "OptimismResolverStub" 80 | ] 81 | }, 82 | "/Users/makoto/work/ens/op-resolver/node_modules/hardhat/console.sol": { 83 | "lastModificationDate": 1666956976401, 84 | "contentHash": "4ff3cd2f6272c9a6516e9ee4f2b967d3", 85 | "sourceName": "hardhat/console.sol", 86 | "solcConfig": { 87 | "version": "0.8.9", 88 | "settings": { 89 | "optimizer": { 90 | "enabled": false, 91 | "runs": 200 92 | }, 93 | "outputSelection": { 94 | "*": { 95 | "*": [ 96 | "abi", 97 | "evm.bytecode", 98 | "evm.deployedBytecode", 99 | "evm.methodIdentifiers", 100 | "metadata" 101 | ], 102 | "": [ 103 | "ast" 104 | ] 105 | } 106 | } 107 | } 108 | }, 109 | "imports": [], 110 | "versionPragmas": [ 111 | ">= 0.4.22 <0.9.0" 112 | ], 113 | "artifacts": [ 114 | "console" 115 | ] 116 | }, 117 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/resolver/Lib_AddressResolver.sol": { 118 | "lastModificationDate": 1666957021250, 119 | "contentHash": "34d1331425790d550286884af1599cff", 120 | "sourceName": "@eth-optimism/contracts/libraries/resolver/Lib_AddressResolver.sol", 121 | "solcConfig": { 122 | "version": "0.8.9", 123 | "settings": { 124 | "optimizer": { 125 | "enabled": false, 126 | "runs": 200 127 | }, 128 | "outputSelection": { 129 | "*": { 130 | "*": [ 131 | "abi", 132 | "evm.bytecode", 133 | "evm.deployedBytecode", 134 | "evm.methodIdentifiers", 135 | "metadata" 136 | ], 137 | "": [ 138 | "ast" 139 | ] 140 | } 141 | } 142 | } 143 | }, 144 | "imports": [ 145 | "./Lib_AddressManager.sol" 146 | ], 147 | "versionPragmas": [ 148 | "^0.8.9" 149 | ], 150 | "artifacts": [ 151 | "Lib_AddressResolver" 152 | ] 153 | }, 154 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/trie/Lib_SecureMerkleTrie.sol": { 155 | "lastModificationDate": 1666957021261, 156 | "contentHash": "9237036ad9eefb061b60db7e0ce18fa0", 157 | "sourceName": "@eth-optimism/contracts/libraries/trie/Lib_SecureMerkleTrie.sol", 158 | "solcConfig": { 159 | "version": "0.8.9", 160 | "settings": { 161 | "optimizer": { 162 | "enabled": false, 163 | "runs": 200 164 | }, 165 | "outputSelection": { 166 | "*": { 167 | "*": [ 168 | "abi", 169 | "evm.bytecode", 170 | "evm.deployedBytecode", 171 | "evm.methodIdentifiers", 172 | "metadata" 173 | ], 174 | "": [ 175 | "ast" 176 | ] 177 | } 178 | } 179 | } 180 | }, 181 | "imports": [ 182 | "./Lib_MerkleTrie.sol" 183 | ], 184 | "versionPragmas": [ 185 | "^0.8.9" 186 | ], 187 | "artifacts": [ 188 | "Lib_SecureMerkleTrie" 189 | ] 190 | }, 191 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/codec/Lib_OVMCodec.sol": { 192 | "lastModificationDate": 1666957021278, 193 | "contentHash": "6e2de6f520efb187c5e01c485675dba8", 194 | "sourceName": "@eth-optimism/contracts/libraries/codec/Lib_OVMCodec.sol", 195 | "solcConfig": { 196 | "version": "0.8.9", 197 | "settings": { 198 | "optimizer": { 199 | "enabled": false, 200 | "runs": 200 201 | }, 202 | "outputSelection": { 203 | "*": { 204 | "*": [ 205 | "abi", 206 | "evm.bytecode", 207 | "evm.deployedBytecode", 208 | "evm.methodIdentifiers", 209 | "metadata" 210 | ], 211 | "": [ 212 | "ast" 213 | ] 214 | } 215 | } 216 | } 217 | }, 218 | "imports": [ 219 | "../rlp/Lib_RLPReader.sol", 220 | "../rlp/Lib_RLPWriter.sol", 221 | "../utils/Lib_BytesUtils.sol", 222 | "../utils/Lib_Bytes32Utils.sol" 223 | ], 224 | "versionPragmas": [ 225 | "^0.8.9" 226 | ], 227 | "artifacts": [ 228 | "Lib_OVMCodec" 229 | ] 230 | }, 231 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/L1/rollup/StateCommitmentChain.sol": { 232 | "lastModificationDate": 1666957021233, 233 | "contentHash": "bec50ce2a25260b388467e81cec4569f", 234 | "sourceName": "@eth-optimism/contracts/L1/rollup/StateCommitmentChain.sol", 235 | "solcConfig": { 236 | "version": "0.8.9", 237 | "settings": { 238 | "optimizer": { 239 | "enabled": false, 240 | "runs": 200 241 | }, 242 | "outputSelection": { 243 | "*": { 244 | "*": [ 245 | "abi", 246 | "evm.bytecode", 247 | "evm.deployedBytecode", 248 | "evm.methodIdentifiers", 249 | "metadata" 250 | ], 251 | "": [ 252 | "ast" 253 | ] 254 | } 255 | } 256 | } 257 | }, 258 | "imports": [ 259 | "../../libraries/codec/Lib_OVMCodec.sol", 260 | "../../libraries/resolver/Lib_AddressResolver.sol", 261 | "../../libraries/utils/Lib_MerkleTree.sol", 262 | "./IStateCommitmentChain.sol", 263 | "./ICanonicalTransactionChain.sol", 264 | "../verification/IBondManager.sol", 265 | "./IChainStorageContainer.sol" 266 | ], 267 | "versionPragmas": [ 268 | "^0.8.9" 269 | ], 270 | "artifacts": [ 271 | "StateCommitmentChain" 272 | ] 273 | }, 274 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/rlp/Lib_RLPReader.sol": { 275 | "lastModificationDate": 1666957021292, 276 | "contentHash": "da043486f683fccce3edacfd5f84d98d", 277 | "sourceName": "@eth-optimism/contracts/libraries/rlp/Lib_RLPReader.sol", 278 | "solcConfig": { 279 | "version": "0.8.9", 280 | "settings": { 281 | "optimizer": { 282 | "enabled": false, 283 | "runs": 200 284 | }, 285 | "outputSelection": { 286 | "*": { 287 | "*": [ 288 | "abi", 289 | "evm.bytecode", 290 | "evm.deployedBytecode", 291 | "evm.methodIdentifiers", 292 | "metadata" 293 | ], 294 | "": [ 295 | "ast" 296 | ] 297 | } 298 | } 299 | } 300 | }, 301 | "imports": [], 302 | "versionPragmas": [ 303 | "^0.8.9" 304 | ], 305 | "artifacts": [ 306 | "Lib_RLPReader" 307 | ] 308 | }, 309 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/utils/Lib_BytesUtils.sol": { 310 | "lastModificationDate": 1666957021262, 311 | "contentHash": "1ebfdb02780beca3c418a3465c33dab1", 312 | "sourceName": "@eth-optimism/contracts/libraries/utils/Lib_BytesUtils.sol", 313 | "solcConfig": { 314 | "version": "0.8.9", 315 | "settings": { 316 | "optimizer": { 317 | "enabled": false, 318 | "runs": 200 319 | }, 320 | "outputSelection": { 321 | "*": { 322 | "*": [ 323 | "abi", 324 | "evm.bytecode", 325 | "evm.deployedBytecode", 326 | "evm.methodIdentifiers", 327 | "metadata" 328 | ], 329 | "": [ 330 | "ast" 331 | ] 332 | } 333 | } 334 | } 335 | }, 336 | "imports": [], 337 | "versionPragmas": [ 338 | "^0.8.9" 339 | ], 340 | "artifacts": [ 341 | "Lib_BytesUtils" 342 | ] 343 | }, 344 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/resolver/Lib_AddressManager.sol": { 345 | "lastModificationDate": 1666957021250, 346 | "contentHash": "684734f3098d69ba634f53ab58eaf4af", 347 | "sourceName": "@eth-optimism/contracts/libraries/resolver/Lib_AddressManager.sol", 348 | "solcConfig": { 349 | "version": "0.8.9", 350 | "settings": { 351 | "optimizer": { 352 | "enabled": false, 353 | "runs": 200 354 | }, 355 | "outputSelection": { 356 | "*": { 357 | "*": [ 358 | "abi", 359 | "evm.bytecode", 360 | "evm.deployedBytecode", 361 | "evm.methodIdentifiers", 362 | "metadata" 363 | ], 364 | "": [ 365 | "ast" 366 | ] 367 | } 368 | } 369 | } 370 | }, 371 | "imports": [ 372 | "@openzeppelin/contracts/access/Ownable.sol" 373 | ], 374 | "versionPragmas": [ 375 | "^0.8.9" 376 | ], 377 | "artifacts": [ 378 | "Lib_AddressManager" 379 | ] 380 | }, 381 | "/Users/makoto/work/ens/op-resolver/node_modules/@openzeppelin/contracts/access/Ownable.sol": { 382 | "lastModificationDate": 1666956992902, 383 | "contentHash": "e436cea06129be2c73cda4b1acc848b5", 384 | "sourceName": "@openzeppelin/contracts/access/Ownable.sol", 385 | "solcConfig": { 386 | "version": "0.8.9", 387 | "settings": { 388 | "optimizer": { 389 | "enabled": false, 390 | "runs": 200 391 | }, 392 | "outputSelection": { 393 | "*": { 394 | "*": [ 395 | "abi", 396 | "evm.bytecode", 397 | "evm.deployedBytecode", 398 | "evm.methodIdentifiers", 399 | "metadata" 400 | ], 401 | "": [ 402 | "ast" 403 | ] 404 | } 405 | } 406 | } 407 | }, 408 | "imports": [ 409 | "../utils/Context.sol" 410 | ], 411 | "versionPragmas": [ 412 | "^0.8.0" 413 | ], 414 | "artifacts": [ 415 | "Ownable" 416 | ] 417 | }, 418 | "/Users/makoto/work/ens/op-resolver/node_modules/@openzeppelin/contracts/utils/Context.sol": { 419 | "lastModificationDate": 1666956992918, 420 | "contentHash": "5f2c5c4b6af2dd4551027144797bc8be", 421 | "sourceName": "@openzeppelin/contracts/utils/Context.sol", 422 | "solcConfig": { 423 | "version": "0.8.9", 424 | "settings": { 425 | "optimizer": { 426 | "enabled": false, 427 | "runs": 200 428 | }, 429 | "outputSelection": { 430 | "*": { 431 | "*": [ 432 | "abi", 433 | "evm.bytecode", 434 | "evm.deployedBytecode", 435 | "evm.methodIdentifiers", 436 | "metadata" 437 | ], 438 | "": [ 439 | "ast" 440 | ] 441 | } 442 | } 443 | } 444 | }, 445 | "imports": [], 446 | "versionPragmas": [ 447 | "^0.8.0" 448 | ], 449 | "artifacts": [ 450 | "Context" 451 | ] 452 | }, 453 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/trie/Lib_MerkleTrie.sol": { 454 | "lastModificationDate": 1666957021261, 455 | "contentHash": "06df0cb2a04ccaa207eff2314e68abec", 456 | "sourceName": "@eth-optimism/contracts/libraries/trie/Lib_MerkleTrie.sol", 457 | "solcConfig": { 458 | "version": "0.8.9", 459 | "settings": { 460 | "optimizer": { 461 | "enabled": false, 462 | "runs": 200 463 | }, 464 | "outputSelection": { 465 | "*": { 466 | "*": [ 467 | "abi", 468 | "evm.bytecode", 469 | "evm.deployedBytecode", 470 | "evm.methodIdentifiers", 471 | "metadata" 472 | ], 473 | "": [ 474 | "ast" 475 | ] 476 | } 477 | } 478 | } 479 | }, 480 | "imports": [ 481 | "../utils/Lib_BytesUtils.sol", 482 | "../rlp/Lib_RLPReader.sol", 483 | "../rlp/Lib_RLPWriter.sol" 484 | ], 485 | "versionPragmas": [ 486 | "^0.8.9" 487 | ], 488 | "artifacts": [ 489 | "Lib_MerkleTrie" 490 | ] 491 | }, 492 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/rlp/Lib_RLPWriter.sol": { 493 | "lastModificationDate": 1666957021292, 494 | "contentHash": "53fff0651143b7a21aa3b90373eace72", 495 | "sourceName": "@eth-optimism/contracts/libraries/rlp/Lib_RLPWriter.sol", 496 | "solcConfig": { 497 | "version": "0.8.9", 498 | "settings": { 499 | "optimizer": { 500 | "enabled": false, 501 | "runs": 200 502 | }, 503 | "outputSelection": { 504 | "*": { 505 | "*": [ 506 | "abi", 507 | "evm.bytecode", 508 | "evm.deployedBytecode", 509 | "evm.methodIdentifiers", 510 | "metadata" 511 | ], 512 | "": [ 513 | "ast" 514 | ] 515 | } 516 | } 517 | } 518 | }, 519 | "imports": [], 520 | "versionPragmas": [ 521 | "^0.8.9" 522 | ], 523 | "artifacts": [ 524 | "Lib_RLPWriter" 525 | ] 526 | }, 527 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/utils/Lib_Bytes32Utils.sol": { 528 | "lastModificationDate": 1666957021253, 529 | "contentHash": "d7f2a52fc5fed34eca6431d9b530995f", 530 | "sourceName": "@eth-optimism/contracts/libraries/utils/Lib_Bytes32Utils.sol", 531 | "solcConfig": { 532 | "version": "0.8.9", 533 | "settings": { 534 | "optimizer": { 535 | "enabled": false, 536 | "runs": 200 537 | }, 538 | "outputSelection": { 539 | "*": { 540 | "*": [ 541 | "abi", 542 | "evm.bytecode", 543 | "evm.deployedBytecode", 544 | "evm.methodIdentifiers", 545 | "metadata" 546 | ], 547 | "": [ 548 | "ast" 549 | ] 550 | } 551 | } 552 | } 553 | }, 554 | "imports": [], 555 | "versionPragmas": [ 556 | "^0.8.9" 557 | ], 558 | "artifacts": [ 559 | "Lib_Bytes32Utils" 560 | ] 561 | }, 562 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/L1/verification/IBondManager.sol": { 563 | "lastModificationDate": 1666957021233, 564 | "contentHash": "587fc0c46c5aea9b1fc715dc8fd2fa9e", 565 | "sourceName": "@eth-optimism/contracts/L1/verification/IBondManager.sol", 566 | "solcConfig": { 567 | "version": "0.8.9", 568 | "settings": { 569 | "optimizer": { 570 | "enabled": false, 571 | "runs": 200 572 | }, 573 | "outputSelection": { 574 | "*": { 575 | "*": [ 576 | "abi", 577 | "evm.bytecode", 578 | "evm.deployedBytecode", 579 | "evm.methodIdentifiers", 580 | "metadata" 581 | ], 582 | "": [ 583 | "ast" 584 | ] 585 | } 586 | } 587 | } 588 | }, 589 | "imports": [], 590 | "versionPragmas": [ 591 | "^0.8.9" 592 | ], 593 | "artifacts": [ 594 | "IBondManager" 595 | ] 596 | }, 597 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/libraries/utils/Lib_MerkleTree.sol": { 598 | "lastModificationDate": 1666957021278, 599 | "contentHash": "b671cbe24ef8f6422fae78d305417889", 600 | "sourceName": "@eth-optimism/contracts/libraries/utils/Lib_MerkleTree.sol", 601 | "solcConfig": { 602 | "version": "0.8.9", 603 | "settings": { 604 | "optimizer": { 605 | "enabled": false, 606 | "runs": 200 607 | }, 608 | "outputSelection": { 609 | "*": { 610 | "*": [ 611 | "abi", 612 | "evm.bytecode", 613 | "evm.deployedBytecode", 614 | "evm.methodIdentifiers", 615 | "metadata" 616 | ], 617 | "": [ 618 | "ast" 619 | ] 620 | } 621 | } 622 | } 623 | }, 624 | "imports": [], 625 | "versionPragmas": [ 626 | "^0.8.9" 627 | ], 628 | "artifacts": [ 629 | "Lib_MerkleTree" 630 | ] 631 | }, 632 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/L1/rollup/IStateCommitmentChain.sol": { 633 | "lastModificationDate": 1666957021233, 634 | "contentHash": "111e1ade1cde6245c261b01683170ef6", 635 | "sourceName": "@eth-optimism/contracts/L1/rollup/IStateCommitmentChain.sol", 636 | "solcConfig": { 637 | "version": "0.8.9", 638 | "settings": { 639 | "optimizer": { 640 | "enabled": false, 641 | "runs": 200 642 | }, 643 | "outputSelection": { 644 | "*": { 645 | "*": [ 646 | "abi", 647 | "evm.bytecode", 648 | "evm.deployedBytecode", 649 | "evm.methodIdentifiers", 650 | "metadata" 651 | ], 652 | "": [ 653 | "ast" 654 | ] 655 | } 656 | } 657 | } 658 | }, 659 | "imports": [ 660 | "../../libraries/codec/Lib_OVMCodec.sol" 661 | ], 662 | "versionPragmas": [ 663 | ">0.5.0 <0.9.0" 664 | ], 665 | "artifacts": [ 666 | "IStateCommitmentChain" 667 | ] 668 | }, 669 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/L1/rollup/ICanonicalTransactionChain.sol": { 670 | "lastModificationDate": 1666957021233, 671 | "contentHash": "7e4b90095962a399dc41c11860440180", 672 | "sourceName": "@eth-optimism/contracts/L1/rollup/ICanonicalTransactionChain.sol", 673 | "solcConfig": { 674 | "version": "0.8.9", 675 | "settings": { 676 | "optimizer": { 677 | "enabled": false, 678 | "runs": 200 679 | }, 680 | "outputSelection": { 681 | "*": { 682 | "*": [ 683 | "abi", 684 | "evm.bytecode", 685 | "evm.deployedBytecode", 686 | "evm.methodIdentifiers", 687 | "metadata" 688 | ], 689 | "": [ 690 | "ast" 691 | ] 692 | } 693 | } 694 | } 695 | }, 696 | "imports": [ 697 | "../../libraries/codec/Lib_OVMCodec.sol", 698 | "./IChainStorageContainer.sol" 699 | ], 700 | "versionPragmas": [ 701 | ">0.5.0 <0.9.0" 702 | ], 703 | "artifacts": [ 704 | "ICanonicalTransactionChain" 705 | ] 706 | }, 707 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/L1/rollup/IChainStorageContainer.sol": { 708 | "lastModificationDate": 1666957021228, 709 | "contentHash": "df41f85a23f3e783857d81c90c1a7e4a", 710 | "sourceName": "@eth-optimism/contracts/L1/rollup/IChainStorageContainer.sol", 711 | "solcConfig": { 712 | "version": "0.8.9", 713 | "settings": { 714 | "optimizer": { 715 | "enabled": false, 716 | "runs": 200 717 | }, 718 | "outputSelection": { 719 | "*": { 720 | "*": [ 721 | "abi", 722 | "evm.bytecode", 723 | "evm.deployedBytecode", 724 | "evm.methodIdentifiers", 725 | "metadata" 726 | ], 727 | "": [ 728 | "ast" 729 | ] 730 | } 731 | } 732 | } 733 | }, 734 | "imports": [], 735 | "versionPragmas": [ 736 | ">0.5.0 <0.9.0" 737 | ], 738 | "artifacts": [ 739 | "IChainStorageContainer" 740 | ] 741 | }, 742 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/L1/rollup/CanonicalTransactionChain.sol": { 743 | "lastModificationDate": 1666957021226, 744 | "contentHash": "39c2a8a44238844a261488a38b3b4ded", 745 | "sourceName": "@eth-optimism/contracts/L1/rollup/CanonicalTransactionChain.sol", 746 | "solcConfig": { 747 | "version": "0.8.9", 748 | "settings": { 749 | "optimizer": { 750 | "enabled": false, 751 | "runs": 200 752 | }, 753 | "outputSelection": { 754 | "*": { 755 | "*": [ 756 | "abi", 757 | "evm.bytecode", 758 | "evm.deployedBytecode", 759 | "evm.methodIdentifiers", 760 | "metadata" 761 | ], 762 | "": [ 763 | "ast" 764 | ] 765 | } 766 | } 767 | } 768 | }, 769 | "imports": [ 770 | "../../standards/AddressAliasHelper.sol", 771 | "../../libraries/codec/Lib_OVMCodec.sol", 772 | "../../libraries/resolver/Lib_AddressResolver.sol", 773 | "./ICanonicalTransactionChain.sol", 774 | "./IChainStorageContainer.sol" 775 | ], 776 | "versionPragmas": [ 777 | "^0.8.9" 778 | ], 779 | "artifacts": [ 780 | "CanonicalTransactionChain" 781 | ] 782 | }, 783 | "/Users/makoto/work/ens/op-resolver/node_modules/@eth-optimism/contracts/standards/AddressAliasHelper.sol": { 784 | "lastModificationDate": 1666956992620, 785 | "contentHash": "dfcebe065f40cb205e841943e2b9b066", 786 | "sourceName": "@eth-optimism/contracts/standards/AddressAliasHelper.sol", 787 | "solcConfig": { 788 | "version": "0.8.9", 789 | "settings": { 790 | "optimizer": { 791 | "enabled": false, 792 | "runs": 200 793 | }, 794 | "outputSelection": { 795 | "*": { 796 | "*": [ 797 | "abi", 798 | "evm.bytecode", 799 | "evm.deployedBytecode", 800 | "evm.methodIdentifiers", 801 | "metadata" 802 | ], 803 | "": [ 804 | "ast" 805 | ] 806 | } 807 | } 808 | } 809 | }, 810 | "imports": [], 811 | "versionPragmas": [ 812 | "^0.8.7" 813 | ], 814 | "artifacts": [ 815 | "AddressAliasHelper" 816 | ] 817 | }, 818 | "/Users/makoto/work/ens/op-resolver/packages/contracts/contracts/l1/dependencies.sol": { 819 | "lastModificationDate": 1666955053474, 820 | "contentHash": "4666e67043d1de643cd8ac1e742969ea", 821 | "sourceName": "contracts/l1/dependencies.sol", 822 | "solcConfig": { 823 | "version": "0.8.9", 824 | "settings": { 825 | "optimizer": { 826 | "enabled": false, 827 | "runs": 200 828 | }, 829 | "outputSelection": { 830 | "*": { 831 | "*": [ 832 | "abi", 833 | "evm.bytecode", 834 | "evm.deployedBytecode", 835 | "evm.methodIdentifiers", 836 | "metadata" 837 | ], 838 | "": [ 839 | "ast" 840 | ] 841 | } 842 | } 843 | } 844 | }, 845 | "imports": [ 846 | "@eth-optimism/contracts/L1/rollup/CanonicalTransactionChain.sol", 847 | "@eth-optimism/contracts/L1/rollup/StateCommitmentChain.sol", 848 | "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol" 849 | ], 850 | "versionPragmas": [], 851 | "artifacts": [] 852 | }, 853 | "/Users/makoto/work/ens/op-resolver/packages/contracts/node_modules/@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol": { 854 | "lastModificationDate": 1666957022704, 855 | "contentHash": "6fe078a9b569a56d86d7ddba70814c89", 856 | "sourceName": "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol", 857 | "solcConfig": { 858 | "version": "0.8.9", 859 | "settings": { 860 | "optimizer": { 861 | "enabled": false, 862 | "runs": 200 863 | }, 864 | "outputSelection": { 865 | "*": { 866 | "*": [ 867 | "abi", 868 | "evm.bytecode", 869 | "evm.deployedBytecode", 870 | "evm.methodIdentifiers", 871 | "metadata" 872 | ], 873 | "": [ 874 | "ast" 875 | ] 876 | } 877 | } 878 | } 879 | }, 880 | "imports": [ 881 | "./ENS.sol" 882 | ], 883 | "versionPragmas": [ 884 | ">=0.8.4" 885 | ], 886 | "artifacts": [ 887 | "ENSRegistry" 888 | ] 889 | }, 890 | "/Users/makoto/work/ens/op-resolver/packages/contracts/node_modules/@ensdomains/ens-contracts/contracts/registry/ENS.sol": { 891 | "lastModificationDate": 1666957022704, 892 | "contentHash": "bbf48c9206c1f48073dbf3d40024bc94", 893 | "sourceName": "@ensdomains/ens-contracts/contracts/registry/ENS.sol", 894 | "solcConfig": { 895 | "version": "0.8.9", 896 | "settings": { 897 | "optimizer": { 898 | "enabled": false, 899 | "runs": 200 900 | }, 901 | "outputSelection": { 902 | "*": { 903 | "*": [ 904 | "abi", 905 | "evm.bytecode", 906 | "evm.deployedBytecode", 907 | "evm.methodIdentifiers", 908 | "metadata" 909 | ], 910 | "": [ 911 | "ast" 912 | ] 913 | } 914 | } 915 | } 916 | }, 917 | "imports": [], 918 | "versionPragmas": [ 919 | ">=0.8.4" 920 | ], 921 | "artifacts": [ 922 | "ENS" 923 | ] 924 | }, 925 | "/Users/makoto/work/ens/op-resolver/packages/contracts/contracts/l2/OptimismResolver.sol": { 926 | "lastModificationDate": 1666955053473, 927 | "contentHash": "ffbd8a4a5c7fcd5b987660d9e0057fd4", 928 | "sourceName": "contracts/l2/OptimismResolver.sol", 929 | "solcConfig": { 930 | "version": "0.8.9", 931 | "settings": { 932 | "optimizer": { 933 | "enabled": false, 934 | "runs": 200 935 | }, 936 | "outputSelection": { 937 | "*": { 938 | "*": [ 939 | "abi", 940 | "evm.bytecode", 941 | "evm.deployedBytecode", 942 | "evm.methodIdentifiers", 943 | "metadata" 944 | ], 945 | "": [ 946 | "ast" 947 | ] 948 | } 949 | } 950 | } 951 | }, 952 | "imports": [ 953 | "@openzeppelin/contracts/access/Ownable.sol" 954 | ], 955 | "versionPragmas": [ 956 | "^0.8.0" 957 | ], 958 | "artifacts": [ 959 | "OptimismResolver" 960 | ] 961 | }, 962 | "/Users/makoto/work/ens/op-resolver/packages/contracts/contracts/l2/Test.sol": { 963 | "lastModificationDate": 1666955053473, 964 | "contentHash": "e962a5740a78f78946e6a4b4881867f6", 965 | "sourceName": "contracts/l2/Test.sol", 966 | "solcConfig": { 967 | "version": "0.8.9", 968 | "settings": { 969 | "optimizer": { 970 | "enabled": false, 971 | "runs": 200 972 | }, 973 | "outputSelection": { 974 | "*": { 975 | "*": [ 976 | "abi", 977 | "evm.bytecode", 978 | "evm.deployedBytecode", 979 | "evm.methodIdentifiers", 980 | "metadata" 981 | ], 982 | "": [ 983 | "ast" 984 | ] 985 | } 986 | } 987 | } 988 | }, 989 | "imports": [], 990 | "versionPragmas": [], 991 | "artifacts": [ 992 | "Test" 993 | ] 994 | } 995 | } 996 | } 997 | --------------------------------------------------------------------------------