-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
4,852 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 Nick Johnson | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# @ensdomains/arb-gateway | ||
|
||
An instantiation of [evm-gateway](https://github.com/ensdomains/evmgateway/tree/main/evm-gateway) that targets Arbitrum - that is, it implements a CCIP-Read gateway that generates proofs of contract state on Arbitrum. | ||
|
||
For a detailed readme and usage instructions, see the [monorepo readme](https://github.com/ensdomains/evmgateway/tree/main). | ||
|
||
To get started, you need to have an RPC URL for both Ethereum Mainnet and Arbitrum. You also need to provide an L2_ROLLUP address which is the Rollup contract deployed on Mainnet or the Nitro Node. | ||
|
||
## How to use arb-gateway locally via cloudflare dev env (aka wrangler) | ||
|
||
``` | ||
npm install -g bun | ||
cd arb-gateway | ||
bun install | ||
touch .dev.vars | ||
## set L1_PROVIDER_URL, L2_PROVIDER_URL, L2_ROLLUP | ||
yarn dev | ||
``` | ||
|
||
## How to deploy arb-gateway to cloudflare | ||
|
||
``` | ||
cd arb-gateway | ||
npm install -g wrangler | ||
wrngler login | ||
wrangler secret put L1_PROVIDER_URL | ||
wrangler secret put L2_PROVIDER_URL | ||
wrangler secret put L2_ROLLUP | ||
yarn deploy | ||
``` | ||
|
||
## How to test | ||
|
||
1. Start the Nitro Test node. You can find instructions here: https://docs.arbitrum.io/node-running/how-tos/local-dev-node | ||
2. Retrieve the Rollup address from the Node's Logs. | ||
3. Copy the example.env file in both arb-gateway and arb-verifier, and add the Rollup address. | ||
4. Build the Project. | ||
5. Navigate to the Gateway directory using `cd ./arb-gateway`. | ||
6. Start the Gateway by running `bun run start -u http://127.0.0.1:8545/ -v http://127.0.0.1:8547/ -p 8089`. | ||
7. Open another Terminal Tab and navigate to the verifier directory using `cd ./arb-verifier/`. | ||
8. Deploy contracts to the node using the command ` npx hardhat --network arbDevnetL2 deploy && npx hardhat --network arbDevnetL1 deploy `. | ||
9. Run the test using the command `bun run test`. | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
{ | ||
"name": "@ensdomains/arb-gateway", | ||
"version": "0.1.0", | ||
"author": "Nick Johnson", | ||
"license": "MIT", | ||
"type": "module", | ||
"main": "./_cjs/index.js", | ||
"module": "./_esm/index.js", | ||
"types": "./_types/index.d.ts", | ||
"typings": "./_types/index.d.ts", | ||
"bin": "./_cjs/server.js", | ||
"sideEffects": false, | ||
"files": [ | ||
"_esm", | ||
"_cjs", | ||
"_types", | ||
"src", | ||
"!**/*.tsbuildinfo" | ||
], | ||
"exports": { | ||
".": { | ||
"types": "./_types/index.d.ts", | ||
"import": "./_esm/index.js", | ||
"require": "./_cjs/index.js" | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"engines": { | ||
"node": ">=10", | ||
"bun": ">=1.0.4" | ||
}, | ||
"peerDependencies": { | ||
"typescript": ">=5.0.4" | ||
}, | ||
"peerDependenciesMeta": { | ||
"typescript": { | ||
"optional": true | ||
} | ||
}, | ||
"scripts": { | ||
"start": "bun ./src/server.ts", | ||
"dev": "wrangler dev", | ||
"deploy": "wrangler deploy", | ||
"build:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir ./_cjs --removeComments --verbatimModuleSyntax false && echo > ./_cjs/package.json '{\"type\":\"commonjs\"}'", | ||
"build:esm": "tsc --project tsconfig.build.json --module es2022 --outDir ./_esm && echo > ./_esm/package.json '{\"type\":\"module\",\"sideEffects\":false}'", | ||
"build:types": "tsc --project ./tsconfig.build.json --module esnext --declarationDir ./_types --emitDeclarationOnly --declaration --declarationMap", | ||
"build": "echo 'building arb-gateway...' && bun run clean && bun run build:cjs && bun run build:esm && bun run build:types", | ||
"prepublishOnly": "bun ../scripts/prepublishOnly.ts", | ||
"lint": "eslint . --ext .ts", | ||
"prepare": "bun run build", | ||
"clean": "rm -fr _cjs _esm _types" | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "bun run lint" | ||
} | ||
}, | ||
"dependencies": { | ||
"@chainlink/ccip-read-server": "^0.2.1", | ||
"@ensdomains/evm-gateway": "^0.1.0", | ||
"@ethereumjs/block": "^5.0.0", | ||
"@nomicfoundation/ethereumjs-block": "^5.0.2", | ||
"commander": "^11.0.0", | ||
"ethers": "^6.7.1" | ||
}, | ||
"devDependencies": { | ||
"@commander-js/extra-typings": "^11.0.0" | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
/* eslint-disable prettier/prettier */ | ||
import { EVMProofHelper, type IProofService } from '@ensdomains/evm-gateway'; | ||
import { AbiCoder, Contract, EventLog, ethers, toBeHex, type AddressLike, toNumber } from 'ethers'; | ||
import rollupAbi from "./abi/rollupABI.js"; | ||
import type { IBlockCache } from './blockCache/IBlockCache.js'; | ||
export interface ArbProvableBlock { | ||
number: number | ||
sendRoot: string, | ||
nodeIndex: string, | ||
rlpEncodedBlock: string | ||
} | ||
|
||
|
||
/** | ||
* The proofService class can be used to calculate proofs for a given target and slot on the Arbitrum network. | ||
* It's also capable of proofing long types such as mappings or string by using all included slots in the proof. | ||
* | ||
*/ | ||
export class ArbProofService implements IProofService<ArbProvableBlock> { | ||
private readonly l2Provider: ethers.JsonRpcProvider; | ||
private readonly rollup: Contract; | ||
private readonly helper: EVMProofHelper; | ||
private readonly cache: IBlockCache; | ||
|
||
|
||
constructor( | ||
l1Provider: ethers.JsonRpcProvider, | ||
l2Provider: ethers.JsonRpcProvider, | ||
l2RollupAddress: string, | ||
cache: IBlockCache | ||
|
||
) { | ||
this.l2Provider = l2Provider; | ||
this.rollup = new Contract( | ||
l2RollupAddress, | ||
rollupAbi, | ||
l1Provider | ||
); | ||
this.helper = new EVMProofHelper(l2Provider); | ||
this.cache = cache | ||
} | ||
|
||
async getStorageAt(block: ArbProvableBlock, address: AddressLike, slot: bigint): Promise<string> { | ||
return this.helper.getStorageAt(block.number, address, slot); | ||
} | ||
|
||
|
||
/** | ||
* @dev Fetches a set of proofs for the requested state slots. | ||
* @param block A `ProvableBlock` returned by `getProvableBlock`. | ||
* @param address The address of the contract to fetch data from. | ||
* @param slots An array of slots to fetch data for. | ||
* @returns A proof of the given slots, encoded in a manner that this service's | ||
* corresponding decoding library will understand. | ||
*/ | ||
async getProofs( | ||
block: ArbProvableBlock, | ||
address: AddressLike, | ||
slots: bigint[] | ||
): Promise<string> { | ||
const proof = await this.helper.getProofs(block.number, address, slots); | ||
|
||
return AbiCoder.defaultAbiCoder().encode( | ||
[ | ||
'tuple(bytes32 version, bytes32 sendRoot, uint64 nodeIndex,bytes rlpEncodedBlock)', | ||
'tuple(bytes[] stateTrieWitness, bytes[][] storageProofs)', | ||
], | ||
[ | ||
{ | ||
version: | ||
'0x0000000000000000000000000000000000000000000000000000000000000000', | ||
sendRoot: block.sendRoot, | ||
nodeIndex: block.nodeIndex, | ||
rlpEncodedBlock: block.rlpEncodedBlock | ||
}, | ||
proof, | ||
] | ||
); | ||
} | ||
/** | ||
* Retrieves information about the latest provable block in the Arbitrum Rollup. | ||
* | ||
* @returns { Promise<ArbProvableBlock> } A promise that resolves to an object containing information about the provable block. | ||
* @throws Throws an error if any of the underlying operations fail. | ||
* | ||
* @typedef { Object } ArbProvableBlock | ||
* @property { string } rlpEncodedBlock - The RLP - encoded block information. | ||
* @property { string } sendRoot - The send root of the provable block. | ||
* @property { string } blockHash - The hash of the provable block. | ||
* @property { number } nodeIndex - The index of the node corresponding to the provable block. | ||
* @property { number } number - The block number of the provable block. | ||
*/ | ||
public async getProvableBlock(): Promise<ArbProvableBlock> { | ||
//Retrieve the latest pending node that has been committed to the rollup. | ||
const nodeIndex = await this.rollup.latestNodeCreated() | ||
const [l2blockRaw, sendRoot] = await this.getL2BlockForNode(nodeIndex) | ||
|
||
const blockarray = [ | ||
l2blockRaw.parentHash, | ||
l2blockRaw.sha3Uncles, | ||
l2blockRaw.miner, | ||
l2blockRaw.stateRoot, | ||
l2blockRaw.transactionsRoot, | ||
l2blockRaw.receiptsRoot, | ||
l2blockRaw.logsBloom, | ||
toBeHex(l2blockRaw.difficulty), | ||
toBeHex(l2blockRaw.number), | ||
toBeHex(l2blockRaw.gasLimit), | ||
toBeHex(l2blockRaw.gasUsed), | ||
toBeHex(l2blockRaw.timestamp), | ||
l2blockRaw.extraData, | ||
l2blockRaw.mixHash, | ||
l2blockRaw.nonce, | ||
toBeHex(l2blockRaw.baseFeePerGas) | ||
] | ||
|
||
//Rlp encode the block to pass it as an argument | ||
const rlpEncodedBlock = ethers.encodeRlp(blockarray) | ||
|
||
return { | ||
rlpEncodedBlock, | ||
sendRoot, | ||
nodeIndex: nodeIndex, | ||
number: toNumber(l2blockRaw.number) | ||
} | ||
} | ||
/** | ||
* Fetches the corrospending L2 block for a given node index and returns it along with the send root. | ||
* @param {bigint} nodeIndex - The index of the node for which to fetch the block. | ||
* @returns {Promise<[Record<string, string>, string]>} A promise that resolves to a tuple containing the fetched block and the send root. | ||
*/ | ||
private async getL2BlockForNode(nodeIndex: bigint): Promise<[Record<string, string>, string]> { | ||
|
||
//We first check if we have the block cached | ||
const cachedBlock = await this.cache.getBlock(nodeIndex) | ||
if (cachedBlock) { | ||
return [cachedBlock.block, cachedBlock.sendRoot] | ||
} | ||
|
||
//We fetch the node created event for the node index we just retrieved. | ||
const nodeEventFilter = await this.rollup.filters.NodeCreated(nodeIndex); | ||
const nodeEvents = await this.rollup.queryFilter(nodeEventFilter); | ||
const assertion = (nodeEvents[0] as EventLog).args!.assertion | ||
//Instead of using the AssertionHelper contract we can extract sendRoot from the assertion. Avoiding the deployment of the AssertionHelper contract and an additional RPC call. | ||
const [blockHash, sendRoot] = assertion[1][0][0] | ||
|
||
|
||
//The L1 rollup only provides us with the block hash. In order to ensure that the stateRoot we're using for the proof is indeed part of the block, we need to fetch the block. And provide it to the proof. | ||
const l2blockRaw = await this.l2Provider.send('eth_getBlockByHash', [ | ||
blockHash, | ||
false | ||
]); | ||
|
||
//Cache the block for future use | ||
await this.cache.setBlock(nodeIndex, l2blockRaw, sendRoot) | ||
|
||
return [l2blockRaw, sendRoot] | ||
} | ||
|
||
} |
Oops, something went wrong.