├── .eslintrc.json ├── .gitignore ├── .husky └── pre-commit ├── .mocharc.json ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── docgen-templates └── contract.hbs ├── docs └── .gitignore ├── hardhat.config.ts ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── client │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── rollup.config.mjs │ ├── src │ │ ├── api.ts │ │ ├── blockhistory.ts │ │ ├── bridges │ │ │ ├── bridge.ts │ │ │ ├── index.ts │ │ │ ├── optimism.ts │ │ │ └── zksync.ts │ │ ├── client.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ ├── provers │ │ │ ├── accountinfo.ts │ │ │ ├── accountstorage.ts │ │ │ ├── attendance.ts │ │ │ ├── birthcert.ts │ │ │ ├── block.ts │ │ │ ├── cachedmultistorage.ts │ │ │ ├── cachedstorage.ts │ │ │ ├── index.ts │ │ │ ├── log.ts │ │ │ ├── multistorage.ts │ │ │ ├── prover.ts │ │ │ ├── storage.ts │ │ │ ├── transaction.ts │ │ │ └── withdrawal.ts │ │ ├── reliquary.ts │ │ └── utils │ │ │ ├── block.ts │ │ │ ├── facts.ts │ │ │ ├── index.ts │ │ │ ├── network.ts │ │ │ ├── optimism.ts │ │ │ └── storage.ts │ └── tsconfig.json ├── contracts │ ├── .gitignore │ ├── BirthCertificateVerifier.sol │ ├── README.md │ ├── RelicReceiver.sol │ ├── hardhat.config.ts │ ├── interfaces │ │ ├── IAttendanceProver.sol │ │ ├── IBatchProver.sol │ │ ├── IBeaconBlockHistory.sol │ │ ├── IBirthCertificateProver.sol │ │ ├── IBlockHashMessenger.sol │ │ ├── IBlockHistory.sol │ │ ├── IEphemeralFacts.sol │ │ ├── IOptimismNativeBlockHistory.sol │ │ ├── IProver.sol │ │ ├── IProxyBeaconBlockHistory.sol │ │ ├── IProxyBlockHistory.sol │ │ ├── IRelicReceiver.sol │ │ ├── IReliquary.sol │ │ └── IStorageSlotProver.sol │ ├── lib │ │ ├── BirthCertificate.sol │ │ ├── BytesCalldata.sol │ │ ├── CoreTypes.sol │ │ ├── FactSigs.sol │ │ ├── Facts.sol │ │ ├── RLP.sol │ │ └── Storage.sol │ ├── package.json │ ├── scripts │ │ └── pack.sh │ ├── test │ │ ├── BirthCertificateVerifierTest.sol │ │ ├── RelicReceiverTest.sol │ │ └── StorageParseTest.sol │ └── tsconfig.json └── types │ ├── .gitignore │ ├── package.json │ ├── rollup.config.mjs │ ├── src │ ├── client │ │ ├── api.ts │ │ ├── client.ts │ │ ├── index.ts │ │ ├── prover.ts │ │ └── utils.ts │ └── index.ts │ └── tsconfig.json ├── rollup.config.mjs ├── scripts └── test │ ├── clean.sh │ └── prepare.sh ├── test ├── fixtures.ts └── with-hardhat │ ├── birthcert.test.ts │ ├── receiver.test.ts │ └── storage.test.ts ├── tsconfig.json └── typedoc.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["standard-with-typescript", "prettier"], 7 | "overrides": [], 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "ignorePatterns": ["**/dist/*.js"], 13 | "rules": {} 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | packages/*/build 9 | packages/*/dist 10 | 11 | packages/contracts/abi 12 | 13 | # Hardhat files 14 | cache 15 | artifacts 16 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": ["hardhat/register", "test/fixtures.ts"], 3 | "timeout": 40000, 4 | "_": ["test/**/*.test.ts"] 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/dist 2 | docgen-templates/ 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Theori 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 |

5 |

SDK

6 |

7 | 8 |

9 | 10 | ## Getting Started 11 | 12 | Visit [https://docs.relicprotocol.com/sdk/client-sdk/](https://docs.relicprotocol.com/sdk/client-sdk/) to get started with Relic Protocol SDK. 13 | -------------------------------------------------------------------------------- /docgen-templates/contract.hbs: -------------------------------------------------------------------------------- 1 | {{>common}} 2 | 3 | File: `{{__item_context.file.relativePath}}` 4 | 5 | {{#each items}} 6 | {{#hsection}} 7 | {{>item}} 8 | {{/hsection}} 9 | 10 | {{/each}} 11 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | ts-sdk 2 | solidity-sdk 3 | .nojekyll 4 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from 'hardhat/config' 2 | import '@nomiclabs/hardhat-ethers' 3 | import '@nomicfoundation/hardhat-chai-matchers' 4 | import 'solidity-docgen' 5 | 6 | const config: HardhatUserConfig = { 7 | solidity: '0.8.12', 8 | paths: { 9 | sources: './packages/contracts', 10 | }, 11 | networks: { 12 | hardhat: { 13 | chainId: 1, 14 | forking: { 15 | url: process.env.MAINNET_RPC_URL || '', 16 | }, 17 | }, 18 | }, 19 | docgen: { 20 | outputDir: './docs/solidity-sdk', 21 | templates: './docgen-templates/', 22 | pages: 'single', 23 | exclude: ['test'], 24 | }, 25 | } 26 | 27 | export default config 28 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "packages": ["packages/*"], 4 | "useWorkspaces": true, 5 | "version": "0.2.1" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relic-sdk", 3 | "description": "Relic SDK root package", 4 | "scripts": { 5 | "clean": "lerna run clean", 6 | "build": "lerna run build", 7 | "test:mocha": "mocha", 8 | "test": "npm run test:mocha", 9 | "docs": "npx lerna run build && npx typedoc && npx hardhat docgen" 10 | }, 11 | "workspaces": [ 12 | "packages/client", 13 | "packages/contracts", 14 | "packages/types" 15 | ], 16 | "lint-staged": { 17 | "**/*": "prettier --write --ignore-unknown" 18 | }, 19 | "devDependencies": { 20 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.4", 21 | "@nomiclabs/hardhat-ethers": "^2.2.1", 22 | "@rollup/plugin-commonjs": "^24.0.0", 23 | "@rollup/plugin-json": "^6.0.0", 24 | "@rollup/plugin-node-resolve": "^15.0.1", 25 | "@typescript-eslint/eslint-plugin": "^5.40.0", 26 | "eslint": "^8.25.0", 27 | "eslint-config-prettier": "^8.5.0", 28 | "eslint-config-standard-with-typescript": "^23.0.0", 29 | "eslint-plugin-import": "^2.26.0", 30 | "eslint-plugin-n": "^15.3.0", 31 | "eslint-plugin-promise": "^6.1.0", 32 | "hardhat": "^2.12.2", 33 | "husky": "^8.0.1", 34 | "lerna": "^6.0.1", 35 | "lint-staged": "^13.0.3", 36 | "mocha": "^10.1.0", 37 | "prettier": "2.7.1", 38 | "rollup": "^3.10.0", 39 | "rollup-plugin-esbuild": "^5.0.0", 40 | "solidity-docgen": "^0.6.0-beta.34", 41 | "typedoc": "^0.23.24", 42 | "typedoc-plugin-resolve-crossmodule-references": "^0.3.3" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | tsconfig.tsbuildinfo 4 | -------------------------------------------------------------------------------- /packages/client/README.md: -------------------------------------------------------------------------------- 1 | # Relic Client SDK 2 | 3 | The client SDK is designed to simplify fetching proofs from the Relic Prover and generating transaction data to be submitted on-chain for verification. 4 | 5 | ![Relic Architecture Overview](https://miro.medium.com/max/1400/1*c8jPRfDNS_KCQADhBQNhYg.png) 6 | 7 | ## Usage 8 | 9 | Initializing `RelicClient` requires passing an [`ethers` Provider](https://docs.ethers.io/v5/api/providers/). Providers can be created with an RPC url or by [connecting to Metamask](https://docs.ethers.io/v5/getting-started/#getting-started--connecting) or another wallet extension. 10 | 11 | ```typescript 12 | import { AccountNotFound, RelicClient, utils, InfoType } from '@relicprotocol/client' 13 | import { ethers } from 'ethers' 14 | 15 | async function main() { 16 | // Note: you could also get the provider from a browser wallet extension 17 | const provider = new ethers.providers.JsonRpcProvider('[RPC URL here]') 18 | const signer = await provider.getSigner() 19 | 20 | const relic = await RelicClient.fromProvider(provider) 21 | 22 | // prove an account's birth certificate 23 | const account = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' // vitalik.eth 24 | const bcTx = await relic.birthCertificateProver.prove({ account }) 25 | console.log(await provider.estimateGas(bcTx)) 26 | 27 | // use the transaction data... 28 | // to send the proof transaction as is: 29 | // let tx = await signer.sendTransaction(bcTx) 30 | // await tx.wait() 31 | 32 | // prove an account's code hash 33 | // note: other account data fields can be proven by changing the |info| param 34 | const aiTx = await relic.accountInfoProver.prove( 35 | { block, account, info: InfoType.CodeHash } 36 | ) 37 | // use the transaction data... 38 | console.log(await provider.estimateGas(aiTx) 39 | 40 | // prove a storage slot's value, in this case WETH.balanceOf(account) 41 | const blockNum = 15000000 42 | const wethAddr = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' // WETH 43 | const slot = utils.mapElemSlot(3, account) // calculate balanceOf(account) slot 44 | 45 | // you can optionally specify the expected slot value, to ensure the slot is correct 46 | // we'll compute this by calling balanceOf(account) at the target block 47 | const contract = new ethers.Contract( 48 | wethAddr, 49 | ['function balanceOf(address) external view returns (uint256)'], 50 | provider 51 | ) 52 | const expected = await contract.balanceOf(account, { blockTag: blockNum }) 53 | 54 | // expected is optional parameter 55 | const ssTx = await relic.storageSlotProver.prove({ 56 | block: blockNum, 57 | account: wethAddr, 58 | slot, 59 | expected, 60 | }) 61 | 62 | // use the transaction data... 63 | console.log(await provider.estimateGas(ssTx)) 64 | 65 | // You can also prove multiple storage slots in one call to save gas 66 | const ZERO_ADDR = '0x' + '0'.repeat(40) 67 | const slot2 = utils.mapElemSlot(3, ZERO_ADDR) // calculate balanceOf(0x000..00) slot 68 | const expected2 = await contract.balanceOf(ZERO_ADDR, { blockTag: blockNum }) 69 | 70 | // prove two storage slots from the same account simultaneously 71 | const mssTx = await relic.multiStorageSlotProver.prove({ 72 | block: blockNum, 73 | account: wethAddr, 74 | slots: [slot, slot2], 75 | expected: [expected, expected2], 76 | }) 77 | 78 | // use the transaction data... 79 | console.log(await provider.estimateGas(mssTx)) 80 | 81 | // prove the storage root an account in a particular block, 82 | // potentially making slot proofs in that block much cheaper 83 | const asTx = await relic.accountStorageProver.prove({ 84 | block: 15000000, 85 | account: wethAddr, 86 | }) 87 | console.log(await provider.estimateGas(asTx)) 88 | 89 | // once the above transaction is confirmed, you can use cheap cached storage 90 | // slot proofs for that (account, block) 91 | const cssTx = await relic.cachedStorageSlotProver.prove({ 92 | block: blockNum, 93 | account: wethAddr, 94 | slot, 95 | expected, 96 | }) 97 | // use the transaction data... 98 | console.log(await provider.estimateGas(cssTx)) 99 | 100 | // Now let's prove some ephemeral facts; a block header proof and a log proof 101 | // NOTE: you probably don't want to prove these facts without using proveEphemeral, 102 | // because storing these large facts on-chain costs a lot of gas 103 | 104 | // Your contract which implements IRelicReceiver 105 | // Consider using the RelicReceiver base contract in the solidity SDK 106 | const receiver = '0x...' 107 | 108 | // prove a historical block header is valid 109 | const bhTx = await relic.blockHeaderProver.proveEphemeral( 110 | { 111 | initiator: await signer.getAddress(), 112 | receiver, 113 | gasLimit: 50000, // 50000 gas is enough for our receiver callback, be sure to check yours! 114 | }, 115 | { block: 15000000 } 116 | ) 117 | console.log(await signer.estimateGas(bhTx)) 118 | 119 | // get BAYC mint events 120 | // NOTE: this may be very slow if your RPC provider doesn't index logs well 121 | const logs = await provider.getLogs({ 122 | address: '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D', // BAYC contract 123 | topics: [ 124 | '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // Transfer event 125 | '0x0000000000000000000000000000000000000000000000000000000000000000', // from == address(0) 126 | ], 127 | fromBlock: 0, 128 | }) 129 | 130 | // prove the first BAYC mint log 131 | const logTx = await relic.logProver.proveEphemeral( 132 | { 133 | initiator: await signer.getAddress(), 134 | receiver, 135 | gasLimit: 50000, 136 | }, 137 | logs[0] 138 | ) 139 | 140 | // use the transaction data... 141 | console.log(await signer.estimateGas(logTx)) 142 | 143 | // prove the first withdrawal 144 | const withdrawalTx = await relic.withdrawalProver.prove({ 145 | block: 17034871, 146 | idx: 0, 147 | }) 148 | // use the transaction data... 149 | console.log(await signer.estimateGas(withdrawalTx)) 150 | 151 | // prove a transaction was included 152 | const receipt = await provider.getTransactionReceipt(logs[0].transactionHash) 153 | const txTx = await relic.transactionProver.prove(receipt) 154 | 155 | // use the transaction data... 156 | console.log(await signer.estimateGas(txTx)) 157 | 158 | // prove a beacon chain withdrawal occured 159 | const withdrawalTx = await relic.withdrawalProver.prove( 160 | { block: 17034871, idx: 0 } // first withdrawal in the first shapella block 161 | ) 162 | // use the transaction data... 163 | console.log(await signer.estimateGas(withdrawalTx)) 164 | 165 | 166 | // demonstrate error handling 167 | try { 168 | const randomAddr = ethers.utils.hexlify(ethers.utils.randomBytes(20)) 169 | await relic.birthCertificateProver.prove({ account: randomAddr }) 170 | } catch (error: any) { 171 | if (!(error instanceof AccountNotFound)) throw error 172 | // handle account not found 173 | } 174 | } 175 | 176 | main() 177 | ``` 178 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@relicprotocol/client", 3 | "description": "Client SDK for interacting with Relic Protocol", 4 | "version": "0.2.1", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "unpkg": "dist/index.umd.js", 8 | "types": "dist/types/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "types": "./dist/types/index.d.ts", 12 | "import": "./dist/index.es.js", 13 | "require": "./dist/index.cjs.js" 14 | } 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "scripts": { 20 | "clean": "rm -rf *.tsbuildinfo;", 21 | "build:pre": "npm run clean; rm -rf dist;", 22 | "build:types": "tsc", 23 | "build:source": "rollup --config rollup.config.mjs", 24 | "build": "npm run clean; npm run build:pre; npm run build:source && npm run build:types; npm run clean;", 25 | "prepare": "npm run build" 26 | }, 27 | "devDependencies": { 28 | "ts-node": "^10.9.1", 29 | "typescript": "^4.8.4" 30 | }, 31 | "dependencies": { 32 | "@eth-optimism/sdk": "^3.1.6", 33 | "@relicprotocol/contracts": "^0.2.0", 34 | "@relicprotocol/types": "^0.2.0", 35 | "axios": "^1.3.2", 36 | "axios-retry": "^3.4.0", 37 | "rlp": "^3.0.0", 38 | "typescript-memoize": "^1.1.1", 39 | "zksync-web3": "^0.16.0" 40 | }, 41 | "peerDependencies": { 42 | "ethers": "5.x" 43 | }, 44 | "keywords": [ 45 | "relic", 46 | "reliquary", 47 | "client", 48 | "sdk", 49 | "historical", 50 | "state", 51 | "query" 52 | ], 53 | "homepage": "https://docs.relicprotocol.com/", 54 | "license": "MIT", 55 | "repository": { 56 | "type": "git", 57 | "url": "https://github.com/Relic-Protocol/relic-sdk" 58 | }, 59 | "typedoc": { 60 | "entryPoint": "src/index.ts" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/client/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' assert { type: 'json' } 2 | import createConfig from '../../rollup.config.mjs' 3 | 4 | export default createConfig( 5 | 'Relic', 6 | Object.keys({ 7 | ...pkg.dependencies, 8 | ...pkg.peerDependencies, 9 | }), 10 | ['ethers', 'axios'] 11 | ) 12 | -------------------------------------------------------------------------------- /packages/client/src/api.ts: -------------------------------------------------------------------------------- 1 | import { API_ERROR_MAP, RelicError, UnknownError } from './errors' 2 | 3 | import type { 4 | AccountProof, 5 | AttendanceProof, 6 | BlockProof, 7 | LogProof, 8 | StorageSlotProof, 9 | TransactionProof, 10 | WithdrawalProof, 11 | ErrorResult, 12 | RelicAddresses, 13 | } from '@relicprotocol/types' 14 | 15 | import { ethers } from 'ethers' 16 | import axios, { 17 | AxiosError, 18 | AxiosInstance, 19 | AxiosRequestConfig, 20 | AxiosResponse, 21 | } from 'axios' 22 | 23 | import axiosRetry from 'axios-retry' 24 | 25 | function makeError( 26 | response: AxiosResponse | undefined 27 | ): RelicError { 28 | if (!response) { 29 | throw new UnknownError('No response data from API') 30 | } 31 | const constructor = API_ERROR_MAP[response.data.error] 32 | return constructor ? new constructor() : new UnknownError(response.data.error) 33 | } 34 | 35 | export class RelicAPI { 36 | private instance: AxiosInstance 37 | private baseBlock: number | undefined 38 | 39 | constructor(apiUrl: string) { 40 | this.instance = axios.create({ baseURL: apiUrl }) 41 | 42 | // exponential backoff when rate limited 43 | axiosRetry(this.instance, { 44 | retries: 10, 45 | retryDelay: axiosRetry.exponentialDelay, 46 | retryCondition: (error) => error.response?.status == 429, 47 | }) 48 | } 49 | 50 | private _fetch(req: AxiosRequestConfig): Promise { 51 | if (this.baseBlock !== undefined) { 52 | req.params = { 53 | ...(req.params || {}), 54 | baseBlock: this.baseBlock.toString(), 55 | } 56 | } 57 | return this.instance 58 | .request(req) 59 | .then(({ data }: AxiosResponse) => data) 60 | .catch((error: AxiosError) => { 61 | throw makeError(error.response) 62 | }) 63 | } 64 | 65 | setBaseBlock(baseBlock: number) { 66 | this.baseBlock = baseBlock 67 | } 68 | 69 | accountProof( 70 | block: ethers.providers.BlockTag, 71 | address: string 72 | ): Promise { 73 | return this._fetch({ 74 | method: 'get', 75 | url: `/account/${block}/${ethers.utils.getAddress(address)}`, 76 | }) 77 | } 78 | 79 | addresses(chainId: number): Promise { 80 | return this._fetch({ 81 | method: 'get', 82 | url: `/addresses/${chainId}`, 83 | }) 84 | } 85 | 86 | attendanceProof( 87 | address: string, 88 | eventId: ethers.BigNumberish, 89 | code: string 90 | ): Promise { 91 | return this._fetch({ 92 | method: 'get', 93 | url: '/attendance', 94 | params: { 95 | event: ethers.BigNumber.from(eventId).toString(), 96 | code: code, 97 | account: ethers.utils.getAddress(address), 98 | }, 99 | }) 100 | } 101 | 102 | birthCertificateProof(address: string): Promise { 103 | return this._fetch({ 104 | method: 'get', 105 | url: `/birthcert/${ethers.utils.getAddress(address)}`, 106 | }) 107 | } 108 | 109 | blockProof(block: ethers.providers.BlockTag): Promise { 110 | return this._fetch({ 111 | method: 'get', 112 | url: `/block/${block}`, 113 | }) 114 | } 115 | 116 | logProof( 117 | block: ethers.providers.BlockTag, 118 | txIdx: number, 119 | logIdx: number 120 | ): Promise { 121 | return this._fetch({ 122 | method: 'get', 123 | url: `/log/${block}/${txIdx}/${logIdx}`, 124 | }) 125 | } 126 | 127 | storageSlotProof( 128 | block: ethers.providers.BlockTag, 129 | address: string, 130 | slot: ethers.BigNumberish 131 | ): Promise { 132 | return this._fetch({ 133 | method: 'get', 134 | url: `/storage/${block}/${ethers.utils.getAddress( 135 | address 136 | )}/${ethers.utils.hexlify(slot)}`, 137 | }) 138 | } 139 | 140 | transactionProof( 141 | block: ethers.providers.BlockTag, 142 | txIdx: number 143 | ): Promise { 144 | return this._fetch({ 145 | method: 'get', 146 | url: `/transaction/${block}/${txIdx}`, 147 | }) 148 | } 149 | 150 | withdrawalProof( 151 | block: ethers.providers.BlockTag, 152 | idx: number 153 | ): Promise { 154 | return this._fetch({ 155 | method: 'get', 156 | url: `/withdrawal/${block}/${idx}`, 157 | }) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /packages/client/src/blockhistory.ts: -------------------------------------------------------------------------------- 1 | import type { RelicConfig } from '@relicprotocol/types' 2 | import { ethers } from 'ethers' 3 | import { BlockProof } from '@relicprotocol/types' 4 | import { abi as mainAbi } from '@relicprotocol/contracts/abi/IBlockHistory.json' 5 | import { abi as proxyAbi } from '@relicprotocol/contracts/abi/IProxyBlockHistory.json' 6 | import { abi as opNativeAbi } from '@relicprotocol/contracts/abi/IOptimismNativeBlockHistory.json' 7 | import { abi as beaconAbi } from '@relicprotocol/contracts/abi/IBeaconBlockHistory.json' 8 | import { abi as proxyBeaconAbi } from '@relicprotocol/contracts/abi/IProxyBeaconBlockHistory.json' 9 | import { RelicClient } from './client' 10 | import { 11 | blockForTimestamp, 12 | blockNumberToChunk, 13 | getLogs, 14 | getOutputRootProof, 15 | hashOutputRootProof, 16 | isL1ChainId, 17 | isL2ChainId, 18 | isOptimismChainId, 19 | isProxyL2Deployment, 20 | slotToTimestamp, 21 | timestampToSlot, 22 | toBytes32, 23 | } from './utils' 24 | import { 25 | BlockNotVerifiable, 26 | L1BlockHashNotAccessible, 27 | NotL1Network, 28 | NotNativeL2, 29 | } from './errors' 30 | 31 | const TRUSTED_HASH_PROOF = '0x01' 32 | const PRECOMITTED_BLOCK_PROOF = '0x02' 33 | 34 | const SLOTS_PER_HISTORICAL_ROOT = 8192 35 | 36 | function getAbi(chainId: number, dataChainId: number): any { 37 | if (isProxyL2Deployment(chainId, dataChainId)) { 38 | return proxyAbi 39 | } else if (isL2ChainId(chainId)) { 40 | return opNativeAbi 41 | } else { 42 | return mainAbi 43 | } 44 | } 45 | 46 | function getBeaconAbi(chainId: number, dataChainId: number): any { 47 | if (isProxyL2Deployment(chainId, dataChainId)) { 48 | return proxyBeaconAbi 49 | } else { 50 | return beaconAbi 51 | } 52 | } 53 | 54 | const NEGATIVE_ONE = ethers.BigNumber.from(-1) 55 | 56 | function max(vals: Array) { 57 | return vals.reduce((l, r) => (l.gt(r) ? l : r)) 58 | } 59 | 60 | export interface IBlockHistory { 61 | getContract(): ethers.Contract 62 | getLastVerifiableBlock(): Promise 63 | ensureValidProof(proof: BlockProof): Promise 64 | canVerifyBlock(block: ethers.providers.BlockTag): Promise 65 | waitUntilVerifiable(block: ethers.providers.BlockTag): Promise 66 | commitRecent( 67 | blockNum: ethers.BigNumberish 68 | ): Promise 69 | commitCurrentL1BlockHash(): Promise 70 | } 71 | 72 | export class BlockHistory implements IBlockHistory { 73 | private client: RelicClient 74 | private contract: ethers.Contract 75 | private merkleRootCache: Record 76 | private trustedCache: Record 77 | 78 | constructor(client: RelicClient) { 79 | this.client = client 80 | const abi = getAbi(client.chainId, client.dataChainId) 81 | this.contract = new ethers.Contract( 82 | client.addresses.legacyBlockHistory || client.addresses.blockHistory, 83 | abi, 84 | client.provider 85 | ) 86 | this.merkleRootCache = {} 87 | this.trustedCache = {} 88 | } 89 | 90 | getContract(): ethers.Contract { 91 | return this.contract 92 | } 93 | 94 | async merkleRootForBlock(blockNum: number): Promise { 95 | const chunk = blockNumberToChunk(blockNum) 96 | let root = this.merkleRootCache[chunk] 97 | if (root) { 98 | return root 99 | } 100 | const filter = this.contract.filters.ImportMerkleRoot(chunk) 101 | const logs = await getLogs(this.contract.provider, { 102 | ...filter, 103 | fromBlock: blockNum, 104 | }) 105 | if (logs.length == 0) { 106 | return null 107 | } 108 | root = logs.pop()!.data.substring(0, 66) 109 | this.merkleRootCache[chunk] = root 110 | return root 111 | } 112 | 113 | async isTrustedHash(blockNum: number, blockHash: string): Promise { 114 | if (!this.contract.filters.TrustedBlockHash) { 115 | return false 116 | } 117 | if (this.trustedCache[blockNum] == toBytes32(blockHash)) { 118 | return true 119 | } 120 | const trusted = await this.contract 121 | .validBlockHash(blockHash, blockNum, TRUSTED_HASH_PROOF, { 122 | from: this.client.addresses.reliquary, 123 | }) 124 | .catch(() => false) 125 | if (trusted) { 126 | this.trustedCache[blockNum] = toBytes32(blockHash) 127 | } 128 | return trusted 129 | } 130 | 131 | async canVerifyBlock(block: ethers.providers.BlockTag): Promise { 132 | const header = await this.client.dataProvider.getBlock(block) 133 | const [root, trusted] = await Promise.all([ 134 | this.merkleRootForBlock(header.number), 135 | this.isTrustedHash(header.number, header.hash), 136 | ]) 137 | return root != null || trusted 138 | } 139 | 140 | async validBlockHash( 141 | hash: string, 142 | number: ethers.BigNumberish, 143 | proof: string 144 | ): Promise { 145 | return this.contract.validBlockHash(hash, number, proof) 146 | } 147 | 148 | async ensureValidProof(proof: BlockProof): Promise { 149 | let number = proof.blockNum 150 | let hash = ethers.utils.keccak256(proof.header) 151 | 152 | // if the merkle root is imported, leave the proof as-is 153 | if ((await this.merkleRootForBlock(number)) != null) { 154 | return 155 | } 156 | 157 | // if the block is a known trusted hash, replace the proof 158 | if (await this.isTrustedHash(number, hash)) { 159 | proof.blockProof = TRUSTED_HASH_PROOF 160 | return 161 | } 162 | 163 | // otherwise, the proof is not verifiable 164 | throw new BlockNotVerifiable(number, this.client.chainId) 165 | } 166 | 167 | async getLastMerkleRootBlock() { 168 | if (!this.contract.filters.ImportMerkleRoot) { 169 | return NEGATIVE_ONE 170 | } 171 | const logs = await getLogs( 172 | this.contract.provider, 173 | this.contract.filters.ImportMerkleRoot() 174 | ) 175 | if (logs.length == 0) { 176 | return NEGATIVE_ONE 177 | } 178 | const vals = logs.map((l) => { 179 | const rootIdx = ethers.BigNumber.from(logs[logs.length - 1].topics[1]) 180 | return rootIdx.add(1).mul(8192).sub(1) 181 | }) 182 | return max(vals) 183 | } 184 | 185 | async getLastTrustedBlock() { 186 | if (!this.contract.filters.TrustedBlockHash) { 187 | return NEGATIVE_ONE 188 | } 189 | const logs = await getLogs( 190 | this.contract.provider, 191 | this.contract.filters.TrustedBlockHash() 192 | ) 193 | if (logs.length == 0) { 194 | return NEGATIVE_ONE 195 | } 196 | const vals = logs.map((l) => { 197 | const [blockNum] = ethers.utils.defaultAbiCoder.decode( 198 | ['uint256', 'bytes32'], 199 | logs[logs.length - 1].data 200 | ) 201 | return blockNum 202 | }) 203 | return max(vals) 204 | } 205 | 206 | async getLastPrecomiitedBlock() { 207 | if (!this.contract.filters.PrecomittedBlock) { 208 | return NEGATIVE_ONE 209 | } 210 | const logs = await getLogs( 211 | this.contract.provider, 212 | this.contract.filters.PrecomittedBlock() 213 | ) 214 | if (logs.length == 0) { 215 | return NEGATIVE_ONE 216 | } 217 | const vals = logs.map((l) => { 218 | return ethers.BigNumber.from(logs[logs.length - 1].topics[1]) 219 | }) 220 | return max(vals) 221 | } 222 | 223 | async getLastVerifiableBlock() { 224 | const vals = await Promise.all([ 225 | this.getLastMerkleRootBlock(), 226 | this.getLastTrustedBlock(), 227 | this.getLastPrecomiitedBlock(), 228 | ]) 229 | // return the max 230 | return max(vals) 231 | } 232 | 233 | async commitRecent( 234 | blockNum: ethers.BigNumberish 235 | ): Promise { 236 | if ( 237 | this.client.chainId != this.client.dataChainId || 238 | !isL2ChainId(this.client.chainId) 239 | ) { 240 | throw new NotNativeL2(this.client.chainId, this.client.dataChainId) 241 | } 242 | return this.contract.populateTransaction.commitRecent(blockNum) 243 | } 244 | 245 | async importBlockhashFromOutputRoot( 246 | l2BlockNumber: ethers.BigNumberish, 247 | l1Provider: ethers.providers.Provider, 248 | proxyConfigOverride?: Partial 249 | ): Promise { 250 | if ( 251 | this.client.chainId != this.client.dataChainId || 252 | !isL2ChainId(this.client.chainId) 253 | ) { 254 | throw new NotNativeL2(this.client.chainId, this.client.dataChainId) 255 | } 256 | 257 | l2BlockNumber = ethers.BigNumber.from(l2BlockNumber) 258 | const proxyClient = await RelicClient.fromProviders( 259 | this.client.provider, 260 | l1Provider, 261 | proxyConfigOverride 262 | ) 263 | if (isL2ChainId(proxyClient.dataChainId)) { 264 | throw new NotL1Network(proxyClient.dataChainId) 265 | } 266 | const l2OutputOracle = new ethers.Contract( 267 | await this.contract.l2OutputOracle(), 268 | [ 269 | 'function SUBMISSION_INTERVAL() external view returns (uint256)', 270 | 'function FINALIZATION_PERIOD_SECONDS() external view returns (uint256)', 271 | 'function startingBlockNumber() external view returns (uint256)', 272 | 'function getL2Output(uint256) external view returns (bytes32,uint128,uint128)', 273 | ], 274 | l1Provider 275 | ) 276 | const [interval, startingBlockNumber, base, block] = await Promise.all([ 277 | l2OutputOracle.SUBMISSION_INTERVAL(), 278 | l2OutputOracle.startingBlockNumber(), 279 | this.contract.OUTPUT_ROOTS_BASE_SLOT(), 280 | proxyClient.blockHistory.getLastVerifiableBlock(), 281 | ]) 282 | if (l2BlockNumber.lt(startingBlockNumber.add(interval))) { 283 | throw new Error( 284 | `the given l2 block number is below first stored output root` 285 | ) 286 | } 287 | if (!l2BlockNumber.sub(startingBlockNumber).mod(interval).eq(0)) { 288 | throw new Error( 289 | `l2 block number must be a multiple of SUBMISSION_INTERVAL (${interval})` 290 | ) 291 | } 292 | const index = l2BlockNumber.sub(startingBlockNumber).div(interval).sub(1) 293 | 294 | const [[, submissionTimestamp], l1BlockTimestamp, finalization] = 295 | await Promise.all([ 296 | l2OutputOracle.getL2Output(index), 297 | l1Provider.getBlock(block.toNumber()).then((b) => b.timestamp), 298 | l2OutputOracle.FINALIZATION_PERIOD_SECONDS(), 299 | ]) 300 | if (submissionTimestamp.add(finalization).gt(l1BlockTimestamp)) { 301 | throw new Error( 302 | `checkpoint block is not finalized in most recent verifiable L1 block` 303 | ) 304 | } 305 | 306 | const account = l2OutputOracle.address 307 | const l2OutputRootProof = await getOutputRootProof( 308 | this.client.provider, 309 | l2BlockNumber.toNumber() 310 | ) 311 | const slot = ethers.BigNumber.from(ethers.utils.keccak256(base)).add( 312 | index.mul(2) 313 | ) 314 | const slots = [slot, slot.add(1)] 315 | const expected = [hashOutputRootProof(l2OutputRootProof), undefined] 316 | const { proof } = await proxyClient.multiStorageSlotProver.getProofData({ 317 | account, 318 | slots, 319 | block: block.toNumber(), 320 | expected, 321 | includeHeader: true, 322 | }) 323 | 324 | return this.contract.populateTransaction.importCheckpointBlockFromL1( 325 | proof, 326 | index, 327 | block, 328 | l2OutputRootProof 329 | ) 330 | } 331 | 332 | async waitUntilVerifiable(block: ethers.providers.BlockTag): Promise { 333 | const header = await this.client.dataProvider.getBlock(block) 334 | const filter = this.contract.filters.TrustedBlockHash() 335 | const fromBlock = await blockForTimestamp( 336 | this.client.provider, 337 | header.timestamp 338 | ).then((b) => b.number) 339 | const isTargetHash = (hash: string) => { 340 | return hash == header.hash 341 | } 342 | return new Promise(async (res) => { 343 | const listener = (_: ethers.BigNumber, blockHash: string) => { 344 | if (isTargetHash(blockHash)) { 345 | this.contract.off(filter, listener) 346 | res() 347 | } 348 | } 349 | this.contract.on(filter, listener) 350 | 351 | // query if verifiable after setting up listeners (to avoid races) 352 | if (await this.canVerifyBlock(block)) { 353 | this.contract.off(filter, listener) 354 | res() 355 | } 356 | }) 357 | } 358 | 359 | async commitCurrentL1BlockHash(): Promise { 360 | if ( 361 | !isOptimismChainId(this.client.chainId) || 362 | !isL1ChainId(this.client.dataChainId) 363 | ) { 364 | throw new L1BlockHashNotAccessible(this.client.chainId) 365 | } 366 | return this.contract.populateTransaction.commitCurrentL1BlockHash() 367 | } 368 | } 369 | 370 | export class BeaconBlockHistory implements IBlockHistory { 371 | private client: RelicClient 372 | private preDencunBlockHistory: BlockHistory 373 | private contract: ethers.Contract 374 | private merkleRootCache: Record 375 | private precomittedCache: Record 376 | 377 | constructor(client: RelicClient) { 378 | this.client = client 379 | this.preDencunBlockHistory = new BlockHistory(client) 380 | this.contract = new ethers.Contract( 381 | client.addresses.blockHistory, 382 | getBeaconAbi(this.client.chainId, this.client.dataChainId), 383 | client.provider 384 | ) 385 | this.merkleRootCache = {} 386 | this.precomittedCache = {} 387 | } 388 | 389 | getContract(): ethers.Contract { 390 | return this.contract 391 | } 392 | 393 | async summarySlotForBlock( 394 | block: ethers.providers.Block 395 | ): Promise { 396 | const slot = timestampToSlot(block.timestamp, this.client.dataChainId) 397 | const summarySlot = slot + 8192 - (slot % SLOTS_PER_HISTORICAL_ROOT) 398 | const filter = this.contract.filters.ImportBlockSummary(summarySlot) 399 | const { number: fromBlock } = await blockForTimestamp( 400 | this.client.provider, 401 | block.timestamp 402 | ) 403 | const logs = await getLogs(this.contract.provider, { 404 | ...filter, 405 | fromBlock, 406 | }) 407 | if (logs.length == 0) { 408 | return null 409 | } 410 | return summarySlot 411 | } 412 | 413 | async isPrecomitted(blockNum: number, blockHash: string): Promise { 414 | if (!this.contract.filters.PrecomittedBlock) { 415 | return false 416 | } 417 | if (this.precomittedCache[blockNum] == toBytes32(blockHash)) { 418 | return true 419 | } 420 | const precomitted = await this.contract 421 | .validBlockHash(blockHash, blockNum, PRECOMITTED_BLOCK_PROOF, { 422 | from: this.client.addresses.reliquary, 423 | }) 424 | .catch(() => false) 425 | if (precomitted) { 426 | this.precomittedCache[blockNum] = toBytes32(blockHash) 427 | } 428 | return precomitted 429 | } 430 | 431 | async canVerifyBlock(block: ethers.providers.BlockTag): Promise { 432 | const header = await this.client.dataProvider.getBlock(block) 433 | const [root, precomitted] = await Promise.all([ 434 | this.summarySlotForBlock(header), 435 | this.isPrecomitted(header.number, header.hash), 436 | ]) 437 | return root != null || precomitted 438 | } 439 | 440 | async validBlockHash( 441 | hash: string, 442 | number: ethers.BigNumberish, 443 | proof: string 444 | ): Promise { 445 | return this.contract.validBlockHash(hash, number, proof) 446 | } 447 | 448 | async ensureValidProof(proof: BlockProof): Promise { 449 | let number = proof.blockNum 450 | let hash = ethers.utils.keccak256(proof.header) 451 | 452 | if (number < (await this.contract.UPGRADE_BLOCK())) { 453 | return this.preDencunBlockHistory.ensureValidProof(proof) 454 | } 455 | 456 | // if the block is a known precommitted hash, replace the proof 457 | if (await this.isPrecomitted(number, hash)) { 458 | proof.blockProof = PRECOMITTED_BLOCK_PROOF 459 | return 460 | } 461 | 462 | // otherwise ensure the provided proof verifies 463 | const valid = await this.contract 464 | .validBlockHash(hash, number, proof.blockProof, { 465 | from: this.client.addresses.reliquary, 466 | }) 467 | .catch(() => false) 468 | if (valid) { 469 | return 470 | } 471 | 472 | // otherwise, the proof is not verifiable 473 | throw new BlockNotVerifiable(number, this.client.chainId) 474 | } 475 | 476 | async getLastSummaryBlock() { 477 | if (!this.contract.filters.ImportBlockSummary) { 478 | return NEGATIVE_ONE 479 | } 480 | const logs = await getLogs( 481 | this.contract.provider, 482 | this.contract.filters.ImportBlockSummary() 483 | ) 484 | if (logs.length == 0) { 485 | return NEGATIVE_ONE 486 | } 487 | const vals = logs.map((l) => { 488 | const slot = ethers.BigNumber.from(logs[logs.length - 1].topics[1]) 489 | return slot.sub(1) 490 | }) 491 | const maxSlot = max(vals) 492 | const maxTimestamp = slotToTimestamp( 493 | maxSlot.toNumber(), 494 | this.client.dataChainId 495 | ) 496 | const maxBlock = await blockForTimestamp( 497 | this.client.dataProvider, 498 | maxTimestamp 499 | ) 500 | return ethers.BigNumber.from(maxBlock.number) 501 | } 502 | 503 | async getLastPrecomiitedBlock() { 504 | if (!this.contract.filters.PrecomittedBlock) { 505 | return NEGATIVE_ONE 506 | } 507 | const logs = await getLogs( 508 | this.contract.provider, 509 | this.contract.filters.PrecomittedBlock() 510 | ) 511 | if (logs.length == 0) { 512 | return NEGATIVE_ONE 513 | } 514 | const vals = logs.map((l) => { 515 | return ethers.BigNumber.from(logs[logs.length - 1].topics[1]) 516 | }) 517 | return max(vals) 518 | } 519 | 520 | async getLastVerifiableBlock() { 521 | const vals = await Promise.all([ 522 | this.preDencunBlockHistory.getLastVerifiableBlock(), 523 | this.getLastSummaryBlock(), 524 | this.getLastPrecomiitedBlock(), 525 | ]) 526 | // return the max 527 | return max(vals) 528 | } 529 | 530 | async commitRecent( 531 | blockNum: ethers.BigNumberish 532 | ): Promise { 533 | if ( 534 | this.client.chainId != this.client.dataChainId || 535 | isL2ChainId(this.client.chainId) 536 | ) { 537 | throw new NotL1Network(this.client.dataChainId) 538 | } 539 | return this.contract.populateTransaction.commitRecent(blockNum) 540 | } 541 | 542 | async waitUntilVerifiable(block: ethers.providers.BlockTag): Promise { 543 | const header = await this.client.dataProvider.getBlock(block) 544 | const filter = this.contract.filters.PrecomittedBlock(header.number) 545 | return new Promise(async (res) => { 546 | const listener = () => { 547 | this.contract.off(filter, listener) 548 | res() 549 | } 550 | this.contract.on(filter, listener) 551 | 552 | // query if verifiable after setting up listeners (to avoid races) 553 | if (await this.canVerifyBlock(block)) { 554 | this.contract.off(filter, listener) 555 | res() 556 | } 557 | }) 558 | } 559 | 560 | async commitCurrentL1BlockHash(): Promise { 561 | if ( 562 | !isOptimismChainId(this.client.chainId) || 563 | !isL1ChainId(this.client.dataChainId) 564 | ) { 565 | throw new L1BlockHashNotAccessible(this.client.chainId) 566 | } 567 | return this.contract.populateTransaction.commitCurrentL1BlockHash() 568 | } 569 | } 570 | -------------------------------------------------------------------------------- /packages/client/src/bridges/bridge.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { abi as messengerAbi } from '@relicprotocol/contracts/abi/IBlockHashMessenger.json' 3 | import { RelicClient } from '../client' 4 | import { BridgeNotNecessary } from '../errors' 5 | 6 | export interface MessengerParams { 7 | value: ethers.BigNumberish 8 | params: string 9 | } 10 | 11 | export abstract class Bridge { 12 | readonly client: RelicClient 13 | readonly messenger: ethers.Contract 14 | 15 | constructor(client: RelicClient) { 16 | this.client = client 17 | this.messenger = new ethers.Contract( 18 | client.addresses.messenger, 19 | messengerAbi, 20 | client.dataProvider 21 | ) 22 | } 23 | 24 | abstract getParams( 25 | blockNum: ethers.BigNumberish, 26 | blockHash: string 27 | ): Promise 28 | 29 | async waitUntilBridged(block: ethers.providers.BlockTag): Promise { 30 | await this.client.blockHistory.waitUntilVerifiable(block) 31 | } 32 | 33 | async sendBlock( 34 | block: ethers.providers.BlockTag, 35 | force?: boolean 36 | ): Promise { 37 | const header = await this.client.dataProvider.getBlock(block) 38 | if (!force) { 39 | const notNecessary = await this.client.blockHistory.canVerifyBlock( 40 | header.number 41 | ) 42 | if (notNecessary) { 43 | throw new BridgeNotNecessary(block) 44 | } 45 | } 46 | const proof = await this.client.api.blockProof(block) 47 | const { value, params } = await this.getParams(header.number, header.hash) 48 | return this.messenger.populateTransaction.sendBlockHash( 49 | this.client.addresses.blockHistory, 50 | params, 51 | header.number, 52 | header.hash, 53 | proof.blockProof, 54 | { value } 55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/client/src/bridges/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bridge' 2 | export * from './zksync' 3 | export * from './optimism' 4 | -------------------------------------------------------------------------------- /packages/client/src/bridges/optimism.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { MessengerParams, Bridge } from './bridge' 3 | import { RelicClient } from '../client' 4 | 5 | import { utils as zksyncUtils } from 'zksync-web3' 6 | 7 | export class OptimismBridge extends Bridge { 8 | constructor(client: RelicClient) { 9 | super(client) 10 | } 11 | 12 | override async getParams( 13 | blockNum: ethers.BigNumberish, 14 | blockHash: string 15 | ): Promise { 16 | const contract = this.client.blockHistory.getContract() 17 | const call = await contract.populateTransaction.importTrustedHash( 18 | blockNum, 19 | blockHash 20 | ) 21 | const l2GasLimit = await this.client.provider.estimateGas({ 22 | ...call, 23 | from: zksyncUtils.applyL1ToL2Alias(this.messenger.address), 24 | }) 25 | 26 | const params = ethers.utils.defaultAbiCoder.encode(['uint64'], [l2GasLimit]) 27 | const value = 0 28 | 29 | return { value, params } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/client/src/bridges/zksync.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { MessengerParams, Bridge } from './bridge' 3 | import { RelicClient } from '../client' 4 | import { getConnectionInfo } from '../utils' 5 | 6 | import { Provider, Wallet, utils as zksyncUtils } from 'zksync-web3' 7 | 8 | export class ZkSyncBridge extends Bridge { 9 | private zkWallet: Wallet 10 | 11 | constructor(client: RelicClient) { 12 | super(client) 13 | const provider = new Provider(getConnectionInfo(client.provider)) 14 | // Wallet is needed for some methods, but we don't care about the priv key 15 | this.zkWallet = Wallet.createRandom() 16 | .connect(provider) 17 | .connectToL1(this.client.dataProvider) 18 | } 19 | 20 | override async getParams( 21 | blockNum: ethers.BigNumberish, 22 | blockHash: string 23 | ): Promise { 24 | const contract = this.client.blockHistory.getContract() 25 | const call = await contract.populateTransaction.importTrustedHash( 26 | blockNum, 27 | blockHash 28 | ) 29 | const request = { 30 | contractAddress: call.to!, 31 | calldata: call.data!, 32 | caller: zksyncUtils.applyL1ToL2Alias(this.messenger.address), 33 | } 34 | const l2GasLimit = await this.zkWallet.provider.estimateL1ToL2Execute( 35 | request 36 | ) 37 | 38 | const l2Tx = await this.zkWallet.getRequestExecuteTx(request) 39 | const value = l2Tx.value! 40 | 41 | const l2GasPerPubdataByteLimit = 42 | zksyncUtils.REQUIRED_L1_TO_L2_GAS_PER_PUBDATA_LIMIT 43 | const params = ethers.utils.defaultAbiCoder.encode( 44 | ['uint256', 'uint256'], 45 | [l2GasLimit, l2GasPerPubdataByteLimit] 46 | ) 47 | 48 | return { value, params } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/client/src/client.ts: -------------------------------------------------------------------------------- 1 | import type { RelicConfig, RelicAddresses } from '@relicprotocol/types' 2 | import { Memoize } from 'typescript-memoize' 3 | import { ethers } from 'ethers' 4 | 5 | import { RelicAPI } from './api' 6 | import { NoBridger, InvalidDataProvider, UnsupportedNetwork } from './errors' 7 | import { 8 | AccountInfoProver, 9 | AccountStorageProver, 10 | AttendanceProver, 11 | BirthCertificateProver, 12 | BlockHeaderProver, 13 | CachedMultiStorageSlotProver, 14 | CachedStorageSlotProver, 15 | LogProver, 16 | MultiStorageSlotProver, 17 | StorageSlotProver, 18 | TransactionProver, 19 | WithdrawalProver, 20 | } from './provers' 21 | 22 | import { Reliquary } from './reliquary' 23 | import { IBlockHistory, BlockHistory, BeaconBlockHistory } from './blockhistory' 24 | import { Bridge, OptimismBridge, ZkSyncBridge } from './bridges' 25 | import { 26 | ChainId, 27 | isZkSyncChainId, 28 | isOptimismChainId, 29 | isL2ChainId, 30 | } from './utils' 31 | 32 | // chainId -> dataChainId -> apiUrl 33 | const defaultAPI: Record> = { 34 | [ChainId.EthMainnet]: { 35 | [ChainId.EthMainnet]: 'https://api.mainnet.relicprotocol.com/v1', 36 | }, 37 | [ChainId.EthSepolia]: { 38 | [ChainId.EthSepolia]: 'https://api.sepolia.relicprotocol.com/v1', 39 | }, 40 | [ChainId.ZkSyncMainnet]: { 41 | [ChainId.EthMainnet]: 'https://api.mainnet.relicprotocol.com/v1', 42 | }, 43 | [ChainId.ZkSyncSepolia]: { 44 | [ChainId.EthSepolia]: 'https://api.sepolia.relicprotocol.com/v1', 45 | }, 46 | [ChainId.OpMainnet]: { 47 | [ChainId.EthMainnet]: 'https://api.mainnet.relicprotocol.com/v1', 48 | [ChainId.OpMainnet]: 'https://api.optimism-mainnet.relicprotocol.com/v1', 49 | }, 50 | [ChainId.OpSepolia]: { 51 | [ChainId.EthSepolia]: 'https://api.sepolia.relicprotocol.com/v1', 52 | [ChainId.OpSepolia]: 'https://api.optimism-sepolia.relicprotocol.com/v1', 53 | }, 54 | [ChainId.BaseMainnet]: { 55 | [ChainId.EthMainnet]: 'https://api.mainnet.relicprotocol.com/v1', 56 | [ChainId.BaseMainnet]: 'https://api.base-mainnet.relicprotocol.com/v1', 57 | }, 58 | [ChainId.BaseSepolia]: { 59 | [ChainId.EthSepolia]: 'https://api.sepolia.relicprotocol.com/v1', 60 | [ChainId.BaseMainnet]: 'https://api.base-sepolia.relicprotocol.com/v1', 61 | }, 62 | } 63 | 64 | export class RelicClient { 65 | readonly provider: ethers.providers.Provider 66 | readonly dataProvider: ethers.providers.Provider 67 | readonly api: RelicAPI 68 | readonly addresses: RelicAddresses 69 | readonly chainId: number 70 | readonly dataChainId: number 71 | 72 | constructor( 73 | provider: ethers.providers.Provider, 74 | config: RelicConfig, 75 | dataProvider: ethers.providers.Provider, 76 | chainId: number, 77 | dataChainId: number 78 | ) { 79 | this.provider = provider 80 | this.dataProvider = dataProvider 81 | this.addresses = config.addresses 82 | this.chainId = chainId 83 | this.dataChainId = dataChainId 84 | this.api = new RelicAPI(config.apiUrl) 85 | } 86 | 87 | static async fromProviders( 88 | provider: ethers.providers.Provider, 89 | dataProvider: ethers.providers.Provider, 90 | configOverride?: Partial 91 | ) { 92 | const chainId = await provider.getNetwork().then((n) => n.chainId) 93 | const defaults = defaultAPI[chainId] 94 | if (defaults === undefined) { 95 | throw new UnsupportedNetwork() 96 | } 97 | const dataChainId = await dataProvider.getNetwork().then((n) => n.chainId) 98 | const apiUrl = configOverride?.apiUrl || defaults[dataChainId] 99 | if (apiUrl === undefined) { 100 | throw new InvalidDataProvider(chainId, Object.keys(defaults)) 101 | } 102 | 103 | const addresses = 104 | configOverride?.addresses || 105 | (await new RelicAPI(apiUrl).addresses(chainId)) 106 | 107 | const config: RelicConfig = { apiUrl, addresses } 108 | return new RelicClient(provider, config, dataProvider, chainId, dataChainId) 109 | } 110 | 111 | static async fromProvider( 112 | provider: ethers.providers.Provider, 113 | configOverride?: Partial 114 | ) { 115 | return this.fromProviders(provider, provider, configOverride) 116 | } 117 | 118 | @Memoize() 119 | get reliquary(): Reliquary { 120 | return new Reliquary(this) 121 | } 122 | 123 | @Memoize() 124 | get blockHistory(): IBlockHistory { 125 | if (this.addresses.legacyBlockHistory) { 126 | return new BeaconBlockHistory(this) 127 | } else { 128 | return new BlockHistory(this) 129 | } 130 | } 131 | 132 | @Memoize() 133 | get bridge(): Bridge { 134 | if (isZkSyncChainId(this.chainId) && !isL2ChainId(this.dataChainId)) { 135 | return new ZkSyncBridge(this) 136 | } else if ( 137 | isOptimismChainId(this.chainId) && 138 | !isL2ChainId(this.dataChainId) 139 | ) { 140 | return new OptimismBridge(this) 141 | } 142 | throw new NoBridger(this.chainId, this.dataChainId) 143 | } 144 | 145 | @Memoize() 146 | get accountInfoProver(): AccountInfoProver { 147 | return new AccountInfoProver(this) 148 | } 149 | 150 | @Memoize() 151 | get accountStorageProver(): AccountStorageProver { 152 | return new AccountStorageProver(this) 153 | } 154 | 155 | @Memoize() 156 | get attendanceProver(): AttendanceProver { 157 | return new AttendanceProver(this) 158 | } 159 | 160 | @Memoize() 161 | get birthCertificateProver(): BirthCertificateProver { 162 | return new BirthCertificateProver(this) 163 | } 164 | 165 | @Memoize() 166 | get blockHeaderProver(): BlockHeaderProver { 167 | return new BlockHeaderProver(this) 168 | } 169 | 170 | @Memoize() 171 | get cachedMultiStorageSlotProver(): CachedMultiStorageSlotProver { 172 | return new CachedMultiStorageSlotProver(this) 173 | } 174 | 175 | @Memoize() 176 | get cachedStorageSlotProver(): CachedStorageSlotProver { 177 | return new CachedStorageSlotProver(this) 178 | } 179 | 180 | @Memoize() 181 | get logProver(): LogProver { 182 | return new LogProver(this) 183 | } 184 | 185 | @Memoize() 186 | get multiStorageSlotProver(): MultiStorageSlotProver { 187 | return new MultiStorageSlotProver(this) 188 | } 189 | 190 | @Memoize() 191 | get storageSlotProver(): StorageSlotProver { 192 | return new StorageSlotProver(this) 193 | } 194 | 195 | @Memoize() 196 | get transactionProver(): TransactionProver { 197 | return new TransactionProver(this) 198 | } 199 | 200 | @Memoize() 201 | get withdrawalProver(): WithdrawalProver { 202 | return new WithdrawalProver(this) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /packages/client/src/errors.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, BigNumberish, ethers } from 'ethers' 2 | import { hexValue } from 'ethers/lib/utils' 3 | 4 | export class RelicError extends Error { 5 | constructor(message: string) { 6 | super(message) 7 | this.name = 'RelicError' 8 | } 9 | } 10 | 11 | export class UnknownError extends RelicError { 12 | constructor(message: string) { 13 | super(message) 14 | } 15 | } 16 | 17 | export class UnsupportedNetwork extends RelicError { 18 | constructor() { 19 | super('unsupported network') 20 | } 21 | } 22 | 23 | export class InvalidDataProvider extends RelicError { 24 | constructor(executionChainId: number, dataChainIds: Array) { 25 | super( 26 | `network ${executionChainId} requires a dataProvider for one ` + 27 | `of the following networks: ${dataChainIds}` 28 | ) 29 | } 30 | } 31 | 32 | export class SlotValueMismatch extends RelicError { 33 | constructor(value: BigNumber, expected: BigNumber) { 34 | super( 35 | `slot value didn't match expected: ${value.toHexString()} vs ${expected.toHexString()}` 36 | ) 37 | } 38 | } 39 | 40 | export class TransactionHashMismatch extends RelicError { 41 | constructor(value: BigNumberish, expected: BigNumberish) { 42 | super( 43 | `tx hash didn't match expected: ${hexValue(value)} vs ${hexValue( 44 | expected 45 | )}` 46 | ) 47 | } 48 | } 49 | 50 | export class NoBridger extends RelicError { 51 | constructor(chainId: number, dataChainId: number) { 52 | super( 53 | `network ${chainId} with data network ${dataChainId} does not have a bridger` 54 | ) 55 | } 56 | } 57 | 58 | export class L1BlockHashNotAccessible extends RelicError { 59 | constructor(chainId: number) { 60 | super(`network ${chainId} doesn't expose L1 block hash`) 61 | } 62 | } 63 | 64 | export class NotNativeL2 extends RelicError { 65 | constructor(chainId: number, dataChainId: number) { 66 | super( 67 | `network ${chainId} with data network ${dataChainId} is not a native L2 config` 68 | ) 69 | } 70 | } 71 | 72 | export class NotL1Network extends RelicError { 73 | constructor(chainId: number) { 74 | super(`${chainId} is not an L1 network`) 75 | } 76 | } 77 | 78 | export class UnsupportedProvider extends RelicError { 79 | constructor(provider: ethers.providers.Provider) { 80 | const providerType = typeof provider 81 | super(`provider type ${providerType} is not supported`) 82 | } 83 | } 84 | 85 | export class BridgeNotNecessary extends RelicError { 86 | constructor(block: ethers.providers.BlockTag) { 87 | super( 88 | `block ${block} does not need to be bridged; it can already be verified. ` + 89 | `call this function with force=true to override this check` 90 | ) 91 | } 92 | } 93 | 94 | export class BlockNotVerifiable extends RelicError { 95 | constructor(block: ethers.providers.BlockTag, chainId: number) { 96 | super( 97 | `block ${block} is not verifiable on chainId ${chainId}. ` + 98 | `you may need to wait for a merkle root import, or bridge ` + 99 | `the block hash manually using RelicClient.bridge.sendBlock` 100 | ) 101 | } 102 | } 103 | 104 | export class TimestampAfterCurrent extends RelicError { 105 | constructor(timestamp: number) { 106 | super(`timestamp ${timestamp} is after current block's timestamp`) 107 | } 108 | } 109 | 110 | export class UnexpectedSlotTime extends RelicError { 111 | constructor(timestamp: number) { 112 | super(`block timestamp ${timestamp} is unexpected`) 113 | } 114 | } 115 | 116 | enum ErrorMsg { 117 | ACCOUNT_NOT_FOUND = 'Account info not available', 118 | INVALID_EVENTID = 'Invalid eventId', 119 | EVENTID_NOT_FOUND = 'EventId not found', 120 | EVENT_CODE_MISTYPED = 'Event code mistyped', 121 | EVENT_CODE_NOT_FOUND = 'Event code not found', 122 | INVALID_BLOCK = 'Error parsing request query', 123 | BLOCKNUM_NOT_FOUND = 'Block number not available', 124 | BLOCK_NOT_FOUND = 'Block header not available', 125 | INVALID_ROOT = 'Invalid root number', 126 | INVALID_ARG = 'Invalid argument', 127 | ROOT_NOT_FOUND = 'Root not found', 128 | } 129 | 130 | type Msg = keyof typeof ErrorMsg 131 | 132 | export abstract class RelicApiError extends RelicError { 133 | constructor(err: Msg) { 134 | super(ErrorMsg[err]) 135 | } 136 | } 137 | 138 | export class AccountNotFound extends RelicApiError { 139 | constructor() { 140 | super('ACCOUNT_NOT_FOUND') 141 | } 142 | } 143 | export class InvalidEventId extends RelicApiError { 144 | constructor() { 145 | super('INVALID_EVENTID') 146 | } 147 | } 148 | export class EventIdNotFound extends RelicApiError { 149 | constructor() { 150 | super('EVENTID_NOT_FOUND') 151 | } 152 | } 153 | export class EventCodeMistyped extends RelicApiError { 154 | constructor() { 155 | super('EVENT_CODE_MISTYPED') 156 | } 157 | } 158 | export class EventCodeNotFound extends RelicApiError { 159 | constructor() { 160 | super('EVENT_CODE_NOT_FOUND') 161 | } 162 | } 163 | export class InvalidBlock extends RelicApiError { 164 | constructor() { 165 | super('INVALID_BLOCK') 166 | } 167 | } 168 | export class BlockNumNotFound extends RelicApiError { 169 | constructor() { 170 | super('BLOCKNUM_NOT_FOUND') 171 | } 172 | } 173 | export class BlockNotFound extends RelicApiError { 174 | constructor() { 175 | super('BLOCK_NOT_FOUND') 176 | } 177 | } 178 | export class InvalidRoot extends RelicApiError { 179 | constructor() { 180 | super('INVALID_ROOT') 181 | } 182 | } 183 | export class RootNotFound extends RelicApiError { 184 | constructor() { 185 | super('ROOT_NOT_FOUND') 186 | } 187 | } 188 | export class InvalidArgument extends RelicApiError { 189 | constructor() { 190 | super('INVALID_ARG') 191 | } 192 | } 193 | 194 | const errorClassMap: { [m in Msg]: new () => RelicApiError } = { 195 | ACCOUNT_NOT_FOUND: AccountNotFound, 196 | INVALID_EVENTID: InvalidEventId, 197 | EVENTID_NOT_FOUND: EventIdNotFound, 198 | EVENT_CODE_MISTYPED: EventCodeMistyped, 199 | EVENT_CODE_NOT_FOUND: EventCodeNotFound, 200 | INVALID_BLOCK: InvalidBlock, 201 | BLOCKNUM_NOT_FOUND: BlockNumNotFound, 202 | BLOCK_NOT_FOUND: BlockNotFound, 203 | INVALID_ROOT: InvalidRoot, 204 | INVALID_ARG: InvalidArgument, 205 | ROOT_NOT_FOUND: RootNotFound, 206 | } 207 | 208 | export const API_ERROR_MAP: Record RelicApiError> = 209 | Object.fromEntries( 210 | (Object.keys(ErrorMsg) as Array).map((err) => [ 211 | ErrorMsg[err], 212 | errorClassMap[err], 213 | ]) 214 | ) 215 | -------------------------------------------------------------------------------- /packages/client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api' 2 | export * from './client' 3 | export * from './reliquary' 4 | export * from './errors' 5 | export * from './bridges' 6 | export * from './blockhistory' 7 | 8 | export * from './provers' 9 | 10 | import * as utils from './utils/' 11 | export { utils } 12 | -------------------------------------------------------------------------------- /packages/client/src/provers/accountinfo.ts: -------------------------------------------------------------------------------- 1 | import { ethers, utils as ethersUtils } from 'ethers' 2 | 3 | import { EphemeralProverImpl, ProofData } from './prover' 4 | import { RelicClient } from '../client' 5 | 6 | import { InvalidArgument, utils } from '../' 7 | 8 | export enum InfoType { 9 | StorageRoot, 10 | CodeHash, 11 | Balance, 12 | Nonce, 13 | RawHeader, 14 | } 15 | 16 | export interface AccountInfoParams { 17 | block: ethers.providers.BlockTag 18 | account: string 19 | info: InfoType 20 | } 21 | 22 | export class AccountInfoProver extends EphemeralProverImpl { 23 | constructor(client: RelicClient) { 24 | super(client, 'accountInfoProver') 25 | } 26 | 27 | override async getProofData(params: AccountInfoParams): Promise { 28 | const proof = await this.api.accountProof(params.block, params.account) 29 | 30 | const proofData = ethersUtils.defaultAbiCoder.encode( 31 | ['address', 'bytes', 'bytes', 'bytes', 'uint256'], 32 | [ 33 | proof.account, 34 | proof.accountProof, 35 | proof.header, 36 | proof.blockProof, 37 | params.info, 38 | ] 39 | ) 40 | 41 | var sigData: string 42 | switch (params.info) { 43 | case InfoType.StorageRoot: 44 | sigData = utils.accountStorageSigData(proof.blockNum, proof.storageHash) 45 | break 46 | case InfoType.CodeHash: 47 | sigData = utils.accountCodeHashSigData(proof.blockNum, proof.codeHash) 48 | break 49 | case InfoType.Balance: 50 | sigData = utils.accountBalanceSigData(proof.blockNum) 51 | break 52 | case InfoType.Nonce: 53 | sigData = utils.accountNonceSigData(proof.blockNum) 54 | break 55 | case InfoType.RawHeader: 56 | sigData = utils.accountSigData(proof.blockNum) 57 | break 58 | default: 59 | throw new InvalidArgument() 60 | } 61 | 62 | return { 63 | proof: proofData, 64 | sigData: sigData, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/client/src/provers/accountstorage.ts: -------------------------------------------------------------------------------- 1 | import { ethers, utils as ethersUtils } from 'ethers' 2 | 3 | import { RelicClient } from '../client' 4 | import { EphemeralProverImpl, ProofData } from './prover' 5 | 6 | import { utils } from '../' 7 | 8 | export interface AccountStorageParams { 9 | block: ethers.providers.BlockTag 10 | account: string 11 | } 12 | 13 | export class AccountStorageProver extends EphemeralProverImpl { 14 | constructor(client: RelicClient) { 15 | super(client, 'accountStorageProver') 16 | } 17 | 18 | override async getProofData( 19 | params: AccountStorageParams 20 | ): Promise { 21 | const proof = await this.api.accountProof(params.block, params.account) 22 | 23 | const proofData = ethersUtils.defaultAbiCoder.encode( 24 | ['address', 'bytes', 'bytes', 'bytes'], 25 | [proof.account, proof.accountProof, proof.header, proof.blockProof] 26 | ) 27 | 28 | return { 29 | proof: proofData, 30 | sigData: utils.accountStorageSigData(proof.blockNum, proof.storageHash), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/client/src/provers/attendance.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { abi } from '@relicprotocol/contracts/abi/IAttendanceProver.json' 3 | import { Prover } from '@relicprotocol/types' 4 | 5 | import { RelicClient } from '../client' 6 | 7 | export interface AttendanceParams { 8 | account: string 9 | eventId: ethers.BigNumberish 10 | code: string 11 | } 12 | 13 | export class AttendanceProver implements Prover { 14 | readonly client: RelicClient 15 | readonly contract: ethers.Contract 16 | 17 | constructor(client: RelicClient) { 18 | this.client = client 19 | this.contract = new ethers.Contract( 20 | client.addresses.attendanceProver, 21 | abi, 22 | client.provider 23 | ) 24 | } 25 | 26 | async prove(params: AttendanceParams): Promise { 27 | const proof = await this.client.api.attendanceProof( 28 | params.account, 29 | params.eventId, 30 | params.code 31 | ) 32 | return await this.contract.populateTransaction.claim( 33 | proof.account, 34 | proof.eventId, 35 | proof.number, 36 | proof.signatureInner, 37 | proof.signatureOuter 38 | ) 39 | } 40 | 41 | fee(): Promise { 42 | return this.client.reliquary.getFee(this.contract.address) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/client/src/provers/birthcert.ts: -------------------------------------------------------------------------------- 1 | import { utils as ethersUtils } from 'ethers' 2 | 3 | import { RelicClient } from '../client' 4 | import { EphemeralProverImpl, ProofData } from './prover' 5 | 6 | import { utils } from '../' 7 | 8 | export interface BirthCertificateParams { 9 | account: string 10 | } 11 | 12 | export class BirthCertificateProver extends EphemeralProverImpl { 13 | constructor(client: RelicClient) { 14 | super(client, 'birthCertificateProver') 15 | } 16 | 17 | override async getProofData( 18 | params: BirthCertificateParams 19 | ): Promise { 20 | const proof = await this.api.birthCertificateProof(params.account) 21 | 22 | const proofData = ethersUtils.defaultAbiCoder.encode( 23 | ['address', 'bytes', 'bytes', 'bytes'], 24 | [proof.account, proof.accountProof, proof.header, proof.blockProof] 25 | ) 26 | return { 27 | proof: proofData, 28 | sigData: utils.birthCertificateSigData(), 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/client/src/provers/block.ts: -------------------------------------------------------------------------------- 1 | import { ethers, utils as ethersUtils } from 'ethers' 2 | 3 | import { RelicClient } from '../client' 4 | import { EphemeralProverImpl, ProofData } from './prover' 5 | 6 | import { utils } from '../' 7 | 8 | export interface BlockHeaderParams { 9 | block: ethers.providers.BlockTag 10 | } 11 | 12 | export class BlockHeaderProver extends EphemeralProverImpl { 13 | constructor(client: RelicClient) { 14 | super(client, 'blockHeaderProver') 15 | } 16 | 17 | override async getProofData(params: BlockHeaderParams): Promise { 18 | const proof = await this.api.blockProof(params.block) 19 | 20 | const proofData = ethersUtils.defaultAbiCoder.encode( 21 | ['bytes', 'bytes'], 22 | [proof.header, proof.blockProof] 23 | ) 24 | return { 25 | proof: proofData, 26 | sigData: utils.blockHeaderSigData(proof.blockNum), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/client/src/provers/cachedmultistorage.ts: -------------------------------------------------------------------------------- 1 | import { utils as ethersUtils } from 'ethers' 2 | import RLP from 'rlp' 3 | 4 | import { BatchProverImpl, BatchProofData } from './prover' 5 | import { RelicClient } from '../client' 6 | import { MultiStorageSlotParams } from './multistorage' 7 | 8 | import { RelicError, utils } from '..' 9 | 10 | export class CachedMultiStorageSlotProver extends BatchProverImpl { 11 | constructor(client: RelicClient) { 12 | super(client, 'cachedMultiStorageSlotProver') 13 | } 14 | 15 | override async getProofData( 16 | params: MultiStorageSlotParams 17 | ): Promise { 18 | if (params.includeHeader) { 19 | throw new RelicError( 20 | "CachedMultiStorageSlotProver doesn't support includeHeader" 21 | ) 22 | } 23 | 24 | const [accProof, proofs] = await Promise.all([ 25 | this.api.accountProof(params.block, params.account), 26 | Promise.all( 27 | params.slots.map((s) => 28 | this.api.storageSlotProof(params.block, params.account, s) 29 | ) 30 | ), 31 | ]) 32 | 33 | if (params.expected !== undefined) { 34 | for (let i = 0; i < params.expected.length; i++) { 35 | let expected = params.expected[i] 36 | if (expected !== undefined) { 37 | utils.assertSlotValue(proofs[i].slotValue, expected) 38 | } 39 | } 40 | } 41 | 42 | // split each slot proof into its nodes 43 | const splitProofs = proofs.map((proof) => { 44 | let result: Array = [] 45 | let slotProof = ethersUtils.arrayify(proof.slotProof) 46 | while (slotProof.length > 0) { 47 | let { data, remainder } = RLP.decode(slotProof, true) 48 | result.push(ethersUtils.hexlify(RLP.encode(data))) 49 | slotProof = remainder 50 | } 51 | return result 52 | }) 53 | 54 | // build the unique set of nodes from all the proofs 55 | const nodeSet = Array.from( 56 | new Set(new Array().concat(...splitProofs)) 57 | ) 58 | 59 | // compress the slot proofs by referencing the unique set of nodes 60 | const slotProofs = ethersUtils.hexlify( 61 | RLP.encode( 62 | splitProofs.map((proof) => { 63 | return proof.map((node) => nodeSet.indexOf(node)) 64 | }) 65 | ) 66 | ) 67 | 68 | // concatencate the set of nodes 69 | const proofNodes = ethersUtils.concat(nodeSet) 70 | 71 | const proofData = ethersUtils.defaultAbiCoder.encode( 72 | ['address', 'uint256', 'uint256', 'bytes', 'uint256[]', 'bytes'], 73 | [ 74 | accProof.account, 75 | accProof.blockNum, 76 | accProof.storageHash, 77 | proofNodes, 78 | params.slots, 79 | slotProofs, 80 | ] 81 | ) 82 | 83 | let sigDatas = proofs.map((p) => 84 | utils.storageSlotSigData(p.slot, accProof.blockNum) 85 | ) 86 | return { 87 | proof: proofData, 88 | sigDatas, 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/client/src/provers/cachedstorage.ts: -------------------------------------------------------------------------------- 1 | import { utils as ethersUtils } from 'ethers' 2 | 3 | import { EphemeralProverImpl, ProofData } from './prover' 4 | import { RelicClient } from '../client' 5 | import { StorageSlotParams } from './storage' 6 | 7 | import { utils } from '../' 8 | 9 | export class CachedStorageSlotProver extends EphemeralProverImpl { 10 | constructor(client: RelicClient) { 11 | super(client, 'cachedStorageSlotProver') 12 | } 13 | 14 | override async getProofData(params: StorageSlotParams): Promise { 15 | const ssProof = await this.api.storageSlotProof( 16 | params.block, 17 | params.account, 18 | params.slot 19 | ) 20 | 21 | const accProof = await this.api.accountProof(params.block, params.account) 22 | 23 | if (typeof params.expected !== 'undefined') { 24 | utils.assertSlotValue(ssProof.slotValue, params.expected) 25 | } 26 | 27 | const proofData = ethersUtils.defaultAbiCoder.encode( 28 | ['address', 'uint256', 'bytes32', 'bytes32', 'bytes'], 29 | [ 30 | ssProof.account, 31 | accProof.blockNum, 32 | accProof.storageHash, 33 | ssProof.slot, 34 | ssProof.slotProof, 35 | ] 36 | ) 37 | 38 | return { 39 | proof: proofData, 40 | sigData: utils.storageSlotSigData(ssProof.slot, accProof.blockNum), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/client/src/provers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './accountinfo' 2 | export * from './accountstorage' 3 | export * from './attendance' 4 | export * from './birthcert' 5 | export * from './block' 6 | export * from './cachedmultistorage' 7 | export * from './cachedstorage' 8 | export * from './log' 9 | export * from './multistorage' 10 | export * from './prover' 11 | export * from './storage' 12 | export * from './transaction' 13 | export * from './withdrawal' 14 | -------------------------------------------------------------------------------- /packages/client/src/provers/log.ts: -------------------------------------------------------------------------------- 1 | import { ethers, utils as ethersUtils } from 'ethers' 2 | 3 | import { RelicClient } from '../client' 4 | import { EphemeralProverImpl, ProofData } from './prover' 5 | 6 | import { utils } from '../' 7 | 8 | type Params = ethers.providers.Log 9 | 10 | export class LogProver extends EphemeralProverImpl { 11 | constructor(client: RelicClient) { 12 | super(client, 'logProver') 13 | } 14 | 15 | override async getProofData(log: Params): Promise { 16 | // get the index of the log inside the transaction 17 | const receipt = await this.client.dataProvider.getTransactionReceipt( 18 | log.transactionHash 19 | ) 20 | const logIdx = log.logIndex - receipt.logs[0].logIndex 21 | 22 | const proof = await this.api.logProof( 23 | log.blockHash, 24 | log.transactionIndex, 25 | logIdx 26 | ) 27 | 28 | const proofData = ethersUtils.defaultAbiCoder.encode( 29 | ['uint256', 'uint256', 'bytes', 'bytes', 'bytes'], 30 | [ 31 | proof.txIdx, 32 | proof.logIdx, 33 | proof.receiptProof, 34 | proof.header, 35 | proof.blockProof, 36 | ] 37 | ) 38 | 39 | return { 40 | proof: proofData, 41 | sigData: utils.logSigData(proof.blockNum, proof.txIdx, proof.logIdx), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/client/src/provers/multistorage.ts: -------------------------------------------------------------------------------- 1 | import { ethers, utils as ethersUtils } from 'ethers' 2 | import RLP from 'rlp' 3 | 4 | import { BatchProverImpl, BatchProofData } from './prover' 5 | import { RelicClient } from '../client' 6 | 7 | import { utils } from '../' 8 | 9 | export interface MultiStorageSlotParams { 10 | block: ethers.providers.BlockTag 11 | account: string 12 | includeHeader?: boolean 13 | slots: Array 14 | expected?: Array 15 | } 16 | 17 | export class MultiStorageSlotProver extends BatchProverImpl { 18 | constructor(client: RelicClient) { 19 | super(client, 'multiStorageSlotProver') 20 | } 21 | 22 | override async getProofData( 23 | params: MultiStorageSlotParams 24 | ): Promise { 25 | const [accProof, proofs] = await Promise.all([ 26 | this.api.accountProof(params.block, params.account), 27 | Promise.all( 28 | params.slots.map((s) => 29 | this.api.storageSlotProof(params.block, params.account, s) 30 | ) 31 | ), 32 | ]) 33 | 34 | if (params.expected !== undefined) { 35 | for (let i = 0; i < params.expected.length; i++) { 36 | let expected = params.expected[i] 37 | if (expected !== undefined) { 38 | utils.assertSlotValue(proofs[i].slotValue, expected) 39 | } 40 | } 41 | } 42 | 43 | // split each slot proof into its nodes 44 | const splitProofs = proofs.map((proof) => { 45 | let result: Array = [] 46 | let slotProof = ethersUtils.arrayify(proof.slotProof) 47 | while (slotProof.length > 0) { 48 | let { data, remainder } = RLP.decode(slotProof, true) 49 | result.push(ethersUtils.hexlify(RLP.encode(data))) 50 | slotProof = remainder 51 | } 52 | return result 53 | }) 54 | 55 | // build the unique set of nodes from all the proofs 56 | const nodeSet = Array.from( 57 | new Set(new Array().concat(...splitProofs)) 58 | ) 59 | 60 | // compress the slot proofs by referencing the unique set of nodes 61 | const slotProofs = ethersUtils.hexlify( 62 | RLP.encode( 63 | splitProofs.map((proof) => { 64 | return proof.map((node) => nodeSet.indexOf(node)) 65 | }) 66 | ) 67 | ) 68 | 69 | // concatencate the set of nodes 70 | const proofNodes = ethersUtils.concat(nodeSet) 71 | 72 | const proofData = ethersUtils.defaultAbiCoder.encode( 73 | [ 74 | 'address', 75 | 'bytes', 76 | 'bytes', 77 | 'bytes', 78 | 'bytes', 79 | 'uint256[]', 80 | 'bytes', 81 | 'bool', 82 | ], 83 | [ 84 | accProof.account, 85 | accProof.accountProof, 86 | accProof.header, 87 | accProof.blockProof, 88 | proofNodes, 89 | params.slots, 90 | slotProofs, 91 | params.includeHeader || false, 92 | ] 93 | ) 94 | 95 | let sigDatas = proofs.map((p) => 96 | utils.storageSlotSigData(p.slot, accProof.blockNum) 97 | ) 98 | return { 99 | proof: proofData, 100 | sigDatas, 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/client/src/provers/prover.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { abi as proverAbi } from '@relicprotocol/contracts/abi/IProver.json' 3 | import { abi as batchProverAbi } from '@relicprotocol/contracts/abi/IBatchProver.json' 4 | import { abi as ephemeralFactsAbi } from '@relicprotocol/contracts/abi/IEphemeralFacts.json' 5 | import { 6 | BlockProof, 7 | Prover, 8 | EphemeralProver, 9 | RelicAddresses, 10 | } from '@relicprotocol/types' 11 | 12 | import type { RelicClient } from '../client' 13 | import { RelicAPI } from '../api' 14 | import { isProxyL2Deployment } from '../utils' 15 | 16 | export interface ReceiverContext { 17 | initiator: string 18 | receiver: string 19 | extra?: string 20 | gasLimit: ethers.BigNumberish 21 | } 22 | 23 | export type ProofData = { 24 | proof: string 25 | sigData: string 26 | } 27 | 28 | export type BatchProofData = { 29 | proof: string 30 | sigDatas: Array 31 | } 32 | 33 | // wrap a RelicAPI with one that ensures blockProofs can be 34 | // validated on the target chain 35 | function wrapAPI(client: RelicClient): RelicAPI { 36 | if (!isProxyL2Deployment(client.chainId, client.dataChainId)) { 37 | return client.api 38 | } 39 | 40 | function isBlockProof(obj: any): obj is BlockProof { 41 | return (obj.blockNum && obj.header && obj.blockProof) !== undefined 42 | } 43 | 44 | return new Proxy(client.api, { 45 | get(target: RelicAPI, p: keyof RelicAPI) { 46 | if (p.startsWith('_')) return target[p] 47 | if (target[p] instanceof Function) { 48 | return async (...args: any[]) => { 49 | const lastVerifiable = 50 | await client.blockHistory.getLastVerifiableBlock() 51 | target.setBaseBlock(lastVerifiable.toNumber()) 52 | let result = await (target[p] as Function).call(target, ...args) 53 | if (isBlockProof(result)) { 54 | await client.blockHistory.ensureValidProof(result) 55 | } 56 | return result 57 | } 58 | } 59 | }, 60 | }) 61 | } 62 | 63 | export abstract class ProverImpl implements Prover { 64 | readonly client: RelicClient 65 | readonly contract: ethers.Contract 66 | readonly api: RelicAPI 67 | 68 | constructor(client: RelicClient, key: keyof RelicAddresses) { 69 | this.client = client 70 | this.contract = new ethers.Contract( 71 | client.addresses[key], 72 | proverAbi, 73 | client.provider 74 | ) 75 | this.api = wrapAPI(client) 76 | } 77 | 78 | abstract getProofData(params: Params): Promise 79 | 80 | async prove(params: Params): Promise { 81 | const { proof } = await this.getProofData(params) 82 | return await this.contract.populateTransaction.prove(proof, true, { 83 | value: await this.fee(), 84 | }) 85 | } 86 | 87 | fee(): Promise { 88 | return this.client.reliquary.getFee(this.contract.address) 89 | } 90 | } 91 | 92 | export abstract class EphemeralProverImpl 93 | extends ProverImpl 94 | implements EphemeralProver 95 | { 96 | readonly ephemeralFacts: ethers.Contract 97 | 98 | constructor(client: RelicClient, key: keyof RelicAddresses) { 99 | super(client, key) 100 | this.ephemeralFacts = new ethers.Contract( 101 | client.addresses.ephemeralFacts, 102 | ephemeralFactsAbi, 103 | client.provider 104 | ) 105 | } 106 | 107 | /** 108 | * Proves a fact ephemerally and deliver it to the receiver 109 | * If context.extra is undefined, it will default to the fact signature data 110 | * for compatibility with the RelicReceiver SDK contracts 111 | */ 112 | async proveEphemeral( 113 | context: ReceiverContext, 114 | params: Params 115 | ): Promise { 116 | const { proof, sigData } = await this.getProofData(params) 117 | 118 | // default value for extra = fact signature data 119 | if (!context.extra) { 120 | context = { ...context, extra: sigData } 121 | } 122 | 123 | return await this.ephemeralFacts.populateTransaction.proveEphemeral( 124 | context, 125 | this.contract.address, 126 | proof, 127 | { value: await this.fee() } 128 | ) 129 | } 130 | } 131 | 132 | export abstract class BatchProverImpl implements Prover { 133 | readonly client: RelicClient 134 | readonly contract: ethers.Contract 135 | readonly api: RelicAPI 136 | 137 | constructor(client: RelicClient, key: keyof RelicAddresses) { 138 | this.client = client 139 | this.contract = new ethers.Contract( 140 | client.addresses[key], 141 | batchProverAbi, 142 | client.provider 143 | ) 144 | this.api = wrapAPI(client) 145 | } 146 | 147 | abstract getProofData(params: Params): Promise 148 | 149 | async prove(params: Params): Promise { 150 | const { proof } = await this.getProofData(params) 151 | return await this.contract.populateTransaction.proveBatch(proof, true, { 152 | value: await this.fee(), 153 | }) 154 | } 155 | 156 | fee(): Promise { 157 | return this.client.reliquary.getFee(this.contract.address) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /packages/client/src/provers/storage.ts: -------------------------------------------------------------------------------- 1 | import { ethers, utils as ethersUtils } from 'ethers' 2 | 3 | import { EphemeralProverImpl, ProofData } from './prover' 4 | import { RelicClient } from '../client' 5 | 6 | import { utils } from '../' 7 | 8 | export interface StorageSlotParams { 9 | block: ethers.providers.BlockTag 10 | account: string 11 | slot: ethers.BigNumberish 12 | expected?: ethers.BigNumberish 13 | } 14 | 15 | export class StorageSlotProver extends EphemeralProverImpl { 16 | constructor(client: RelicClient) { 17 | super(client, 'storageSlotProver') 18 | } 19 | 20 | override async getProofData(params: StorageSlotParams): Promise { 21 | const proof = await this.api.storageSlotProof( 22 | params.block, 23 | params.account, 24 | params.slot 25 | ) 26 | 27 | if (typeof params.expected !== 'undefined') { 28 | utils.assertSlotValue(proof.slotValue, params.expected) 29 | } 30 | const proofData = ethersUtils.defaultAbiCoder.encode( 31 | ['address', 'bytes', 'bytes32', 'bytes', 'bytes', 'bytes'], 32 | [ 33 | proof.account, 34 | proof.accountProof, 35 | proof.slot, 36 | proof.slotProof, 37 | proof.header, 38 | proof.blockProof, 39 | ] 40 | ) 41 | 42 | return { 43 | proof: proofData, 44 | sigData: utils.storageSlotSigData(proof.slot, proof.blockNum), 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/client/src/provers/transaction.ts: -------------------------------------------------------------------------------- 1 | import { ethers, utils as ethersUtils } from 'ethers' 2 | 3 | import { EphemeralProverImpl, ProofData } from './prover' 4 | import { RelicClient } from '../client' 5 | import { TransactionHashMismatch } from '../errors' 6 | import { utils } from '..' 7 | 8 | type Params = ethers.providers.TransactionReceipt 9 | 10 | export class TransactionProver extends EphemeralProverImpl { 11 | constructor(client: RelicClient) { 12 | super(client, 'transactionProver') 13 | } 14 | 15 | override async getProofData(params: Params): Promise { 16 | const proof = await this.api.transactionProof( 17 | params.blockHash, 18 | params.transactionIndex 19 | ) 20 | 21 | if (params.transactionHash != proof.txHash) { 22 | throw new TransactionHashMismatch(params.transactionHash, proof.txHash) 23 | } 24 | 25 | const proofData = ethersUtils.defaultAbiCoder.encode( 26 | ['uint256', 'bytes', 'bytes', 'bytes'], 27 | [proof.txIdx, proof.txProof, proof.header, proof.blockProof] 28 | ) 29 | 30 | return { 31 | proof: proofData, 32 | sigData: utils.transactionSigData(proof.txHash), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/client/src/provers/withdrawal.ts: -------------------------------------------------------------------------------- 1 | import { ethers, utils as ethersUtils } from 'ethers' 2 | 3 | import { RelicClient } from '../client' 4 | import { EphemeralProverImpl, ProofData } from './prover' 5 | 6 | import { utils } from '../' 7 | 8 | export interface WithdrawalParams { 9 | block: ethers.providers.BlockTag 10 | idx: number 11 | } 12 | 13 | export class WithdrawalProver extends EphemeralProverImpl { 14 | constructor(client: RelicClient) { 15 | super(client, 'withdrawalProver') 16 | } 17 | 18 | override async getProofData(params: WithdrawalParams): Promise { 19 | const proof = await this.api.withdrawalProof(params.block, params.idx) 20 | 21 | const proofData = ethersUtils.defaultAbiCoder.encode( 22 | ['uint256', 'bytes', 'bytes', 'bytes'], 23 | [params.idx, proof.withdrawalProof, proof.header, proof.blockProof] 24 | ) 25 | 26 | return { 27 | proof: proofData, 28 | sigData: utils.withdrawalSigData(proof.blockNum, params.idx), 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/client/src/reliquary.ts: -------------------------------------------------------------------------------- 1 | import { BytesLike, ethers } from 'ethers' 2 | import { abi } from '@relicprotocol/contracts/abi/IReliquary.json' 3 | import { RelicClient } from './client' 4 | import { RelicError } from './errors' 5 | 6 | const FeeNoFeeFlag = 1 7 | const FeeNativeFlag = 2 8 | 9 | export type FactData = { 10 | exists: boolean 11 | version: number 12 | data: BytesLike 13 | } 14 | 15 | export class Reliquary { 16 | private contract: ethers.Contract 17 | 18 | constructor(client: RelicClient) { 19 | this.contract = new ethers.Contract( 20 | client.addresses.reliquary, 21 | abi, 22 | client.provider 23 | ) 24 | } 25 | 26 | async getFee(proverAddress: string): Promise { 27 | const proverInfo = await this.contract.provers(proverAddress) 28 | const { flags, feeWeiMantissa, feeWeiExponent } = proverInfo.feeInfo 29 | if (flags & FeeNoFeeFlag) { 30 | return ethers.BigNumber.from(0) 31 | } 32 | if (flags & FeeNativeFlag) { 33 | return ethers.BigNumber.from(feeWeiMantissa).mul( 34 | ethers.BigNumber.from(10).pow(feeWeiExponent) 35 | ) 36 | } 37 | throw new RelicError('prover does not support native fees') 38 | } 39 | 40 | async getFact(address: string, factSig: BytesLike): Promise { 41 | const [exists, version, data] = await this.contract.callStatic.debugVerifyFact( 42 | address, 43 | factSig, 44 | { from: '0x0000000000000000000000000000000000000000' }, 45 | ); 46 | 47 | return { 48 | exists, 49 | version, 50 | data, 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/client/src/utils/block.ts: -------------------------------------------------------------------------------- 1 | import { ethers, BigNumber, BigNumberish } from 'ethers' 2 | import { TimestampAfterCurrent } from '../errors' 3 | 4 | export function blockNumberToChunk(blockNum: BigNumberish): number { 5 | return BigNumber.from(blockNum).div(8192).toNumber() 6 | } 7 | 8 | export async function blockForTimestamp( 9 | provider: ethers.providers.Provider, 10 | timestamp: number 11 | ): Promise { 12 | const current = await provider.getBlock('latest') 13 | if (current.timestamp < timestamp) throw new TimestampAfterCurrent(timestamp) 14 | let start = await provider.getBlock(1) 15 | let end = current 16 | 17 | while (end.number - start.number > 1) { 18 | let quantile = 19 | (timestamp - start.timestamp) / (end.timestamp - start.timestamp) 20 | let nextNum = 21 | start.number + Math.floor((end.number - start.number) * quantile) 22 | if (nextNum == start.number) nextNum++ 23 | if (nextNum == end.number) nextNum-- 24 | let next = await provider.getBlock(nextNum) 25 | if (next.timestamp > timestamp) { 26 | end = next 27 | } else { 28 | start = next 29 | } 30 | } 31 | return start 32 | } 33 | -------------------------------------------------------------------------------- /packages/client/src/utils/facts.ts: -------------------------------------------------------------------------------- 1 | import { BigNumberish, utils } from 'ethers' 2 | import { UnknownError } from '../errors' 3 | 4 | const abiCoder = utils.defaultAbiCoder 5 | 6 | export enum FeeClass { 7 | NoFee = 0, 8 | } 9 | 10 | export function toFactSignature(feeClass: FeeClass, sigData: utils.BytesLike) { 11 | if (0 < feeClass || feeClass > 255) { 12 | throw new UnknownError('invalid feeClass parameter') 13 | } 14 | let sigArray = utils.arrayify(utils.keccak256(sigData)) 15 | sigArray.copyWithin(0, 1) // remove highest byte 16 | sigArray[31] = feeClass 17 | return utils.hexlify(sigArray) 18 | } 19 | 20 | export function accountBalanceSigData( 21 | blockNum: number 22 | ) { 23 | return abiCoder.encode( 24 | ['string', 'uint256'], 25 | ['AccountBalance', blockNum] 26 | ) 27 | } 28 | 29 | export function accountCodeHashSigData( 30 | blockNum: number, 31 | codeHash: BigNumberish 32 | ) { 33 | return abiCoder.encode( 34 | ['string', 'uint256', 'bytes32'], 35 | ['AccountCodeHash', blockNum, codeHash] 36 | ) 37 | } 38 | 39 | export function accountNonceSigData( 40 | blockNum: number 41 | ) { 42 | return abiCoder.encode( 43 | ['string', 'uint256'], 44 | ['AccountNonce', blockNum] 45 | ) 46 | } 47 | 48 | export function accountSigData( 49 | blockNum: number 50 | ) { 51 | return abiCoder.encode( 52 | ['string', 'uint256'], 53 | ['Account', blockNum] 54 | ) 55 | } 56 | 57 | export function accountStorageSigData( 58 | blockNum: number, 59 | storageRoot: BigNumberish 60 | ) { 61 | return abiCoder.encode( 62 | ['string', 'uint256', 'bytes32'], 63 | ['AccountStorage', blockNum, storageRoot] 64 | ) 65 | } 66 | 67 | export function birthCertificateSigData() { 68 | return abiCoder.encode(['string'], ['BirthCertificate']) 69 | } 70 | 71 | export function blockHeaderSigData(blockNum: number) { 72 | return abiCoder.encode(['string', 'uint256'], ['BlockHeader', blockNum]) 73 | } 74 | 75 | export function eventSigData(eventID: BigNumberish) { 76 | return abiCoder.encode( 77 | ['string', 'string', 'uint64'], 78 | ['EventAttendance', 'EventID', eventID] 79 | ) 80 | } 81 | 82 | export function logSigData(blockNum: number, txIdx: number, logIdx: number) { 83 | return abiCoder.encode( 84 | ['string', 'uint256', 'uint256', 'uint256'], 85 | ['Log', blockNum, txIdx, logIdx] 86 | ) 87 | } 88 | 89 | export function storageSlotSigData(slot: BigNumberish, blockNum: number) { 90 | return abiCoder.encode( 91 | ['string', 'bytes32', 'uint256'], 92 | ['StorageSlot', slot, blockNum] 93 | ) 94 | } 95 | 96 | export function transactionSigData(txHash: BigNumberish) { 97 | return abiCoder.encode( 98 | ['string', 'bytes32'], 99 | ['Transaction', txHash] 100 | ) 101 | } 102 | 103 | export function withdrawalSigData(blockNum: number, idx: number) { 104 | return abiCoder.encode( 105 | ['string', 'uint256', 'uint256'], 106 | ['Withdrawal', blockNum, idx] 107 | ) 108 | } 109 | -------------------------------------------------------------------------------- /packages/client/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './storage' 2 | export * from './facts' 3 | export * from './network' 4 | export * from './block' 5 | export * from './optimism' 6 | -------------------------------------------------------------------------------- /packages/client/src/utils/network.ts: -------------------------------------------------------------------------------- 1 | import { providers, utils } from 'ethers' 2 | import { 3 | NotL1Network, 4 | UnexpectedSlotTime, 5 | UnsupportedProvider, 6 | } from '../errors' 7 | 8 | export enum ChainId { 9 | EthMainnet = 1, 10 | EthSepolia = 11155111, 11 | ZkSyncMainnet = 324, 12 | ZkSyncSepolia = 300, 13 | OpMainnet = 10, 14 | OpSepolia = 11155420, 15 | BaseMainnet = 8453, 16 | BaseSepolia = 84532, 17 | } 18 | 19 | const BEACON_GENESIS_TIMESTAMP: Record = { 20 | [ChainId.EthMainnet]: 1606824023, 21 | [ChainId.EthSepolia]: 1655733600, 22 | } 23 | 24 | const TIME_PER_SLOT = 12 25 | 26 | export function isL1ChainId(chainId: number) { 27 | return chainId == ChainId.EthMainnet || chainId == ChainId.EthSepolia 28 | } 29 | 30 | export function isZkSyncChainId(chainId: number) { 31 | return chainId == ChainId.ZkSyncMainnet || chainId == ChainId.ZkSyncSepolia 32 | } 33 | 34 | export function isOptimismChainId(chainId: number) { 35 | return ( 36 | chainId == ChainId.OpMainnet || 37 | chainId == ChainId.OpSepolia || 38 | chainId == ChainId.BaseMainnet || 39 | chainId == ChainId.BaseSepolia 40 | ) 41 | } 42 | 43 | export function isL2ChainId(chainId: number) { 44 | return isZkSyncChainId(chainId) || isOptimismChainId(chainId) 45 | } 46 | 47 | export function isProxyL2Deployment(chainId: number, dataChainId: number) { 48 | return isL2ChainId(chainId) && isL1ChainId(dataChainId) 49 | } 50 | 51 | export function beaconGenesisTimestamp(chainId: number): number { 52 | if (!isL1ChainId(chainId)) { 53 | throw new NotL1Network(chainId) 54 | } 55 | return BEACON_GENESIS_TIMESTAMP[chainId] 56 | } 57 | 58 | export function timestampToSlot(timestamp: number, chainId: number): number { 59 | if (!isL1ChainId(chainId)) { 60 | throw new NotL1Network(chainId) 61 | } 62 | const timeDiff = timestamp - BEACON_GENESIS_TIMESTAMP[chainId] 63 | if (timeDiff % TIME_PER_SLOT != 0) { 64 | throw new UnexpectedSlotTime(timestamp) 65 | } 66 | return timeDiff / TIME_PER_SLOT 67 | } 68 | 69 | export function slotToTimestamp(slot: number, chainId: number): number { 70 | if (!isL1ChainId(chainId)) { 71 | throw new NotL1Network(chainId) 72 | } 73 | return BEACON_GENESIS_TIMESTAMP[chainId] + TIME_PER_SLOT * slot 74 | } 75 | 76 | export function getConnectionInfo( 77 | provider: providers.Provider 78 | ): utils.ConnectionInfo { 79 | let obj = provider as any 80 | if (!obj.connection || !obj.connection.url) { 81 | throw new UnsupportedProvider(provider) 82 | } 83 | return obj.connection as utils.ConnectionInfo 84 | } 85 | 86 | export async function getLogs( 87 | provider: providers.Provider, 88 | filter: providers.Filter 89 | ) { 90 | filter = { 91 | ...filter, 92 | fromBlock: 0, 93 | } 94 | let logs: Array 95 | while (true) { 96 | try { 97 | logs = await provider.getLogs(filter) 98 | } catch (e: any) { 99 | // ProviderError: no backends available for method 100 | if (e.code !== -32011) { 101 | throw e 102 | } 103 | continue 104 | } 105 | return logs 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /packages/client/src/utils/optimism.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { 3 | makeStateTrieProof, 4 | DEFAULT_L2_CONTRACT_ADDRESSES, 5 | } from '@eth-optimism/sdk' 6 | 7 | interface OutputRootProof { 8 | version: string 9 | stateRoot: string 10 | messagePasserStorageRoot: string 11 | latestBlockhash: string 12 | } 13 | 14 | export async function getOutputRootProof( 15 | provider: ethers.providers.Provider, 16 | l2BlockNumber: number 17 | ): Promise { 18 | const blockTag = 19 | '0x' + ethers.BigNumber.from(l2BlockNumber).toNumber().toString(16) 20 | // cast the provider to access the `send` method 21 | // note: it doesn't need to be a JsonRpcProvider, as long as it supports the `send` method 22 | const casted = provider as ethers.providers.JsonRpcProvider 23 | const block = await casted.send('eth_getBlockByNumber', [blockTag, false]) 24 | 25 | const rootProof = await makeStateTrieProof( 26 | provider as ethers.providers.JsonRpcProvider, 27 | l2BlockNumber, 28 | DEFAULT_L2_CONTRACT_ADDRESSES.L2ToL1MessagePasser as string, 29 | ethers.constants.HashZero 30 | ) 31 | 32 | return { 33 | version: ethers.constants.HashZero, 34 | stateRoot: block.stateRoot, 35 | messagePasserStorageRoot: rootProof.storageRoot, 36 | latestBlockhash: block.hash, 37 | } 38 | } 39 | 40 | export function hashOutputRootProof(proof: OutputRootProof) { 41 | return ethers.utils.keccak256( 42 | ethers.utils.defaultAbiCoder.encode( 43 | ['bytes32', 'bytes32', 'bytes32', 'bytes32'], 44 | [ 45 | proof.version, 46 | proof.stateRoot, 47 | proof.messagePasserStorageRoot, 48 | proof.latestBlockhash, 49 | ] 50 | ) 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /packages/client/src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, BigNumberish, utils } from 'ethers' 2 | import { SlotValueMismatch } from '../errors' 3 | 4 | const abiCoder = utils.defaultAbiCoder 5 | 6 | export function toBytes32(num: BigNumberish): string { 7 | return abiCoder.encode(['uint256'], [BigNumber.from(num)]) 8 | } 9 | 10 | export function mapElemSlot(base: BigNumberish, key: BigNumberish): string { 11 | return utils.keccak256(abiCoder.encode(['uint256', 'uint256'], [key, base])) 12 | } 13 | 14 | export function staticArrayElemSlot( 15 | base: BigNumberish, 16 | idx: BigNumberish, 17 | slotsPerElem: BigNumberish 18 | ): string { 19 | return toBytes32( 20 | BigNumber.from(base).add(BigNumber.from(idx).mul(slotsPerElem)) 21 | ) 22 | } 23 | 24 | export function dynamicArrayElemSlot( 25 | base: BigNumberish, 26 | idx: BigNumberish, 27 | slotsPerElem: BigNumberish 28 | ): string { 29 | const eltsSlot = BigNumber.from(utils.keccak256(toBytes32(base))) 30 | return toBytes32(eltsSlot.add(BigNumber.from(idx).mul(slotsPerElem))) 31 | } 32 | 33 | export function structFieldSlot( 34 | base: BigNumberish, 35 | offset: BigNumberish 36 | ): string { 37 | return toBytes32(BigNumber.from(base).add(offset)) 38 | } 39 | 40 | export function assertSlotValue(value: BigNumberish, expected: BigNumberish) { 41 | const v0 = BigNumber.from(value) 42 | const v1 = BigNumber.from(expected) 43 | if (!v0.eq(v1)) { 44 | throw new SlotValueMismatch(v0, v1) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "experimentalDecorators": true, 5 | "emitDeclarationOnly": true, 6 | "outDir": "./dist/types", 7 | "rootDir": "src" 8 | }, 9 | "include": ["./src/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/contracts/.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .prettierrc 3 | -------------------------------------------------------------------------------- /packages/contracts/BirthCertificateVerifier.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "./lib/FactSigs.sol"; 5 | import "./lib/BirthCertificate.sol"; 6 | import "./interfaces/IReliquary.sol"; 7 | 8 | /** 9 | * @title BirthCertificateVerifier 10 | * @author Theori, Inc. 11 | * @notice Defines internal functions and modifiers for querying and verifying accounts' 12 | * birth certificates. 13 | */ 14 | contract BirthCertificateVerifier { 15 | IReliquary immutable reliquary; 16 | 17 | constructor(IReliquary _reliquary) { 18 | reliquary = _reliquary; 19 | } 20 | 21 | /** 22 | * @notice Queries the Reliquary for a birth certificate. Reverts if it does not exist. 23 | * Returns the block number and timestamp of account creation. 24 | * @param account the account to query 25 | */ 26 | function getBirthCertificate(address account) internal view returns (uint48, uint64) { 27 | (bool exists, , bytes memory data) = reliquary.verifyFactNoFee( 28 | account, 29 | FactSigs.birthCertificateFactSig() 30 | ); 31 | require(exists, "account has no proven birth certificate"); 32 | return BirthCertificate.parse(data); 33 | } 34 | 35 | /** 36 | * @notice Reverts if the account has not proven its age is at least the provided value. 37 | * @param account the account to query 38 | * @param age the age (in seconds) 39 | */ 40 | function requireOlderThan(address account, uint256 age) internal view { 41 | (, uint64 birthTime) = getBirthCertificate(account); 42 | require(block.timestamp - birthTime > age, "account is not old enough"); 43 | } 44 | 45 | /** 46 | * @notice Reverts if the account has not proven its age is at least the provided value. 47 | * @param account the account to query 48 | * @param age the age (in blocks) 49 | */ 50 | function requireOlderThanBlocks(address account, uint256 age) internal view { 51 | (uint48 birthBlock, ) = getBirthCertificate(account); 52 | require(block.number - birthBlock > age, "account is not old enough"); 53 | } 54 | 55 | /** 56 | * @notice Reverts if the account has not proven it was before the given time. 57 | * @param account the account to query 58 | * @param timestamp the cutoff timestamp (in seconds) 59 | */ 60 | function requireBornBefore(address account, uint256 timestamp) internal view { 61 | (, uint64 birthTime) = getBirthCertificate(account); 62 | require(birthTime < timestamp, "account is not old enough"); 63 | } 64 | 65 | /** 66 | * @notice Reverts if the account has not proven it was before the given block number 67 | * @param account the account to query 68 | * @param blockNum the cutoff block number 69 | */ 70 | function requireBornBeforeBlock(address account, uint256 blockNum) internal view { 71 | (uint48 birthBlock, ) = getBirthCertificate(account); 72 | require(birthBlock < blockNum, "account is not old enough"); 73 | } 74 | 75 | /** 76 | * @notice Modifier version of requireOlderThan(msg.sender, age) 77 | * @param age the age (in seconds) 78 | */ 79 | modifier onlyOlderThan(uint256 age) { 80 | requireOlderThan(msg.sender, age); 81 | _; 82 | } 83 | 84 | /** 85 | * @notice Modifier version of requireOlderThanBlocks(msg.sender, age) 86 | * @param age the age (in blocks) 87 | */ 88 | modifier onlyOlderThanBlocks(uint256 age) { 89 | requireOlderThanBlocks(msg.sender, age); 90 | _; 91 | } 92 | 93 | /** 94 | * @notice Modifier version of requireBornBefore(msg.sender, timestamp) 95 | * @param timestamp the cutoff timestamp (in seconds) 96 | */ 97 | modifier onlyBornBefore(uint256 timestamp) { 98 | requireBornBefore(msg.sender, timestamp); 99 | _; 100 | } 101 | 102 | /** 103 | * @notice Modifier version of requireBornBeforeBlock(msg.sender, blockNum) 104 | * @param blockNum the cutoff block number 105 | */ 106 | modifier onlyBornBeforeBlock(uint256 blockNum) { 107 | requireBornBeforeBlock(msg.sender, blockNum); 108 | _; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /packages/contracts/README.md: -------------------------------------------------------------------------------- 1 | # Relic Contracts 2 | 3 | ## Usage 4 | 5 | Once installed, you can use the contracts in the library by importing them in your solidity. For example, 6 | 7 | ```solidity 8 | pragma solidity ^0.8.0; 9 | 10 | import "@relicprotocol/contracts/BirthCertificateVerifier.sol"; 11 | import "@relicprotocol/contracts/interfaces/IReliquary.sol"; 12 | 13 | contract MyContract is BirthCertificateVerifier { 14 | 15 | // take Reliquary address as constructor arg, could instead be hardcoded 16 | constructor(IReliquary reliquary) BirthCertificateVerifier(reliquary) { } 17 | 18 | function someFunction() external onlyOlderThan(365 days) { 19 | // we know msg.sender's account is at least 1 year old 20 | } 21 | } 22 | ``` 23 | -------------------------------------------------------------------------------- /packages/contracts/RelicReceiver.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import './lib/CoreTypes.sol'; 6 | import './lib/Facts.sol'; 7 | import './lib/FactSigs.sol'; 8 | import './lib/Storage.sol'; 9 | import './lib/BirthCertificate.sol'; 10 | import './interfaces/IRelicReceiver.sol'; 11 | import './interfaces/IEphemeralFacts.sol'; 12 | 13 | /** 14 | * @title RelicReceiver 15 | * @author Theori, Inc. 16 | * @notice 17 | */ 18 | abstract contract RelicReceiver is IRelicReceiver { 19 | IEphemeralFacts immutable ephemeralFacts; 20 | 21 | constructor(IEphemeralFacts _ephemeralFacts) { 22 | ephemeralFacts = _ephemeralFacts; 23 | } 24 | 25 | /** 26 | * @notice Handler for receiving ephemeral storage slot facts. 27 | * @dev By default, handling storage facts is unimplemented and will revert. 28 | * Subcontracts should override this function if desired. 29 | * @param initiator the address which initiated the fact proving 30 | * @param account the account for the storage slot 31 | * @param slot the slot index 32 | * @param blockNum the block number of the fact 33 | * @param value the value contained in the slot 34 | */ 35 | function receiveStorageSlotFact( 36 | address initiator, 37 | address account, 38 | bytes32 slot, 39 | uint256 blockNum, 40 | bytes32 value 41 | ) internal virtual { 42 | initiator; account; slot; blockNum; value; // silence warnings 43 | revert('Unimplemented: receiveStorageSlotFact'); 44 | } 45 | 46 | /** 47 | * @notice Handler for receiving ephemeral birth certificate facts. 48 | * @dev By default, handling birth certificates is unimplemented and will revert. 49 | * Subcontracts should override this function if desired. 50 | * @param initiator the address which initiated the fact proving 51 | * @param account the account for the storage slot 52 | * @param blockNum the block number of the birth certificate 53 | * @param timestamp the timestamp of the birth certificate 54 | */ 55 | function receiveBirthCertificateFact( 56 | address initiator, 57 | address account, 58 | uint256 blockNum, 59 | uint256 timestamp 60 | ) internal virtual { 61 | initiator; account; blockNum; timestamp; // silence warnings 62 | revert('Unimplemented: receiveBirthCertificateFact'); 63 | } 64 | 65 | /** 66 | * @notice Handler for receiving ephemeral log facts. 67 | * @dev By default, handling log facts is unimplemented and will revert. 68 | * Subcontracts should override this function if desired. 69 | * @param initiator the address which initiated the fact proving 70 | * @param account the account which emitted the log 71 | * @param blockNum the block number of the log 72 | * @param txIdx the index of the transaction in the block 73 | * @param logIdx the index of the log in the transaction 74 | * @param log the log data 75 | */ 76 | function receiveLogFact( 77 | address initiator, 78 | address account, 79 | uint256 blockNum, 80 | uint256 txIdx, 81 | uint256 logIdx, 82 | CoreTypes.LogData memory log 83 | ) internal virtual { 84 | initiator; account; blockNum; txIdx; logIdx; log; // silence warnings 85 | revert('Unimplemented: receiveLogFact'); 86 | } 87 | 88 | /** 89 | * @notice Handler for receiving block header facts. 90 | * @dev By default, handling block header facts is unimplemented and will revert. 91 | * Subcontracts should override this function if desired. 92 | * @param initiator the address which initiated the fact proving 93 | * @param blockNum the block number of the log 94 | * @param header the block header data 95 | */ 96 | function receiveBlockHeaderFact( 97 | address initiator, 98 | uint256 blockNum, 99 | CoreTypes.BlockHeaderData memory header 100 | ) internal virtual { 101 | initiator; blockNum; header; // silence warnings 102 | revert('Unimplemented: receiveBlockHeaderFact'); 103 | } 104 | 105 | /** 106 | * @notice receives an ephemeral fact from Relic 107 | * @param initiator the account which initiated the fact proving 108 | * @param fact the proven fact information 109 | * @param data extra data passed from the initiator - this contract requires it to be 110 | * the fact signature data, so that we can identify the fact type and parse 111 | * the fact parameters. 112 | */ 113 | function receiveFact(address initiator, Fact calldata fact, bytes calldata data) external { 114 | require( 115 | msg.sender == address(ephemeralFacts), 116 | 'only EphemeralFacts can call receiveFact' 117 | ); 118 | 119 | // validate data matches the received fact signature 120 | FactSignature computedSig = Facts.toFactSignature(Facts.NO_FEE, data); 121 | require( 122 | FactSignature.unwrap(fact.sig) == FactSignature.unwrap(computedSig), 123 | 'extra data does not match fact signature' 124 | ); 125 | 126 | bytes32 nameHash = keccak256(abi.decode(data, (bytes))); 127 | if (nameHash == keccak256('BirthCertificate')) { 128 | (uint48 blockNum, uint64 timestamp) = BirthCertificate.parse( 129 | fact.data 130 | ); 131 | receiveBirthCertificateFact(initiator, fact.account, blockNum, timestamp); 132 | } else if (nameHash == keccak256('StorageSlot')) { 133 | (, bytes32 slot, uint256 blockNum) = abi.decode( 134 | data, 135 | (bytes, bytes32, uint256) 136 | ); 137 | bytes32 value = bytes32(Storage.parseUint256(fact.data)); 138 | receiveStorageSlotFact(initiator, fact.account, slot, blockNum, value); 139 | } else if (nameHash == keccak256('Log')) { 140 | (, uint256 blockNum, uint256 txIdx, uint256 logIdx) = abi.decode( 141 | data, 142 | (bytes, uint256, uint256, uint256) 143 | ); 144 | CoreTypes.LogData memory log = abi.decode( 145 | fact.data, 146 | (CoreTypes.LogData) 147 | ); 148 | receiveLogFact(initiator, fact.account, blockNum, txIdx, logIdx, log); 149 | } else if (nameHash == keccak256('BlockHeader')) { 150 | (, uint256 blockNum) = abi.decode(data, (bytes, uint256)); 151 | CoreTypes.BlockHeaderData memory header = abi.decode( 152 | fact.data, 153 | (CoreTypes.BlockHeaderData) 154 | ); 155 | receiveBlockHeaderFact(initiator, blockNum, header); 156 | } else { 157 | revert("unsupported fact type"); 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /packages/contracts/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import { HardhatUserConfig } from 'hardhat/config' 2 | import '@nomiclabs/hardhat-ethers' 3 | import '@nomicfoundation/hardhat-chai-matchers' 4 | 5 | const config: HardhatUserConfig = { 6 | solidity: '0.8.12', 7 | paths: { 8 | sources: '.', 9 | }, 10 | networks: { 11 | hardhat: { 12 | chainId: 1, 13 | forking: { 14 | blockNumber: 15770000, 15 | url: process.env.MAINNET_RPC_URL || '', 16 | }, 17 | }, 18 | }, 19 | } 20 | 21 | export default config 22 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IAttendanceProver.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "../lib/Facts.sol"; 6 | 7 | /** 8 | * @title Prover for attendance/participation 9 | * @notice IAttendanceProver verifies statements signed by trusted sources 10 | * to assign attendance Artifacts to accounts 11 | */ 12 | interface IAttendanceProver { 13 | /** 14 | * @notice Emitted when a new event which may be attended is created 15 | * @param eventId The unique id of this event 16 | * @param deadline The timestamp after which no further attendance requests 17 | * will be processed 18 | * @param factSig The fact signature of this particular event 19 | */ 20 | event NewEvent(uint64 eventId, uint48 deadline, FactSignature factSig); 21 | 22 | /** 23 | * @notice Add a new event which may be attended 24 | * @param eventId The unique eventId for the new event 25 | * @param signer The address for the signer which attests the claim code 26 | * is valid 27 | * @param deadline The timestamp after which no further attendance requests 28 | * will be processed 29 | * @param capacity The initial maximum number of attendees which can claim codes 30 | * @dev Emits NewEvent 31 | */ 32 | function addEvent( 33 | uint64 eventId, 34 | address signer, 35 | uint48 deadline, 36 | uint32 capacity 37 | ) external; 38 | 39 | /** 40 | * @notice Prove attendance for an event and claim the associated conveyances 41 | * @param account The account making the claim of attendance 42 | * @param eventId The event which was attended 43 | * @param number The unique id which may be redeemed only once from the event 44 | * @param signatureInner The signature attesting that the number and eventId are valid 45 | * @param signatureOuter The signature attesting that the account is the claimer of 46 | * the presented information 47 | * @dev Issues a fact in the Reliquary with the fact signature for this event 48 | * @dev Issues a soul-bound NFT Artifact for attending the event 49 | */ 50 | function claim( 51 | address account, 52 | uint64 eventId, 53 | uint64 number, 54 | bytes memory signatureInner, 55 | bytes memory signatureOuter 56 | ) external payable; 57 | 58 | function events(uint64) 59 | external 60 | view 61 | returns ( 62 | address signer, 63 | uint32 capacity, 64 | uint48 deadline 65 | ); 66 | 67 | /** 68 | * @notice Increase the capacity of an existing event. Only callable by the 69 | contract owner. 70 | * @param eventId The unique eventId for the new event 71 | * is valid 72 | * @param newCapacity the new maximum number of attendees which can claim codes 73 | * @dev Emits NewEvent 74 | */ 75 | function increaseCapacity(uint64 eventId, uint32 newCapacity) external; 76 | 77 | function outerSigner() external view returns (address); 78 | 79 | /** 80 | * @notice Sets the signer for the attestation that a request was made 81 | * by a particular account. Only callable by the contract owner. 82 | * @param _outerSigner The address corresponding to the signer 83 | */ 84 | function setOuterSigner(address _outerSigner) external; 85 | } 86 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IBatchProver.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | /// (c) Theori, Inc. 2022 3 | /// All rights reserved 4 | 5 | import "../lib/Facts.sol"; 6 | 7 | pragma solidity >=0.8.0; 8 | 9 | /** 10 | * @title IBatchProver 11 | * @author Theori, Inc. 12 | * @notice IBatchProver is a standard interface implemented by some Relic provers. 13 | * Supports proving multiple facts ephemerally or proving and storing 14 | * them in the Reliquary. 15 | */ 16 | interface IBatchProver { 17 | /** 18 | * @notice prove multiple facts ephemerally 19 | * @param proof the encoded proof, depends on the prover implementation 20 | * @param store whether to store the facts in the reliquary 21 | * @return facts the proven facts' information 22 | */ 23 | function proveBatch(bytes calldata proof, bool store) 24 | external 25 | payable 26 | returns (Fact[] memory facts); 27 | } 28 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IBeaconBlockHistory.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: UNLICENSED 2 | /// (c) Theori, Inc. 2022 3 | /// All rights reserved 4 | 5 | pragma solidity >=0.8.0; 6 | 7 | import "./IBlockHistory.sol"; 8 | 9 | /** 10 | * @title Beacon Block history provider 11 | * @author Theori, Inc. 12 | * @notice IBeaconBlockHistory provides a way to verify beacon block roots as well as execution block hashes 13 | */ 14 | 15 | interface IBeaconBlockHistory is IBlockHistory { 16 | function UPGRADE_BLOCK() external view returns (uint256 blockNum); 17 | 18 | event PrecomittedBlock(uint256 indexed blockNum, bytes32 blockHash); 19 | event ImportBlockSummary(uint256 indexed slot, bytes32 summary); 20 | 21 | /** 22 | * @notice verifies a beacon block root 23 | * @param proof the proof of the beacon blcok 24 | * @return blockRoot the `BeaconBlock` root 25 | */ 26 | function verifyBeaconBlockRoot( 27 | bytes calldata proof 28 | ) external view returns (bytes32 blockRoot); 29 | 30 | /** 31 | * @notice gets the cached block summary for the given slot (if it exists) 32 | * @param slot the slot number to query 33 | * @return result the cached block summary (or bytes32(0) if it is not cached) 34 | */ 35 | function getBlockSummary(uint256 slot) external view returns (bytes32 result); 36 | } 37 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IBirthCertificateProver.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "../lib/Facts.sol"; 6 | 7 | /** 8 | * @title IBirthCertificateProver 9 | * @author Theori, Inc. 10 | * @notice IBirthCertificateProver proves that an account existed in a given block 11 | * and stores the oldest known account proof in the fact database 12 | */ 13 | interface IBirthCertificateProver { 14 | function BIRTH_CERTIFICATE_SIG() external view returns (FactSignature); 15 | 16 | function blockHistory() external view returns (address); 17 | 18 | /** 19 | * @notice Proves that an account existed in the given block. Stores the 20 | * fact in the registry if the given block is the oldest block 21 | * this account is known to exist in. Mints the account an SBT if 22 | * this is the first proof. 23 | * 24 | * @param account the account to prove exists 25 | * @param accountProof the Merkle-Patricia trie proof for the account 26 | * @param header the block header, RLP encoded 27 | * @param blockProof proof that the block header is valid 28 | */ 29 | function proveBirthCertificate( 30 | address account, 31 | bytes memory accountProof, 32 | bytes memory header, 33 | bytes memory blockProof 34 | ) external payable; 35 | } 36 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IBlockHashMessenger.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | interface IBlockHashMessenger { 6 | function sendBlockHash( 7 | address destination, 8 | bytes calldata params, 9 | uint256 number, 10 | bytes32 blockHash, 11 | bytes calldata proof 12 | ) external payable; 13 | } 14 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IBlockHistory.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /** 6 | * @title Block history provider 7 | * @author Theori, Inc. 8 | * @notice IBlockHistory provides a way to verify a blockhash 9 | */ 10 | 11 | interface IBlockHistoryBase { 12 | /** 13 | * @notice Determine if the given hash corresponds to the given block 14 | * @param hash the hash if the block in question 15 | * @param num the number of the block in question 16 | * @param proof any witness data required to prove the block hash is 17 | * correct (such as a Merkle or SNARK proof) 18 | * @return boolean indicating if the block hash can be verified correct 19 | */ 20 | function validBlockHash( 21 | bytes32 hash, 22 | uint256 num, 23 | bytes calldata proof 24 | ) external view returns (bool); 25 | } 26 | 27 | interface IBlockHistoryV0 is IBlockHistoryBase { 28 | event ImportMerkleRoot(uint256 indexed index, bytes32 merkleRoot); 29 | } 30 | 31 | interface IBlockHistoryV1 is IBlockHistoryBase { 32 | event ImportMerkleRoot(uint256 indexed index, bytes32 merkleRoot, bytes32 auxiliaryRoot); 33 | 34 | function commitRecent(uint256 blockNum) external; 35 | } 36 | 37 | interface IBlockHistory is IBlockHistoryV1 { } 38 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IEphemeralFacts.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./IRelicReceiver.sol"; 6 | 7 | interface IEphemeralFacts { 8 | struct ReceiverContext { 9 | address initiator; 10 | IRelicReceiver receiver; 11 | bytes extra; 12 | uint256 gasLimit; 13 | bool requireSuccess; 14 | } 15 | 16 | struct FactDescription { 17 | address account; 18 | bytes sigData; 19 | } 20 | 21 | event FactRequested(FactDescription desc, ReceiverContext context, uint256 bounty); 22 | 23 | event ReceiveSuccess(IRelicReceiver receiver, bytes32 requestId); 24 | 25 | event ReceiveFailure(IRelicReceiver receiver, bytes32 requestId); 26 | 27 | event BountyPaid(uint256 bounty, bytes32 requestId, address relayer); 28 | 29 | /** 30 | * @notice proves a fact ephemerally and provides it to the receiver 31 | * @param context the ReceiverContext for delivering the fact 32 | * @param prover the prover module to use, must implement IProver 33 | * @param proof the proof to pass to the prover 34 | */ 35 | function proveEphemeral( 36 | ReceiverContext calldata context, 37 | address prover, 38 | bytes calldata proof 39 | ) external payable; 40 | 41 | /** 42 | * @notice proves a batch of facts ephemerally and provides them to the receivers 43 | * @param contexts the ReceiverContexts for delivering the facts 44 | * @param prover the prover module to use, must implement IBatchProver 45 | * @param proof the proof to pass to the prover 46 | */ 47 | function batchProveEphemeral( 48 | ReceiverContext[] calldata contexts, 49 | address prover, 50 | bytes calldata proof 51 | ) external payable; 52 | 53 | /** 54 | * @notice requests a fact to be proven asynchronously and passed to the receiver, 55 | * @param account the account associated with the fact 56 | * @param sigData the fact data which determines the fact signature (class is assumed to be NO_FEE) 57 | * @param receiver the contract to receive the fact 58 | * @param data the extra data to pass to the receiver 59 | * @param gasLimit the maxmium gas used by the receiver 60 | * @dev msg.value is added to the bounty for this fact request, 61 | * incentivizing somebody to prove it 62 | */ 63 | function requestFact( 64 | address account, 65 | bytes calldata sigData, 66 | IRelicReceiver receiver, 67 | bytes calldata data, 68 | uint256 gasLimit 69 | ) external payable; 70 | } -------------------------------------------------------------------------------- /packages/contracts/interfaces/IOptimismNativeBlockHistory.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: UNLICENSED 2 | /// (c) Theori, Inc. 2022 3 | /// All rights reserved 4 | 5 | pragma solidity >=0.8.0; 6 | 7 | import "./IBlockHistory.sol"; 8 | 9 | /** 10 | * @title Optimism native block history provider 11 | * @author Theori, Inc. 12 | */ 13 | 14 | interface IOptimismNativeBlockHistory is IBlockHistory { 15 | // https://github.com/ethereum-optimism/optimism/blob/65ec61dde94ffa93342728d324fecf474d228e1f/packages/contracts-bedrock/contracts/libraries/Types.sol#L33 16 | /** 17 | * @notice Struct representing the elements that are hashed together to generate an output root 18 | * which itself represents a snapshot of the L2 state. 19 | * 20 | * @custom:field version Version of the output root. 21 | * @custom:field stateRoot Root of the state trie at the block of this output. 22 | * @custom:field messagePasserStorageRoot Root of the message passer storage trie. 23 | * @custom:field latestBlockhash Hash of the block this output was generated from. 24 | */ 25 | struct OutputRootProof { 26 | bytes32 version; 27 | bytes32 stateRoot; 28 | bytes32 messagePasserStorageRoot; 29 | bytes32 latestBlockhash; 30 | } 31 | 32 | function proxyMultiStorageSlotProver() external view returns (address); 33 | function l2OutputOracle() external view returns (address); 34 | function OUTPUT_ROOTS_BASE_SLOT() external view returns (bytes32); 35 | function FINALIZATION_PERIOD_SECONDS() external view returns (uint256); 36 | 37 | function importCheckpointBlockFromL1( 38 | bytes calldata proof, 39 | uint256 index, 40 | uint256 l1BlockNumber, 41 | OutputRootProof calldata outputRootProof 42 | ) external; 43 | } 44 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IProver.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | import "../lib/Facts.sol"; 4 | 5 | pragma solidity >=0.8.0; 6 | 7 | /** 8 | * @title IProver 9 | * @author Theori, Inc. 10 | * @notice IProver is a standard interface implemented by some Relic provers. 11 | * Supports proving a fact ephemerally or proving and storing it in the 12 | * Reliquary. 13 | */ 14 | interface IProver { 15 | /** 16 | * @notice prove a fact and optionally store it in the Reliquary 17 | * @param proof the encoded proof, depends on the prover implementation 18 | * @param store whether to store the facts in the reliquary 19 | * @return fact the proven fact information 20 | */ 21 | function prove(bytes calldata proof, bool store) external payable returns (Fact memory fact); 22 | } 23 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IProxyBeaconBlockHistory.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: UNLICENSED 2 | /// (c) Theori, Inc. 2022 3 | /// All rights reserved 4 | 5 | pragma solidity >=0.8.0; 6 | 7 | import "./IBeaconBlockHistory.sol"; 8 | 9 | /** 10 | * @title Proxy Beacon Block history provider 11 | * @author Theori, Inc. 12 | * @notice IProxyBeaconBlockHistory provides a way to verify beacon block roots as well as execution block hashes on L2s 13 | */ 14 | 15 | interface IProxyBeaconBlockHistory is IBeaconBlockHistory { 16 | function commitCurrentL1BlockHash() external; 17 | } 18 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IProxyBlockHistory.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./IBlockHistory.sol"; 6 | 7 | /** 8 | * @title Block history provider 9 | * @author Theori, Inc. 10 | * @notice IBlockHistory provides a way to verify a blockhash 11 | */ 12 | 13 | interface IProxyBlockHistory is IBlockHistoryV0 { 14 | event TrustedBlockHash(uint256 number, bytes32 blockHash); 15 | 16 | /** 17 | * @notice Import a trusted block hash from the messenger 18 | * @param number the block number to import 19 | * @param hash the block hash 20 | */ 21 | function importTrustedHash(uint256 number, bytes32 hash) external; 22 | } 23 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IRelicReceiver.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "../lib/Facts.sol"; 6 | 7 | /** 8 | * @title IRelicReceiver 9 | * @author Theori, Inc. 10 | * @notice IRelicReceiver has callbacks to receive ephemeral facts from Relic 11 | * The Relic SDK provides a RelicReceiver base class which implements 12 | * IRelicReceiver and simplifies ephemeral fact handling. 13 | */ 14 | interface IRelicReceiver { 15 | /** 16 | * @notice receives an ephemeral fact from Relic 17 | * @param initiator the account which initiated the fact proving 18 | * @param fact the proven fact information 19 | * @param data extra data passed from the initiator - this data may come 20 | * from untrusted parties and thus should be validated 21 | */ 22 | function receiveFact(address initiator, Fact calldata fact, bytes calldata data) external; 23 | } 24 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IReliquary.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "@openzeppelin/contracts/access/IAccessControl.sol"; 6 | import "../lib/Facts.sol"; 7 | 8 | /** 9 | * @title Holder of Relics and Artifacts 10 | * @author Theori, Inc. 11 | * @notice The Reliquary is the heart of Relic. All issuers of Relics and Artifacts 12 | * must be added to the Reliquary. Queries about Relics and Artifacts should 13 | * be made to the Reliquary. 14 | */ 15 | interface IReliquary is IAccessControl { 16 | /** 17 | * @notice Issued when a new prover is accepted into the Reliquary 18 | * @param prover the address of the prover contract 19 | * @param version the identifier that will always be associated with the prover 20 | */ 21 | event NewProver(address prover, uint64 version); 22 | 23 | /** 24 | * @notice Issued when a new prover is placed under consideration for acceptance 25 | * into the Reliquary 26 | * @param prover the address of the prover contract 27 | * @param version the proposed identifier to always be associated with the prover 28 | * @param timestamp the earliest this prover can be brought into the Reliquary 29 | */ 30 | event PendingProverAdded(address prover, uint64 version, uint64 timestamp); 31 | 32 | /** 33 | * @notice Issued when an existing prover is banished from the Reliquary 34 | * @param prover the address of the prover contract 35 | * @param version the identifier that can never be used again 36 | * @dev revoked provers may not issue new Relics or Artifacts. The meaning of 37 | * any previously introduced Relics or Artifacts is implementation dependent. 38 | */ 39 | event ProverRevoked(address prover, uint64 version); 40 | 41 | struct ProverInfo { 42 | uint64 version; 43 | FeeInfo feeInfo; 44 | bool revoked; 45 | } 46 | 47 | enum FeeFlags { 48 | FeeNone, 49 | FeeNative, 50 | FeeCredits, 51 | FeeExternalDelegate, 52 | FeeExternalToken 53 | } 54 | 55 | struct FeeInfo { 56 | uint8 flags; 57 | uint16 feeCredits; 58 | // feeWei = feeWeiMantissa * pow(10, feeWeiExponent) 59 | uint8 feeWeiMantissa; 60 | uint8 feeWeiExponent; 61 | uint32 feeExternalId; 62 | } 63 | 64 | function ADD_PROVER_ROLE() external view returns (bytes32); 65 | 66 | function CREDITS_ROLE() external view returns (bytes32); 67 | 68 | function DELAY() external view returns (uint64); 69 | 70 | function GOVERNANCE_ROLE() external view returns (bytes32); 71 | 72 | function SUBSCRIPTION_ROLE() external view returns (bytes32); 73 | 74 | /** 75 | * @notice activates a pending prover once the delay has passed. Callable by anyone. 76 | * @param prover the address of the pending prover 77 | */ 78 | function activateProver(address prover) external; 79 | 80 | /** 81 | * @notice Add credits to an account. Requires the CREDITS_ROLE. 82 | * @param user The account to which more credits should be granted 83 | * @param amount The number of credits to be added 84 | */ 85 | function addCredits(address user, uint192 amount) external; 86 | 87 | /** 88 | * @notice Add/propose a new prover to prove facts. Requires the ADD_PROVER_ROLE. 89 | * @param prover the address of the prover in question 90 | * @param version the unique version string to associate with this prover 91 | * @dev Provers and proposed provers must have unique version IDs 92 | * @dev After the Reliquary is initialized, a review period of 64k blocks 93 | * must conclude before a prover may be added. The request must then 94 | * be re-submitted to take effect. Before initialization is complete, 95 | * the review period is skipped. 96 | * @dev Emits PendingProverAdded when a prover is proposed for inclusion 97 | */ 98 | function addProver(address prover, uint64 version) external; 99 | 100 | /** 101 | * @notice Add/update a subscription. Requires the SUBSCRIPTION_ROLE. 102 | * @param user The subscriber account to modify 103 | * @param ts The new block timestamp at which the subscription expires 104 | */ 105 | function addSubscriber(address user, uint64 ts) external; 106 | 107 | /** 108 | * @notice Asserts that a particular block had a particular hash 109 | * @param verifier The block history verifier to use for the query 110 | * @param hash The block hash in question 111 | * @param num The block number to query 112 | * @param proof Any witness information needed by the verifier 113 | * @dev Reverts if the given block was not proven to have the given hash. 114 | * @dev A fee may be required based on the block in question 115 | */ 116 | function assertValidBlockHash( 117 | address verifier, 118 | bytes32 hash, 119 | uint256 num, 120 | bytes memory proof 121 | ) external payable; 122 | 123 | /** 124 | * @notice Asserts that a particular block had a particular hash. Callable only from provers. 125 | * @param verifier The block history verifier to use for the query 126 | * @param hash The block hash in question 127 | * @param num The block number to query 128 | * @param proof Any witness information needed by the verifier 129 | * @dev Reverts if the given block was not proven to have the given hash. 130 | * @dev This function is only for use by provers (reverts otherwise) 131 | */ 132 | function assertValidBlockHashFromProver( 133 | address verifier, 134 | bytes32 hash, 135 | uint256 num, 136 | bytes memory proof 137 | ) external view; 138 | 139 | /** 140 | * @notice Require that an appropriate fee is paid for proving a fact 141 | * @param sender The account wanting to prove a fact 142 | * @dev The fee is derived from the prover which calls this function 143 | * @dev Reverts if the fee is not sufficient 144 | * @dev Only to be called by a prover 145 | */ 146 | function checkProveFactFee(address sender) external payable; 147 | 148 | /** 149 | * @notice Helper function to query the status of a prover 150 | * @param prover the ProverInfo associated with the prover in question 151 | * @dev reverts if the prover is invalid or revoked 152 | */ 153 | function checkProver(ProverInfo memory prover) external pure; 154 | 155 | /** 156 | * @notice Check how many credits a given account possesses 157 | * @param user The account in question 158 | * @return The number of credits 159 | */ 160 | function credits(address user) external view returns (uint192); 161 | 162 | /** 163 | * @notice Verify if a particular block had a particular hash. Only callable by address(0), 164 | for debug 165 | * @param verifier The block history verifier to use for the query 166 | * @param hash The block hash in question 167 | * @param num The block number to query 168 | * @param proof Any witness information needed by the verifier 169 | * @return boolean indication of whether or not the given block was 170 | * proven to have the given hash. 171 | * @dev This function is for use by off-chain tools only (reverts otherwise) 172 | */ 173 | function debugValidBlockHash( 174 | address verifier, 175 | bytes32 hash, 176 | uint256 num, 177 | bytes memory proof 178 | ) external view returns (bool); 179 | 180 | /** 181 | * @notice Query for associated information for a fact. Only callable by address(0), for debug 182 | * @param account The address to which the fact belongs 183 | * @param factSig The unique signature identifying the fact 184 | * @return exists whether or not a fact with the given signature 185 | * is associated with the queried account 186 | * @return version the prover version id that proved this fact 187 | * @return data any associated fact data 188 | * @dev This function is for use by off-chain tools only (reverts otherwise) 189 | */ 190 | function debugVerifyFact(address account, FactSignature factSig) 191 | external 192 | view 193 | returns ( 194 | bool exists, 195 | uint64 version, 196 | bytes memory data 197 | ); 198 | 199 | function factFees(uint8) external view returns (FeeInfo memory); 200 | 201 | function feeAccounts(address) 202 | external 203 | view 204 | returns (uint64 subscriberUntilTime, uint192 credits); 205 | 206 | function feeExternals(uint256) external view returns (address); 207 | 208 | /** 209 | * @notice Query for associated information for a fact. Only callable from provers. 210 | * @param account The address to which the fact belongs 211 | * @param factSig The unique signature identifying the fact 212 | * @return exists whether or not a fact with the given signature 213 | * is associated with the queried account 214 | * @return version the prover version id that proved this fact 215 | * @return data any associated fact data 216 | * @dev This function is only for use by provers (reverts otherwise) 217 | */ 218 | function getFact(address account, FactSignature factSig) 219 | external 220 | view 221 | returns ( 222 | bool exists, 223 | uint64 version, 224 | bytes memory data 225 | ); 226 | 227 | /** 228 | * @notice Determine the appropriate ETH fee to prove a fact 229 | * @param prover The prover of the desired fact 230 | * @return the fee in wei 231 | * @dev Reverts if the fee is not to be paid in ETH 232 | */ 233 | function getProveFactNativeFee(address prover) external view returns (uint256); 234 | 235 | /** 236 | * @notice Determine the appropriate token fee to prove a fact 237 | * @param prover The prover of the desired fact 238 | * @return the fee in wei 239 | * @dev Reverts if the fee is not to be paid in external tokens 240 | */ 241 | function getProveFactTokenFee(address prover) external view returns (uint256); 242 | 243 | /** 244 | * @notice Determine the appropriate ETH fee to query a fact 245 | * @param factSig The signature of the desired fact 246 | * @return the fee in wei 247 | * @dev Reverts if the fee is not to be paid in ETH 248 | */ 249 | function getVerifyFactNativeFee(FactSignature factSig) external view returns (uint256); 250 | 251 | /** 252 | * @notice Determine the appropriate token fee to query a fact 253 | * @param factSig The signature of the desired fact 254 | * @return the fee in wei 255 | * @dev Reverts if the fee is not to be paid in external tokens 256 | */ 257 | function getVerifyFactTokenFee(FactSignature factSig) external view returns (uint256); 258 | 259 | function initialized() external view returns (bool); 260 | 261 | /** 262 | * @notice Check if an account has an active subscription 263 | * @param user The account in question 264 | * @return True if the account is active, otherwise false 265 | */ 266 | function isSubscriber(address user) external view returns (bool); 267 | 268 | function pendingProvers(address) external view returns (uint64 timestamp, uint64 version); 269 | 270 | function provers(address) 271 | external 272 | view 273 | returns (ProverInfo memory); 274 | 275 | /** 276 | * @notice Remove credits from an account. Requires the CREDITS_ROLE. 277 | * @param user The account from which credits should be removed 278 | * @param amount The number of credits to be removed 279 | */ 280 | function removeCredits(address user, uint192 amount) external; 281 | 282 | /** 283 | * @notice Remove a subscription. Requires the SUBSCRIPTION_ROLE. 284 | * @param user The subscriber account to modify 285 | */ 286 | function removeSubscriber(address user) external; 287 | 288 | /** 289 | * @notice Deletes the fact from the Reliquary. Only callable from provers. 290 | * @param account The account to which this information is bound (may be 291 | * the null account for information bound to no specific address) 292 | * @param factSig The unique signature of the particular fact being deleted 293 | * @dev May only be called by non-revoked provers 294 | */ 295 | function resetFact(address account, FactSignature factSig) external; 296 | 297 | /** 298 | * @notice Stop accepting proofs from this prover. Requires the GOVERNANCE_ROLE. 299 | * @param prover The prover to banish from the reliquary 300 | * @dev Emits ProverRevoked 301 | * @dev Note: existing facts proved by the prover may still stand 302 | */ 303 | function revokeProver(address prover) external; 304 | 305 | function setCredits(address user, uint192 amount) external; 306 | 307 | /** 308 | * @notice Adds the given information to the Reliquary. Only callable from provers. 309 | * @param account The account to which this information is bound (may be 310 | * the null account for information bound to no specific address) 311 | * @param factSig The unique signature of the particular fact being proven 312 | * @param data Associated data to store with this item 313 | * @dev May only be called by non-revoked provers 314 | */ 315 | function setFact( 316 | address account, 317 | FactSignature factSig, 318 | bytes memory data 319 | ) external; 320 | 321 | /** 322 | * @notice Sets the FeeInfo for a particular fee class. Requires the GOVERNANCE_ROLE. 323 | * @param cls The fee class 324 | * @param feeInfo The FeeInfo to use for the class 325 | * @param feeExternal An external fee provider (token or delegate). If 326 | * none is required, this should be set to 0. 327 | */ 328 | function setFactFee( 329 | uint8 cls, 330 | FeeInfo memory feeInfo, 331 | address feeExternal 332 | ) external; 333 | 334 | /** 335 | * @notice Initialize the Reliquary, enforcing the time lock for new provers. Requires the 336 | ADD_PROVER_ROLE. 337 | */ 338 | function setInitialized() external; 339 | 340 | /** 341 | * @notice Sets the FeeInfo for a particular prover. Requires the GOVERNANCE_ROLE. 342 | * @param prover The prover in question 343 | * @param feeInfo The FeeInfo to use for the class 344 | * @param feeExternal An external fee provider (token or delegate). If 345 | * none is required, this should be set to 0. 346 | */ 347 | function setProverFee( 348 | address prover, 349 | FeeInfo memory feeInfo, 350 | address feeExternal 351 | ) external; 352 | 353 | /** 354 | * @notice Sets the FeeInfo for block verification. Requires the GOVERNANCE_ROLE. 355 | * @param feeInfo The FeeInfo to use for the class 356 | * @param feeExternal An external fee provider (token or delegate). If 357 | * none is required, this should be set to 0. 358 | */ 359 | function setValidBlockFee(FeeInfo memory feeInfo, address feeExternal) external; 360 | 361 | /** 362 | * @notice Verify if a particular block had a particular hash 363 | * @param verifier The block history verifier to use for the query 364 | * @param hash The block hash in question 365 | * @param num The block number to query 366 | * @param proof Any witness information needed by the verifier 367 | * @return boolean indication of whether or not the given block was 368 | * proven to have the given hash. 369 | * @dev A fee may be required based on the block in question 370 | */ 371 | function validBlockHash( 372 | address verifier, 373 | bytes32 hash, 374 | uint256 num, 375 | bytes memory proof 376 | ) external payable returns (bool); 377 | 378 | /** 379 | * @notice Verify if a particular block had a particular hash. Only callable from provers. 380 | * @param verifier The block history verifier to use for the query 381 | * @param hash The block hash in question 382 | * @param num The block number to query 383 | * @param proof Any witness information needed by the verifier 384 | * @return boolean indication of whether or not the given block was 385 | * proven to have the given hash. 386 | * @dev This function is only for use by provers (reverts otherwise) 387 | */ 388 | function validBlockHashFromProver( 389 | address verifier, 390 | bytes32 hash, 391 | uint256 num, 392 | bytes memory proof 393 | ) external view returns (bool); 394 | 395 | /** 396 | * @notice FeeInfo struct for block hash queries 397 | */ 398 | function verifyBlockFeeInfo() external view returns (FeeInfo memory); 399 | 400 | /** 401 | * @notice Query for associated information for a fact 402 | * @param account The address to which the fact belongs 403 | * @param factSig The unique signature identifying the fact 404 | * @return exists whether or not a fact with the given signature 405 | * is associated with the queried account 406 | * @return version the prover version id that proved this fact 407 | * @return data any associated fact data 408 | * @dev A fee may be required based on the factSig 409 | */ 410 | function verifyFact(address account, FactSignature factSig) 411 | external 412 | payable 413 | returns ( 414 | bool exists, 415 | uint64 version, 416 | bytes memory data 417 | ); 418 | 419 | /** 420 | * @notice Query for associated information for a fact which requires no query fee. 421 | * @param account The address to which the fact belongs 422 | * @param factSig The unique signature identifying the fact 423 | * @return exists whether or not a fact with the given signature 424 | * is associated with the queried account 425 | * @return version the prover version id that proved this fact 426 | * @return data any associated fact data 427 | * @dev This function is for use by anyone 428 | * @dev This function reverts if the fact requires a fee to query 429 | */ 430 | function verifyFactNoFee(address account, FactSignature factSig) 431 | external 432 | view 433 | returns ( 434 | bool exists, 435 | uint64 version, 436 | bytes memory data 437 | ); 438 | 439 | /** 440 | * @notice Query for the prover version for a fact 441 | * @param account The address to which the fact belongs 442 | * @param factSig The unique signature identifying the fact 443 | * @return exists whether or not a fact with the given signature 444 | * is associated with the queried account 445 | * @return version the prover version id that proved this fact 446 | * @dev A fee may be required based on the factSig 447 | */ 448 | function verifyFactVersion(address account, FactSignature factSig) 449 | external 450 | payable 451 | returns (bool exists, uint64 version); 452 | 453 | /** 454 | * @notice Query for the prover version for a fact which requires no query fee. 455 | * @param account The address to which the fact belongs 456 | * @param factSig The unique signature identifying the fact 457 | * @return exists whether or not a fact with the given signature 458 | * is associated with the queried account 459 | * @return version the prover version id that proved this fact 460 | * @dev This function is for use by anyone 461 | * @dev This function reverts if the fact requires a fee to query 462 | */ 463 | function verifyFactVersionNoFee(address account, FactSignature factSig) 464 | external 465 | view 466 | returns (bool exists, uint64 version); 467 | 468 | /** 469 | * @notice Reverse mapping of version information to the unique prover able 470 | * to issue statements with that version 471 | */ 472 | function versions(uint64) external view returns (address); 473 | 474 | /** 475 | * @notice Extract accumulated fees. Requires the GOVERNANCE_ROLE. 476 | * @param token The ERC20 token from which to extract fees. Or the 0 address for 477 | * native ETH 478 | * @param dest The address to which fees should be transferred 479 | */ 480 | function withdrawFees(address token, address dest) external; 481 | } 482 | -------------------------------------------------------------------------------- /packages/contracts/interfaces/IStorageSlotProver.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /** 6 | * @title IStorageSlotProver 7 | * @author Theori, Inc. 8 | * @notice IStorageSlotProver proves that a storage slot had a particular value 9 | * at a particular block. 10 | */ 11 | interface IStorageSlotProver { 12 | function blockHistory() external view returns (address); 13 | 14 | /** 15 | * @notice Proves that a storage slot had a particular value at a particular 16 | * block, and stores this fact in the reliquary. 17 | * 18 | * @param account the account to prove exists 19 | * @param accountProof the Merkle-Patricia trie proof for the account 20 | * @param slot the storage slot index 21 | * @param slotProof the Merkle-Patricia trie proof for the slot 22 | * @param header the block header, RLP encoded 23 | * @param blockProof proof that the block header is valid 24 | * @return blockNum the block number from the header 25 | * @return value the bytes value of the data in the slot 26 | */ 27 | function proveAndStoreStorageSlot( 28 | address account, 29 | bytes memory accountProof, 30 | bytes32 slot, 31 | bytes memory slotProof, 32 | bytes memory header, 33 | bytes memory blockProof 34 | ) external payable returns (uint256 blockNum, bytes memory value); 35 | 36 | /** 37 | * @notice Proves that a storage slot had a particular value at a particular 38 | * block. Returns the block number and bytes value of the slot. 39 | * 40 | * @param account the account to prove exists 41 | * @param accountProof the Merkle-Patricia trie proof for the account 42 | * @param slot the storage slot index 43 | * @param slotProof the Merkle-Patricia trie proof for the slot 44 | * @param header the block header, RLP encoded 45 | * @param blockProof proof that the block header is valid 46 | */ 47 | function proveStorageSlot( 48 | address account, 49 | bytes memory accountProof, 50 | bytes32 slot, 51 | bytes memory slotProof, 52 | bytes memory header, 53 | bytes memory blockProof 54 | ) external payable returns (uint256, bytes memory); 55 | } 56 | -------------------------------------------------------------------------------- /packages/contracts/lib/BirthCertificate.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /** 6 | * @title BirthCertificate 7 | * @author Theori, Inc. 8 | * @notice Helper functions for handling birth certificate facts 9 | */ 10 | library BirthCertificate { 11 | /** 12 | * @notice parse a birth certificate fact 13 | * @param data the stored fact data 14 | * @return blockNum the blockNum from the birth certificate 15 | * @return time the timestamp from the birth certificate 16 | */ 17 | function parse(bytes memory data) internal pure returns (uint48 blockNum, uint64 time) { 18 | require(data.length == 14); 19 | assembly { 20 | let word := mload(add(data, 0x20)) 21 | blockNum := shr(208, word) 22 | time := shr(192, shl(48, word)) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/contracts/lib/BytesCalldata.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | // custom bytes calldata pointer storing (length | offset) in one word, 6 | // also allows calldata pointers to be stored in memory 7 | type BytesCalldata is uint256; 8 | 9 | /** 10 | * @author Theori, Inc 11 | * @title BytesCalldataOps 12 | * @notice Common operations for bytes calldata, implemented for both the builtin 13 | * type and our BytesCalldata type. These operations are heavily optimized 14 | * and omit safety checks, so this library should only be used when memory 15 | * safety is not a security issue. 16 | */ 17 | library BytesCalldataOps { 18 | // each consumer should add the following lines: 19 | using BytesCalldataOps for BytesCalldata; 20 | using BytesCalldataOps for bytes; 21 | 22 | function length(BytesCalldata bc) internal pure returns (uint256 result) { 23 | assembly { 24 | result := shr(128, shl(128, bc)) 25 | } 26 | } 27 | 28 | function offset(BytesCalldata bc) internal pure returns (uint256 result) { 29 | assembly { 30 | result := shr(128, bc) 31 | } 32 | } 33 | 34 | function convert(BytesCalldata bc) internal pure returns (bytes calldata value) { 35 | assembly { 36 | value.offset := shr(128, bc) 37 | value.length := shr(128, shl(128, bc)) 38 | } 39 | } 40 | 41 | function convert(bytes calldata inp) internal pure returns (BytesCalldata bc) { 42 | assembly { 43 | bc := or(shl(128, inp.offset), inp.length) 44 | } 45 | } 46 | 47 | function slice( 48 | BytesCalldata bc, 49 | uint256 start, 50 | uint256 len 51 | ) internal pure returns (BytesCalldata result) { 52 | assembly { 53 | result := shl(128, add(shr(128, bc), start)) // add to the offset and clear the length 54 | result := or(result, len) // set the new length 55 | } 56 | } 57 | 58 | function slice( 59 | bytes calldata value, 60 | uint256 start, 61 | uint256 len 62 | ) internal pure returns (bytes calldata result) { 63 | assembly { 64 | result.offset := add(value.offset, start) 65 | result.length := len 66 | } 67 | } 68 | 69 | function prefix(BytesCalldata bc, uint256 len) internal pure returns (BytesCalldata result) { 70 | assembly { 71 | result := shl(128, shr(128, bc)) // clear out the length 72 | result := or(result, len) // set it to the new length 73 | } 74 | } 75 | 76 | function prefix(bytes calldata value, uint256 len) 77 | internal 78 | pure 79 | returns (bytes calldata result) 80 | { 81 | assembly { 82 | result.offset := value.offset 83 | result.length := len 84 | } 85 | } 86 | 87 | function suffix(BytesCalldata bc, uint256 start) internal pure returns (BytesCalldata result) { 88 | assembly { 89 | result := add(bc, shl(128, start)) // add to the offset 90 | result := sub(result, start) // subtract from the length 91 | } 92 | } 93 | 94 | function suffix(bytes calldata value, uint256 start) 95 | internal 96 | pure 97 | returns (bytes calldata result) 98 | { 99 | assembly { 100 | result.offset := add(value.offset, start) 101 | result.length := sub(value.length, start) 102 | } 103 | } 104 | 105 | function split(BytesCalldata bc, uint256 start) 106 | internal 107 | pure 108 | returns (BytesCalldata, BytesCalldata) 109 | { 110 | return (prefix(bc, start), suffix(bc, start)); 111 | } 112 | 113 | function split(bytes calldata value, uint256 start) 114 | internal 115 | pure 116 | returns (bytes calldata, bytes calldata) 117 | { 118 | return (prefix(value, start), suffix(value, start)); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /packages/contracts/lib/CoreTypes.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./BytesCalldata.sol"; 6 | import "./RLP.sol"; 7 | 8 | /** 9 | * @title CoreTypes 10 | * @author Theori, Inc. 11 | * @notice Data types and parsing functions for core types, including block headers 12 | * and account data. 13 | */ 14 | library CoreTypes { 15 | using BytesCalldataOps for BytesCalldata; 16 | using BytesCalldataOps for bytes; 17 | 18 | struct BlockHeaderData { 19 | bytes32 ParentHash; 20 | address Coinbase; 21 | bytes32 Root; 22 | bytes32 TxHash; 23 | bytes32 ReceiptHash; 24 | uint256 Number; 25 | uint256 GasLimit; 26 | uint256 GasUsed; 27 | uint256 Time; 28 | bytes32 MixHash; 29 | uint256 BaseFee; 30 | bytes32 WithdrawalsHash; 31 | } 32 | 33 | struct AccountData { 34 | uint256 Nonce; 35 | uint256 Balance; 36 | bytes32 StorageRoot; 37 | bytes32 CodeHash; 38 | } 39 | 40 | struct LogData { 41 | address Address; 42 | bytes32[] Topics; 43 | bytes Data; 44 | } 45 | 46 | struct WithdrawalData { 47 | uint256 Index; 48 | uint256 ValidatorIndex; 49 | address Address; 50 | uint256 AmountInGwei; 51 | } 52 | 53 | function parseHash(bytes calldata buf) internal pure returns (bytes32 result, uint256 offset) { 54 | uint256 value; 55 | (value, offset) = RLP.parseUint(buf); 56 | result = bytes32(value); 57 | } 58 | 59 | function parseAddress(bytes calldata buf) 60 | internal 61 | pure 62 | returns (address result, uint256 offset) 63 | { 64 | uint256 value; 65 | (value, offset) = RLP.parseUint(buf); 66 | result = address(uint160(value)); 67 | } 68 | 69 | function parseBlockHeader(bytes calldata header) 70 | internal 71 | pure 72 | returns (BlockHeaderData memory data) 73 | { 74 | (uint256 listSize, uint256 offset) = RLP.parseList(header); 75 | header = header.slice(offset, listSize); 76 | 77 | (data.ParentHash, offset) = parseHash(header); // ParentHash 78 | header = header.suffix(offset); 79 | header = RLP.skip(header); // UncleHash 80 | (data.Coinbase, offset) = parseAddress(header); // Coinbase 81 | header = header.suffix(offset); 82 | (data.Root, offset) = parseHash(header); // Root 83 | header = header.suffix(offset); 84 | (data.TxHash, offset) = parseHash(header); // TxHash 85 | header = header.suffix(offset); 86 | (data.ReceiptHash, offset) = parseHash(header); // ReceiptHash 87 | header = header.suffix(offset); 88 | header = RLP.skip(header); // Bloom 89 | header = RLP.skip(header); // Difficulty 90 | (data.Number, offset) = RLP.parseUint(header); // Number 91 | header = header.suffix(offset); 92 | (data.GasLimit, offset) = RLP.parseUint(header); // GasLimit 93 | header = header.suffix(offset); 94 | (data.GasUsed, offset) = RLP.parseUint(header); // GasUsed 95 | header = header.suffix(offset); 96 | (data.Time, offset) = RLP.parseUint(header); // Time 97 | header = header.suffix(offset); 98 | header = RLP.skip(header); // Extra 99 | (data.MixHash, offset) = parseHash(header); // MixHash 100 | header = header.suffix(offset); 101 | header = RLP.skip(header); // Nonce 102 | 103 | if (header.length > 0) { 104 | (data.BaseFee, offset) = RLP.parseUint(header); // BaseFee 105 | header = header.suffix(offset); 106 | } 107 | 108 | if (header.length > 0) { 109 | (data.WithdrawalsHash, offset) = parseHash(header); // WithdrawalsHash 110 | } 111 | } 112 | 113 | function getBlockHeaderHashAndSize(bytes calldata header) 114 | internal 115 | pure 116 | returns (bytes32 blockHash, uint256 headerSize) 117 | { 118 | (uint256 listSize, uint256 offset) = RLP.parseList(header); 119 | unchecked { 120 | headerSize = offset + listSize; 121 | } 122 | blockHash = keccak256(header.prefix(headerSize)); 123 | } 124 | 125 | function parseAccount(bytes calldata account) internal pure returns (AccountData memory data) { 126 | (, uint256 offset) = RLP.parseList(account); 127 | account = account.suffix(offset); 128 | 129 | (data.Nonce, offset) = RLP.parseUint(account); // Nonce 130 | account = account.suffix(offset); 131 | (data.Balance, offset) = RLP.parseUint(account); // Balance 132 | account = account.suffix(offset); 133 | (data.StorageRoot, offset) = parseHash(account); // StorageRoot 134 | account = account.suffix(offset); 135 | (data.CodeHash, offset) = parseHash(account); // CodeHash 136 | account = account.suffix(offset); 137 | } 138 | 139 | function parseLog(bytes calldata log) internal pure returns (LogData memory data) { 140 | (, uint256 offset) = RLP.parseList(log); 141 | log = log.suffix(offset); 142 | 143 | uint256 tmp; 144 | (tmp, offset) = RLP.parseUint(log); // Address 145 | data.Address = address(uint160(tmp)); 146 | log = log.suffix(offset); 147 | 148 | (tmp, offset) = RLP.parseList(log); // Topics 149 | bytes calldata topics = log.slice(offset, tmp); 150 | log = log.suffix(offset + tmp); 151 | 152 | require(topics.length % 33 == 0); 153 | data.Topics = new bytes32[](tmp / 33); 154 | uint256 i = 0; 155 | while (topics.length > 0) { 156 | (data.Topics[i], offset) = parseHash(topics); 157 | topics = topics.suffix(offset); 158 | unchecked { 159 | i++; 160 | } 161 | } 162 | 163 | (data.Data, ) = RLP.splitBytes(log); 164 | } 165 | 166 | function extractLog(bytes calldata receiptValue, uint256 logIdx) 167 | internal 168 | pure 169 | returns (LogData memory) 170 | { 171 | // support EIP-2718: Currently all transaction types have the same 172 | // receipt RLP format, so we can just skip the receipt type byte 173 | if (receiptValue[0] < 0x80) { 174 | receiptValue = receiptValue.suffix(1); 175 | } 176 | 177 | (, uint256 offset) = RLP.parseList(receiptValue); 178 | receiptValue = receiptValue.suffix(offset); 179 | 180 | // pre EIP-658, receipts stored an intermediate state root in this field 181 | // post EIP-658, the field is a tx status (0 for failure, 1 for success) 182 | uint256 statusOrIntermediateRoot; 183 | (statusOrIntermediateRoot, offset) = RLP.parseUint(receiptValue); 184 | require(statusOrIntermediateRoot != 0, "tx did not succeed"); 185 | receiptValue = receiptValue.suffix(offset); 186 | 187 | receiptValue = RLP.skip(receiptValue); // GasUsed 188 | receiptValue = RLP.skip(receiptValue); // LogsBloom 189 | 190 | uint256 length; 191 | (length, offset) = RLP.parseList(receiptValue); // Logs 192 | receiptValue = receiptValue.slice(offset, length); 193 | 194 | // skip the earlier logs 195 | for (uint256 i = 0; i < logIdx; i++) { 196 | require(receiptValue.length > 0, "log index does not exist"); 197 | receiptValue = RLP.skip(receiptValue); 198 | } 199 | 200 | return parseLog(receiptValue); 201 | } 202 | 203 | function parseWithdrawal(bytes calldata withdrawal) 204 | internal 205 | pure 206 | returns (WithdrawalData memory data) 207 | { 208 | (, uint256 offset) = RLP.parseList(withdrawal); 209 | withdrawal = withdrawal.suffix(offset); 210 | 211 | (data.Index, offset) = RLP.parseUint(withdrawal); // Index 212 | withdrawal = withdrawal.suffix(offset); 213 | (data.ValidatorIndex, offset) = RLP.parseUint(withdrawal); // ValidatorIndex 214 | withdrawal = withdrawal.suffix(offset); 215 | (data.Address, offset) = parseAddress(withdrawal); // Address 216 | withdrawal = withdrawal.suffix(offset); 217 | (data.AmountInGwei, offset) = RLP.parseUint(withdrawal); // Amount 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /packages/contracts/lib/FactSigs.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import "./Facts.sol"; 6 | 7 | /** 8 | * @title FactSigs 9 | * @author Theori, Inc. 10 | * @notice Helper functions for computing fact signatures 11 | */ 12 | library FactSigs { 13 | /** 14 | * @notice Produce the fact signature data for birth certificates 15 | */ 16 | function birthCertificateFactSigData() internal pure returns (bytes memory) { 17 | return abi.encode("BirthCertificate"); 18 | } 19 | 20 | /** 21 | * @notice Produce the fact signature for a birth certificate fact 22 | */ 23 | function birthCertificateFactSig() internal pure returns (FactSignature) { 24 | return Facts.toFactSignature(Facts.NO_FEE, birthCertificateFactSigData()); 25 | } 26 | 27 | /** 28 | * @notice Produce the fact signature data for an account's storage root 29 | * @param blockNum the block number to look at 30 | * @param storageRoot the storageRoot for the account 31 | */ 32 | function accountStorageFactSigData(uint256 blockNum, bytes32 storageRoot) 33 | internal 34 | pure 35 | returns (bytes memory) 36 | { 37 | return abi.encode("AccountStorage", blockNum, storageRoot); 38 | } 39 | 40 | /** 41 | * @notice Produce a fact signature for an account storage root 42 | * @param blockNum the block number to look at 43 | * @param storageRoot the storageRoot for the account 44 | */ 45 | function accountStorageFactSig(uint256 blockNum, bytes32 storageRoot) 46 | internal 47 | pure 48 | returns (FactSignature) 49 | { 50 | return 51 | Facts.toFactSignature(Facts.NO_FEE, accountStorageFactSigData(blockNum, storageRoot)); 52 | } 53 | 54 | /** 55 | * @notice Produce the fact signature data for an account's code hash 56 | * @param blockNum the block number to look at 57 | * @param codeHash the codeHash for the account 58 | */ 59 | function accountCodeHashFactSigData(uint256 blockNum, bytes32 codeHash) 60 | internal 61 | pure 62 | returns (bytes memory) 63 | { 64 | return abi.encode("AccountCodeHash", blockNum, codeHash); 65 | } 66 | 67 | /** 68 | * @notice Produce a fact signature for an account code hash 69 | * @param blockNum the block number to look at 70 | * @param codeHash the codeHash for the account 71 | */ 72 | function accountCodeHashFactSig(uint256 blockNum, bytes32 codeHash) 73 | internal 74 | pure 75 | returns (FactSignature) 76 | { 77 | return Facts.toFactSignature(Facts.NO_FEE, accountCodeHashFactSigData(blockNum, codeHash)); 78 | } 79 | 80 | /** 81 | * @notice Produce the fact signature data for an account's nonce at a block 82 | * @param blockNum the block number to look at 83 | */ 84 | function accountNonceFactSigData(uint256 blockNum) internal pure returns (bytes memory) { 85 | return abi.encode("AccountNonce", blockNum); 86 | } 87 | 88 | /** 89 | * @notice Produce a fact signature for an account nonce at a block 90 | * @param blockNum the block number to look at 91 | */ 92 | function accountNonceFactSig(uint256 blockNum) internal pure returns (FactSignature) { 93 | return Facts.toFactSignature(Facts.NO_FEE, accountNonceFactSigData(blockNum)); 94 | } 95 | 96 | /** 97 | * @notice Produce the fact signature data for an account's balance at a block 98 | * @param blockNum the block number to look at 99 | */ 100 | function accountBalanceFactSigData(uint256 blockNum) internal pure returns (bytes memory) { 101 | return abi.encode("AccountBalance", blockNum); 102 | } 103 | 104 | /** 105 | * @notice Produce a fact signature for an account balance a block 106 | * @param blockNum the block number to look at 107 | */ 108 | function accountBalanceFactSig(uint256 blockNum) internal pure returns (FactSignature) { 109 | return Facts.toFactSignature(Facts.NO_FEE, accountBalanceFactSigData(blockNum)); 110 | } 111 | 112 | /** 113 | * @notice Produce the fact signature data for an account's raw header 114 | * @param blockNum the block number to look at 115 | */ 116 | function accountFactSigData(uint256 blockNum) internal pure returns (bytes memory) { 117 | return abi.encode("Account", blockNum); 118 | } 119 | 120 | /** 121 | * @notice Produce a fact signature for an account raw header 122 | * @param blockNum the block number to look at 123 | */ 124 | function accountFactSig(uint256 blockNum) internal pure returns (FactSignature) { 125 | return Facts.toFactSignature(Facts.NO_FEE, accountFactSigData(blockNum)); 126 | } 127 | 128 | /** 129 | * @notice Produce the fact signature data for a storage slot 130 | * @param slot the account's slot 131 | * @param blockNum the block number to look at 132 | */ 133 | function storageSlotFactSigData(bytes32 slot, uint256 blockNum) 134 | internal 135 | pure 136 | returns (bytes memory) 137 | { 138 | return abi.encode("StorageSlot", slot, blockNum); 139 | } 140 | 141 | /** 142 | * @notice Produce a fact signature for a storage slot 143 | * @param slot the account's slot 144 | * @param blockNum the block number to look at 145 | */ 146 | function storageSlotFactSig(bytes32 slot, uint256 blockNum) 147 | internal 148 | pure 149 | returns (FactSignature) 150 | { 151 | return Facts.toFactSignature(Facts.NO_FEE, storageSlotFactSigData(slot, blockNum)); 152 | } 153 | 154 | /** 155 | * @notice Produce the fact signature data for a log 156 | * @param blockNum the block number to look at 157 | * @param txIdx the transaction index in the block 158 | * @param logIdx the log index in the transaction 159 | */ 160 | function logFactSigData( 161 | uint256 blockNum, 162 | uint256 txIdx, 163 | uint256 logIdx 164 | ) internal pure returns (bytes memory) { 165 | return abi.encode("Log", blockNum, txIdx, logIdx); 166 | } 167 | 168 | /** 169 | * @notice Produce a fact signature for a log 170 | * @param blockNum the block number to look at 171 | * @param txIdx the transaction index in the block 172 | * @param logIdx the log index in the transaction 173 | */ 174 | function logFactSig( 175 | uint256 blockNum, 176 | uint256 txIdx, 177 | uint256 logIdx 178 | ) internal pure returns (FactSignature) { 179 | return Facts.toFactSignature(Facts.NO_FEE, logFactSigData(blockNum, txIdx, logIdx)); 180 | } 181 | 182 | /** 183 | * @notice Produce the fact signature data for a block header 184 | * @param blockNum the block number 185 | */ 186 | function blockHeaderSigData(uint256 blockNum) internal pure returns (bytes memory) { 187 | return abi.encode("BlockHeader", blockNum); 188 | } 189 | 190 | /** 191 | * @notice Produce the fact signature data for a block header 192 | * @param blockNum the block number 193 | */ 194 | function blockHeaderSig(uint256 blockNum) internal pure returns (FactSignature) { 195 | return Facts.toFactSignature(Facts.NO_FEE, blockHeaderSigData(blockNum)); 196 | } 197 | 198 | /** 199 | * @notice Produce the fact signature data for a withdrawal 200 | * @param blockNum the block number 201 | * @param index the withdrawal index 202 | */ 203 | function withdrawalSigData(uint256 blockNum, uint256 index) 204 | internal 205 | pure 206 | returns (bytes memory) 207 | { 208 | return abi.encode("Withdrawal", blockNum, index); 209 | } 210 | 211 | /** 212 | * @notice Produce the fact signature for a withdrawal 213 | * @param blockNum the block number 214 | * @param index the withdrawal index 215 | */ 216 | function withdrawalFactSig(uint256 blockNum, uint256 index) 217 | internal 218 | pure 219 | returns (FactSignature) 220 | { 221 | return Facts.toFactSignature(Facts.NO_FEE, withdrawalSigData(blockNum, index)); 222 | } 223 | 224 | /** 225 | * @notice Produce the fact signature data for an event fact 226 | * @param eventId The event in question 227 | */ 228 | function eventFactSigData(uint64 eventId) internal pure returns (bytes memory) { 229 | return abi.encode("EventAttendance", "EventID", eventId); 230 | } 231 | 232 | /** 233 | * @notice Produce a fact signature for a given event 234 | * @param eventId The event in question 235 | */ 236 | function eventFactSig(uint64 eventId) internal pure returns (FactSignature) { 237 | return Facts.toFactSignature(Facts.NO_FEE, eventFactSigData(eventId)); 238 | } 239 | 240 | /** 241 | * @notice Produce the fact signature data for a transaction fact 242 | * @param transaction the transaction hash to be proven 243 | */ 244 | function transactionFactSigData(bytes32 transaction) internal pure returns (bytes memory) { 245 | return abi.encode("Transaction", transaction); 246 | } 247 | 248 | /** 249 | * @notice Produce a fact signature for a transaction 250 | * @param transaction the transaction hash to be proven 251 | */ 252 | function transactionFactSig(bytes32 transaction) internal pure returns (FactSignature) { 253 | return Facts.toFactSignature(Facts.NO_FEE, transactionFactSigData(transaction)); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /packages/contracts/lib/Facts.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | type FactSignature is bytes32; 6 | 7 | struct Fact { 8 | address account; 9 | FactSignature sig; 10 | bytes data; 11 | } 12 | 13 | /** 14 | * @title Facts 15 | * @author Theori, Inc. 16 | * @notice Helper functions for fact classes (part of fact signature that determines fee). 17 | */ 18 | library Facts { 19 | uint8 internal constant NO_FEE = 0; 20 | 21 | /** 22 | * @notice construct a fact signature from a fact class and some unique data 23 | * @param cls the fact class (determines the fee) 24 | * @param data the unique data for the signature 25 | */ 26 | function toFactSignature(uint8 cls, bytes memory data) internal pure returns (FactSignature) { 27 | return FactSignature.wrap(bytes32((uint256(keccak256(data)) << 8) | cls)); 28 | } 29 | 30 | /** 31 | * @notice extracts the fact class from a fact signature 32 | * @param factSig the input fact signature 33 | */ 34 | function toFactClass(FactSignature factSig) internal pure returns (uint8) { 35 | return uint8(uint256(FactSignature.unwrap(factSig))); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/contracts/lib/RLP.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /** 6 | * @title RLP 7 | * @author Theori, Inc. 8 | * @notice Gas optimized RLP parsing code. Note that some parsing logic is 9 | * duplicated because helper functions are oddly expensive. 10 | */ 11 | library RLP { 12 | function parseUint(bytes calldata buf) internal pure returns (uint256 result, uint256 size) { 13 | assembly { 14 | // check that we have at least one byte of input 15 | if iszero(buf.length) { 16 | revert(0, 0) 17 | } 18 | let first32 := calldataload(buf.offset) 19 | let kind := shr(248, first32) 20 | 21 | // ensure it's a not a long string or list (> 0xB7) 22 | // also ensure it's not a short string longer than 32 bytes (> 0xA0) 23 | if gt(kind, 0xA0) { 24 | revert(0, 0) 25 | } 26 | 27 | switch lt(kind, 0x80) 28 | case true { 29 | // small single byte 30 | result := kind 31 | size := 1 32 | } 33 | case false { 34 | // short string 35 | size := sub(kind, 0x80) 36 | 37 | // ensure it's not reading out of bounds 38 | if lt(buf.length, size) { 39 | revert(0, 0) 40 | } 41 | 42 | switch eq(size, 32) 43 | case true { 44 | // if it's exactly 32 bytes, read it from calldata 45 | result := calldataload(add(buf.offset, 1)) 46 | } 47 | case false { 48 | // if it's < 32 bytes, we've already read it from calldata 49 | result := shr(shl(3, sub(32, size)), shl(8, first32)) 50 | } 51 | size := add(size, 1) 52 | } 53 | } 54 | } 55 | 56 | function nextSize(bytes calldata buf) internal pure returns (uint256 size) { 57 | assembly { 58 | if iszero(buf.length) { 59 | revert(0, 0) 60 | } 61 | let first32 := calldataload(buf.offset) 62 | let kind := shr(248, first32) 63 | 64 | switch lt(kind, 0x80) 65 | case true { 66 | // small single byte 67 | size := 1 68 | } 69 | case false { 70 | switch lt(kind, 0xB8) 71 | case true { 72 | // short string 73 | size := add(1, sub(kind, 0x80)) 74 | } 75 | case false { 76 | switch lt(kind, 0xC0) 77 | case true { 78 | // long string 79 | let lengthSize := sub(kind, 0xB7) 80 | 81 | // ensure that we don't overflow 82 | if gt(lengthSize, 31) { 83 | revert(0, 0) 84 | } 85 | 86 | // ensure that we don't read out of bounds 87 | if lt(buf.length, lengthSize) { 88 | revert(0, 0) 89 | } 90 | size := shr(mul(8, sub(32, lengthSize)), shl(8, first32)) 91 | size := add(size, add(1, lengthSize)) 92 | } 93 | case false { 94 | switch lt(kind, 0xF8) 95 | case true { 96 | // short list 97 | size := add(1, sub(kind, 0xC0)) 98 | } 99 | case false { 100 | let lengthSize := sub(kind, 0xF7) 101 | 102 | // ensure that we don't overflow 103 | if gt(lengthSize, 31) { 104 | revert(0, 0) 105 | } 106 | // ensure that we don't read out of bounds 107 | if lt(buf.length, lengthSize) { 108 | revert(0, 0) 109 | } 110 | size := shr(mul(8, sub(32, lengthSize)), shl(8, first32)) 111 | size := add(size, add(1, lengthSize)) 112 | } 113 | } 114 | } 115 | } 116 | } 117 | } 118 | 119 | function skip(bytes calldata buf) internal pure returns (bytes calldata) { 120 | uint256 size = RLP.nextSize(buf); 121 | assembly { 122 | buf.offset := add(buf.offset, size) 123 | buf.length := sub(buf.length, size) 124 | } 125 | return buf; 126 | } 127 | 128 | function parseList(bytes calldata buf) 129 | internal 130 | pure 131 | returns (uint256 listSize, uint256 offset) 132 | { 133 | assembly { 134 | // check that we have at least one byte of input 135 | if iszero(buf.length) { 136 | revert(0, 0) 137 | } 138 | let first32 := calldataload(buf.offset) 139 | let kind := shr(248, first32) 140 | 141 | // ensure it's a list 142 | if lt(kind, 0xC0) { 143 | revert(0, 0) 144 | } 145 | 146 | switch lt(kind, 0xF8) 147 | case true { 148 | // short list 149 | listSize := sub(kind, 0xC0) 150 | offset := 1 151 | } 152 | case false { 153 | // long list 154 | let lengthSize := sub(kind, 0xF7) 155 | 156 | // ensure that we don't overflow 157 | if gt(lengthSize, 31) { 158 | revert(0, 0) 159 | } 160 | // ensure that we don't read out of bounds 161 | if lt(buf.length, lengthSize) { 162 | revert(0, 0) 163 | } 164 | listSize := shr(mul(8, sub(32, lengthSize)), shl(8, first32)) 165 | offset := add(lengthSize, 1) 166 | } 167 | } 168 | } 169 | 170 | function splitBytes(bytes calldata buf) 171 | internal 172 | pure 173 | returns (bytes calldata result, bytes calldata rest) 174 | { 175 | uint256 offset; 176 | uint256 size; 177 | assembly { 178 | // check that we have at least one byte of input 179 | if iszero(buf.length) { 180 | revert(0, 0) 181 | } 182 | let first32 := calldataload(buf.offset) 183 | let kind := shr(248, first32) 184 | 185 | // ensure it's a not list 186 | if gt(kind, 0xBF) { 187 | revert(0, 0) 188 | } 189 | 190 | switch lt(kind, 0x80) 191 | case true { 192 | // small single byte 193 | offset := 0 194 | size := 1 195 | } 196 | case false { 197 | switch lt(kind, 0xB8) 198 | case true { 199 | // short string 200 | offset := 1 201 | size := sub(kind, 0x80) 202 | } 203 | case false { 204 | // long string 205 | let lengthSize := sub(kind, 0xB7) 206 | 207 | // ensure that we don't overflow 208 | if gt(lengthSize, 31) { 209 | revert(0, 0) 210 | } 211 | // ensure we don't read out of bounds 212 | if lt(buf.length, lengthSize) { 213 | revert(0, 0) 214 | } 215 | size := shr(mul(8, sub(32, lengthSize)), shl(8, first32)) 216 | offset := add(lengthSize, 1) 217 | } 218 | } 219 | 220 | result.offset := add(buf.offset, offset) 221 | result.length := size 222 | 223 | let end := add(offset, size) 224 | rest.offset := add(buf.offset, end) 225 | rest.length := sub(buf.length, end) 226 | } 227 | } 228 | 229 | function encodeUint(uint256 value) internal pure returns (bytes memory) { 230 | // allocate our result bytes 231 | bytes memory result = new bytes(33); 232 | 233 | if (value == 0) { 234 | // store length = 1, value = 0x80 235 | assembly { 236 | mstore(add(result, 1), 0x180) 237 | } 238 | return result; 239 | } 240 | 241 | if (value < 128) { 242 | // store length = 1, value = value 243 | assembly { 244 | mstore(add(result, 1), or(0x100, value)) 245 | } 246 | return result; 247 | } 248 | 249 | if (value > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) { 250 | // length 33, prefix 0xa0 followed by value 251 | assembly { 252 | mstore(add(result, 1), 0x21a0) 253 | mstore(add(result, 33), value) 254 | } 255 | return result; 256 | } 257 | 258 | if (value > 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff) { 259 | // length 32, prefix 0x9f followed by value 260 | assembly { 261 | mstore(add(result, 1), 0x209f) 262 | mstore(add(result, 33), shl(8, value)) 263 | } 264 | return result; 265 | } 266 | 267 | assembly { 268 | let length := 1 269 | for { 270 | let min := 0x100 271 | } lt(sub(min, 1), value) { 272 | min := shl(8, min) 273 | } { 274 | length := add(length, 1) 275 | } 276 | 277 | let bytesLength := add(length, 1) 278 | 279 | // bytes length field 280 | let hi := shl(mul(bytesLength, 8), bytesLength) 281 | 282 | // rlp encoding of value 283 | let lo := or(shl(mul(length, 8), add(length, 0x80)), value) 284 | 285 | mstore(add(result, bytesLength), or(hi, lo)) 286 | } 287 | return result; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /packages/contracts/lib/Storage.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | /** 6 | * @title Storage 7 | * @author Theori, Inc. 8 | * @notice Helper functions for handling storage slot facts and computing storage slots 9 | */ 10 | library Storage { 11 | /** 12 | * @notice compute the slot for an element of a mapping 13 | * @param base the slot of the struct base 14 | * @param key the mapping key, padded to 32 bytes 15 | */ 16 | function mapElemSlot(bytes32 base, bytes32 key) internal pure returns (bytes32) { 17 | return keccak256(abi.encodePacked(key, base)); 18 | } 19 | 20 | /** 21 | * @notice compute the slot for an element of a static array 22 | * @param base the slot of the struct base 23 | * @param idx the index of the element 24 | * @param slotsPerElem the number of slots per element 25 | */ 26 | function staticArrayElemSlot( 27 | bytes32 base, 28 | uint256 idx, 29 | uint256 slotsPerElem 30 | ) internal pure returns (bytes32) { 31 | return bytes32(uint256(base) + idx * slotsPerElem); 32 | } 33 | 34 | /** 35 | * @notice compute the slot for an element of a dynamic array 36 | * @param base the slot of the struct base 37 | * @param idx the index of the element 38 | * @param slotsPerElem the number of slots per element 39 | */ 40 | function dynamicArrayElemSlot( 41 | bytes32 base, 42 | uint256 idx, 43 | uint256 slotsPerElem 44 | ) internal pure returns (bytes32) { 45 | return bytes32(uint256(keccak256(abi.encode(base))) + idx * slotsPerElem); 46 | } 47 | 48 | /** 49 | * @notice compute the slot for a struct field given the base slot and offset 50 | * @param base the slot of the struct base 51 | * @param offset the slot offset in the struct 52 | */ 53 | function structFieldSlot( 54 | bytes32 base, 55 | uint256 offset 56 | ) internal pure returns (bytes32) { 57 | return bytes32(uint256(base) + offset); 58 | } 59 | 60 | function _parseUint256(bytes memory data) internal pure returns (uint256) { 61 | return uint256(bytes32(data)) >> (256 - 8 * data.length); 62 | } 63 | 64 | /** 65 | * @notice parse a uint256 from storage slot bytes 66 | * @param data the storage slot bytes 67 | * @return address the parsed address 68 | */ 69 | function parseUint256(bytes memory data) internal pure returns (uint256) { 70 | require(data.length <= 32, 'data is not a uint256'); 71 | return _parseUint256(data); 72 | } 73 | 74 | /** 75 | * @notice parse a uint64 from storage slot bytes 76 | * @param data the storage slot bytes 77 | */ 78 | function parseUint64(bytes memory data) internal pure returns (uint64) { 79 | require(data.length <= 8, 'data is not a uint64'); 80 | return uint64(_parseUint256(data)); 81 | } 82 | 83 | /** 84 | * @notice parse an address from storage slot bytes 85 | * @param data the storage slot bytes 86 | */ 87 | function parseAddress(bytes memory data) internal pure returns (address) { 88 | require(data.length <= 20, 'data is not an address'); 89 | return address(uint160(_parseUint256(data))); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /packages/contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@relicprotocol/contracts", 3 | "description": "Smart Contracts and libraries for interacting with Relic Protocol", 4 | "version": "0.2.0", 5 | "files": [ 6 | "**/*.sol", 7 | "abi/*.json" 8 | ], 9 | "scripts": { 10 | "clean": "rm -rf artifacts cache;", 11 | "build:pre": "npm run clean; rm -rf abi;", 12 | "build:source": "hardhat compile;", 13 | "build:post": "scripts/pack.sh;", 14 | "build": "npm run clean; npm run build:pre; npm run build:source && npm run build:post; npm run clean;", 15 | "prepare": "npm run build;" 16 | }, 17 | "dependencies": { 18 | "@openzeppelin/contracts": "^4.7.3" 19 | }, 20 | "devDependencies": { 21 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.4", 22 | "@nomiclabs/hardhat-ethers": "^2.2.0", 23 | "@types/mocha": "^10.0.0", 24 | "chai": "^4.3.6", 25 | "ethers": "^5.7.1", 26 | "hardhat": "^2.10.1", 27 | "solc": ">=0.8.12", 28 | "ts-node": "^10.9.1", 29 | "typescript": "^4.8.4" 30 | }, 31 | "keywords": [ 32 | "relic", 33 | "reliquary", 34 | "sdk", 35 | "historical", 36 | "state", 37 | "query", 38 | "contracts", 39 | "library" 40 | ], 41 | "homepage": "https://docs.relicprotocol.com/", 42 | "license": "MIT", 43 | "repository": { 44 | "type": "git", 45 | "url": "https://github.com/Relic-Protocol/relic-sdk" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/contracts/scripts/pack.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir abi; 4 | cp artifacts/{lib/,interfaces/,}*/*.json abi/; 5 | -------------------------------------------------------------------------------- /packages/contracts/test/BirthCertificateVerifierTest.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../BirthCertificateVerifier.sol"; 5 | import "../interfaces/IReliquary.sol"; 6 | 7 | contract BirthCertificateVerifierTest is BirthCertificateVerifier { 8 | constructor(IReliquary reliquary) BirthCertificateVerifier(reliquary) { } 9 | 10 | function testOlderThan(uint256 age) external onlyOlderThan(age) { } 11 | function testOlderThanBlocks(uint256 age) external onlyOlderThanBlocks(age) { } 12 | function testBornBefore(uint256 timestamp) external onlyBornBefore(timestamp) { } 13 | function testBornBeforeBlock(uint256 blockNum) external onlyBornBeforeBlock(blockNum) { } 14 | } 15 | -------------------------------------------------------------------------------- /packages/contracts/test/RelicReceiverTest.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.0; 4 | 5 | import '../RelicReceiver.sol'; 6 | 7 | /** 8 | * @title RelicReceiverTest 9 | * @author Theori, Inc. 10 | * @notice 11 | */ 12 | contract RelicReceiverTest is RelicReceiver { 13 | constructor(IEphemeralFacts ephemeralFacts) RelicReceiver(ephemeralFacts) { } 14 | 15 | event StorageSlotFact( 16 | address initiator, 17 | address account, 18 | bytes32 slot, 19 | uint256 blockNum, 20 | bytes32 value 21 | ); 22 | event BirthCertificateFact( 23 | address initiator, 24 | address account, 25 | uint256 blockNum, 26 | uint256 timestamp 27 | ); 28 | event BlockHeaderFact( 29 | address initiator, 30 | uint256 blockNum, 31 | CoreTypes.BlockHeaderData header 32 | ); 33 | 34 | function receiveStorageSlotFact( 35 | address initiator, 36 | address account, 37 | bytes32 slot, 38 | uint256 blockNum, 39 | bytes32 value 40 | ) internal override { 41 | emit StorageSlotFact(initiator, account, slot, blockNum, value); 42 | } 43 | 44 | function receiveBirthCertificateFact( 45 | address initiator, 46 | address account, 47 | uint256 blockNum, 48 | uint256 timestamp 49 | ) internal override { 50 | emit BirthCertificateFact(initiator, account, blockNum, timestamp); 51 | } 52 | 53 | function receiveBlockHeaderFact( 54 | address initiator, 55 | uint256 blockNum, 56 | CoreTypes.BlockHeaderData memory header 57 | ) internal override { 58 | emit BlockHeaderFact(initiator, blockNum, header); 59 | } 60 | } -------------------------------------------------------------------------------- /packages/contracts/test/StorageParseTest.sol: -------------------------------------------------------------------------------- 1 | /// SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | import "../lib/Storage.sol"; 5 | 6 | contract StorageParseTest { 7 | constructor() {} 8 | 9 | function testParseUint256(bytes memory data) external pure returns (uint256) { 10 | return Storage.parseUint256(data); 11 | } 12 | 13 | function testParseUint64(bytes memory data) external pure returns (uint64) { 14 | return Storage.parseUint64(data); 15 | } 16 | 17 | function testParseAddress(bytes memory data) external pure returns (address) { 18 | return Storage.parseAddress(data); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "build", 5 | "rootDir": "." 6 | }, 7 | "include": ["test"], 8 | "exclude": ["contracts"], 9 | "files": ["./hardhat.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/types/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | tsconfig.tsbuildinfo 4 | -------------------------------------------------------------------------------- /packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@relicprotocol/types", 3 | "version": "0.2.0", 4 | "description": "Typings for Relic Protocol SDK", 5 | "main": "dist/index.cjs.js", 6 | "module": "dist/index.es.js", 7 | "unpkg": "dist/index.umd.js", 8 | "types": "dist/types/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "types": "./dist/types/index.d.ts", 12 | "import": "./dist/index.es.js", 13 | "require": "./dist/index.cjs.js" 14 | } 15 | }, 16 | "files": [ 17 | "dist" 18 | ], 19 | "scripts": { 20 | "clean": "rm -rf *.tsbuildinfo;", 21 | "build:pre": "npm run clean; rm -rf dist;", 22 | "build:types": "tsc", 23 | "build:source": "rollup --config rollup.config.mjs", 24 | "build": "npm run clean; npm run build:pre; npm run build:source && npm run build:types; npm run clean;", 25 | "prepare": "npm run build" 26 | }, 27 | "keywords": [ 28 | "relic", 29 | "reliquary", 30 | "client", 31 | "sdk", 32 | "historical", 33 | "state", 34 | "query" 35 | ], 36 | "homepage": "https://docs.relicprotocol.com/", 37 | "license": "MIT", 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/Relic-Protocol/relic-sdk" 41 | }, 42 | "dependencies": { 43 | "ethers": "^5.7.2" 44 | }, 45 | "typedoc": { 46 | "entryPoint": "src/index.ts" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/types/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' assert { type: 'json' } 2 | import createConfig from '../../rollup.config.mjs' 3 | 4 | export default createConfig(pkg.name, Object.keys(pkg.dependencies)) 5 | -------------------------------------------------------------------------------- /packages/types/src/client/api.ts: -------------------------------------------------------------------------------- 1 | import { BigNumberish } from 'ethers' 2 | import { ZeroExString } from './utils' 3 | 4 | 5 | export interface ErrorResult { 6 | error: string 7 | } 8 | 9 | export interface Proof { } 10 | 11 | export interface AccountProof extends BaseAccountProof { 12 | balance: BigNumberish 13 | nonce: number 14 | codeHash: ZeroExString 15 | storageHash: ZeroExString 16 | } 17 | 18 | export interface AttendanceProof extends Proof { 19 | account: ZeroExString 20 | eventId: string 21 | number: BigNumberish 22 | signatureInner: ZeroExString 23 | signatureOuter: ZeroExString 24 | } 25 | 26 | export interface BaseAccountProof extends BlockProof { 27 | account: ZeroExString 28 | accountProof: ZeroExString 29 | } 30 | 31 | export interface BlockProof extends Proof { 32 | blockNum: number 33 | header: ZeroExString 34 | blockProof: ZeroExString 35 | } 36 | 37 | export interface LogProof extends BlockProof { 38 | txIdx: number 39 | logIdx: number 40 | receiptProof: ZeroExString 41 | } 42 | 43 | export interface StorageSlotProof extends BaseAccountProof { 44 | slot: BigNumberish 45 | slotValue: BigNumberish 46 | slotProof: ZeroExString 47 | } 48 | 49 | export interface TransactionProof extends BlockProof { 50 | txProof: ZeroExString 51 | txIdx: number 52 | txHash: BigNumberish 53 | } 54 | 55 | export interface WithdrawalProof extends BlockProof { 56 | idx: number 57 | withdrawalProof: ZeroExString 58 | } -------------------------------------------------------------------------------- /packages/types/src/client/client.ts: -------------------------------------------------------------------------------- 1 | import { ZeroExString } from './utils' 2 | 3 | export interface RelicAddresses { 4 | reliquary: ZeroExString 5 | legacyBlockHistory: ZeroExString 6 | blockHistory: ZeroExString 7 | messenger: ZeroExString 8 | ephemeralFacts: ZeroExString 9 | accountInfoProver: ZeroExString 10 | accountStorageProver: ZeroExString 11 | attendanceProver: ZeroExString 12 | birthCertificateProver: ZeroExString 13 | blockHeaderProver: ZeroExString 14 | cachedMultiStorageSlotProver: ZeroExString 15 | cachedStorageSlotProver: ZeroExString 16 | logProver: ZeroExString 17 | multiStorageSlotProver: ZeroExString 18 | storageSlotProver: ZeroExString 19 | transactionProver: ZeroExString 20 | withdrawalProver: ZeroExString 21 | } 22 | 23 | export interface RelicConfig { 24 | apiUrl: string 25 | addresses: RelicAddresses 26 | } 27 | -------------------------------------------------------------------------------- /packages/types/src/client/index.ts: -------------------------------------------------------------------------------- 1 | export * from './api' 2 | export * from './client' 3 | export * from './prover' 4 | export * from './utils' 5 | -------------------------------------------------------------------------------- /packages/types/src/client/prover.ts: -------------------------------------------------------------------------------- 1 | import type { ethers } from 'ethers' 2 | 3 | export interface Prover { 4 | prove: (params: any) => Promise 5 | fee: () => Promise 6 | } 7 | 8 | export interface ReceiverContext { 9 | initiator: string 10 | receiver: string 11 | extra: string 12 | gasLimit: ethers.BigNumberish 13 | } 14 | 15 | export interface EphemeralProver extends Prover { 16 | proveEphemeral: ( 17 | context: ReceiverContext, 18 | ...args: any[] 19 | ) => Promise 20 | } 21 | -------------------------------------------------------------------------------- /packages/types/src/client/utils.ts: -------------------------------------------------------------------------------- 1 | export type ZeroExString = `0x${string}` 2 | -------------------------------------------------------------------------------- /packages/types/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './client' 2 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "emitDeclarationOnly": true, 5 | "outDir": "./dist/types", 6 | "rootDir": "src" 7 | }, 8 | "include": ["src/**/*"] 9 | } 10 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | // Note. https://github.com/WalletConnect/walletconnect-monorepo/blob/v2.0/rollup.config.js 2 | 3 | import esbuild from 'rollup-plugin-esbuild' 4 | import { nodeResolve } from '@rollup/plugin-node-resolve' 5 | import commonjs from '@rollup/plugin-commonjs' 6 | import json from '@rollup/plugin-json' 7 | 8 | const input = './src/index.ts' 9 | 10 | const plugins = [ 11 | json(), 12 | nodeResolve({ preferBuiltins: false, browser: true }), 13 | commonjs(), 14 | esbuild({ 15 | tsconfig: './tsconfig.json', 16 | }), 17 | ] 18 | 19 | export const createConfig = (name, external, externalUMD) => { 20 | return [ 21 | { 22 | input, 23 | plugins, 24 | external: externalUMD, 25 | output: { 26 | file: './dist/index.umd.js', 27 | format: 'umd', 28 | exports: 'named', 29 | name, 30 | sourcemap: true, 31 | }, 32 | }, 33 | { 34 | input, 35 | plugins, 36 | external, 37 | output: [ 38 | { 39 | file: './dist/index.cjs.js', 40 | format: 'cjs', 41 | exports: 'named', 42 | name, 43 | sourcemap: true, 44 | }, 45 | { 46 | file: './dist/index.es.js', 47 | format: 'es', 48 | exports: 'named', 49 | name, 50 | sourcemap: true, 51 | }, 52 | ], 53 | }, 54 | ] 55 | } 56 | 57 | export default createConfig 58 | -------------------------------------------------------------------------------- /scripts/test/clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Clean the hardhat-related 4 | hardhat clean 5 | -------------------------------------------------------------------------------- /scripts/test/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Compile contracts 4 | hardhat compile; 5 | -------------------------------------------------------------------------------- /test/fixtures.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process' 2 | 3 | export function mochaGlobalSetup() { 4 | execSync('scripts/test/prepare.sh') 5 | } 6 | 7 | export function mochaGlobalTeardown() { 8 | execSync('scripts/test/clean.sh') 9 | } 10 | -------------------------------------------------------------------------------- /test/with-hardhat/birthcert.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { ethers } from 'hardhat' 3 | import { RelicClient } from '../../packages/client' 4 | 5 | const RELIQUARY_ADDRESS = '0x5E4DE6Bb8c6824f29c44Bd3473d44da120387d08' 6 | const ADDR = '0xf979392E396dc53faB7B3C430dD385e73dD0A4e2' 7 | const BIRTH_BLOCK = 15572049 8 | 9 | describe('BirthCertificateVerifier', function () { 10 | it('test modifiers', async function () { 11 | const provider = ethers.provider 12 | 13 | const BirthCertificateVerifierTest = await ethers.getContractFactory( 14 | 'BirthCertificateVerifierTest' 15 | ) 16 | const deployed = await BirthCertificateVerifierTest.deploy( 17 | RELIQUARY_ADDRESS 18 | ) 19 | await deployed.deployed() 20 | 21 | const impersonatedSigner = await ethers.getImpersonatedSigner(ADDR) 22 | const bcvt = await deployed.connect(impersonatedSigner) 23 | const fundTx = await ( 24 | await ethers.getSigners() 25 | )[0].sendTransaction({ to: ADDR, value: '100000000000000000000' }) 26 | await fundTx.wait() 27 | 28 | // check that it fails before proving a birth certificate 29 | expect(bcvt.testBornBeforeBlock(BIRTH_BLOCK + 1)).to.be.revertedWith( 30 | 'account has no proven birth certificate' 31 | ) 32 | 33 | const relic = await RelicClient.fromProvider(provider) 34 | 35 | // prove the birth certificate 36 | let tx = await impersonatedSigner.sendTransaction( 37 | await relic.birthCertificateProver.prove({ account: ADDR }) 38 | ) 39 | await tx.wait() 40 | 41 | // now it succeeds, but the previous block fails 42 | await bcvt.testBornBeforeBlock(BIRTH_BLOCK + 1) 43 | await expect(bcvt.testBornBeforeBlock(BIRTH_BLOCK)).to.be.revertedWith( 44 | 'account is not old enough' 45 | ) 46 | 47 | let block = await provider.getBlock(BIRTH_BLOCK) 48 | const birthTime = block.timestamp 49 | 50 | // test the timestamp modifiers 51 | await bcvt.testBornBefore(birthTime + 1) 52 | await expect(bcvt.testBornBefore(birthTime)).to.be.revertedWith( 53 | 'account is not old enough' 54 | ) 55 | 56 | block = await provider.getBlock() 57 | const curAge = block.timestamp - birthTime 58 | const curAgeBlocks = block.number - BIRTH_BLOCK 59 | 60 | await bcvt.testOlderThan(curAge - 1) 61 | await expect(bcvt.testOlderThan(curAge + 100)).to.be.revertedWith( 62 | 'account is not old enough' 63 | ) 64 | 65 | await bcvt.testOlderThanBlocks(curAgeBlocks - 1) 66 | await expect( 67 | bcvt.testOlderThanBlocks(curAgeBlocks + 100) 68 | ).to.be.revertedWith('account is not old enough') 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /test/with-hardhat/receiver.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { ethers } from 'hardhat' 3 | import { RelicClient } from '../../packages/client' 4 | 5 | describe('RelicReceiver', function () { 6 | it('test fact receiving', async function () { 7 | const provider = ethers.provider 8 | const relic = await RelicClient.fromProvider(provider) 9 | 10 | const ephemeralFacts = await ethers.getContractAt( 11 | 'IEphemeralFacts', 12 | relic.addresses.ephemeralFacts 13 | ) 14 | const RelicReceiver = await ethers.getContractFactory('RelicReceiverTest') 15 | const receiver = await RelicReceiver.deploy(ephemeralFacts.address) 16 | await receiver.deployed() 17 | 18 | const slot = '0x' + '0'.repeat(64) 19 | const blockNum = 15000000 20 | 21 | const [signer] = await ethers.getSigners() 22 | const context = { 23 | initiator: signer.address, 24 | receiver: receiver.address, 25 | gasLimit: 1000000, 26 | } 27 | 28 | let call = await relic.storageSlotProver.proveEphemeral(context, { 29 | block: 15000000, 30 | account: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 31 | slot, 32 | }) 33 | let tx = await signer.sendTransaction(call) 34 | await expect(tx).to.emit(receiver, 'StorageSlotFact') 35 | 36 | call = await relic.blockHeaderProver.proveEphemeral(context, { 37 | block: 15000000, 38 | }) 39 | tx = await signer.sendTransaction(call) 40 | await expect(tx).to.emit(ephemeralFacts, 'ReceiveSuccess') 41 | await expect(tx).to.emit(receiver, 'BlockHeaderFact') 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /test/with-hardhat/storage.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { ethers } from 'hardhat' 3 | 4 | describe('Storage parse', function () { 5 | it('test parsing', async function () { 6 | const StorageParseTest = await ethers.getContractFactory('StorageParseTest') 7 | const spt = await StorageParseTest.deploy() 8 | await spt.deployed() 9 | 10 | // test that too much data reverts 11 | await expect( 12 | spt.testParseUint64('0xaaaaaaaaaaaaaaaaaa') 13 | ).to.be.revertedWith('data is not a uint64') 14 | 15 | // test that too much data reverts 16 | expect(await spt.testParseUint64('0xaaaaaaaaaaaaaaaa')).to.equal( 17 | ethers.BigNumber.from('0xaaaaaaaaaaaaaaaa') 18 | ) 19 | 20 | // test leading zeros get addedd 21 | expect( 22 | await spt.testParseAddress('0x111111111111111111111111111111111111') 23 | ).to.equal( 24 | ethers.utils.getAddress('0x0000111111111111111111111111111111111111') 25 | ) 26 | 27 | // test that too much data reverts 28 | await expect( 29 | spt.testParseAddress('0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') 30 | ).to.be.revertedWith('data is not an address') 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2020", 5 | "module": "commonjs", 6 | "strict": true, 7 | "moduleResolution": "node", 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "sourceMap": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "composite": true, 14 | "baseUrl": "." 15 | }, 16 | "include": ["test/**/*.test.ts", "hardhat.config.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Relic SDK", 3 | "out": "docs/ts-sdk", 4 | "theme": "default", 5 | "entryPoints": ["./packages/client", "./packages/types"], 6 | "entryPointStrategy": "packages" 7 | } 8 | --------------------------------------------------------------------------------