diff --git a/README.md b/README.md index d2a8bec7c..5aede1561 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,32 @@ yarn hardhat node --fork YOUR_GOERLI_L1_RPC_URL ### Deploy contracts -In second terminal, deploy L1 and L2 smart contracts: +In second terminal, deploy L1 and L2 smart contracts. -Compile +Set your `.env` config file. You can copy [env.example](./packages/contracts/.env.example): ```bash cd packages/contracts +cp .env.example .env +``` + +Edit `.env` and set your config: + +| Var | Description | Default values | +| ----------------- | ------------------------- | --------------------------------------------------------- | +| GOERLI_URL | Goerli provider URL | | +| GOERLI_LINEA_URL | Linea Goerli provider URL | | +| PRIVATE_KEY | Wallet private key | | +| ETHERSCAN_API_KEY | Etherscan API key | | +| L1_ENS_NAME | L1 ENS name | lineatest.eth | +| L2_ENS_NAME | L2 ENS name | julink.lineatest.eth | +| GATEWAY_URL | Primary gateway URL | https://www.ensgateway.amineharty.me/{sender}/{data}.json | + +For local/L2 mode, `GOERLI_URL` is not required. + +Compile smart contracts: + +```bash yarn hardhat compile ``` @@ -31,7 +51,7 @@ Deploy L2 contracts first: npx hardhat run --network goerliLinea scripts/deployL2.ts ``` -Get the resolver address, then deploy L1 contracts. +Get the `L2_RESOLVER_ADDRESS` resolver address, then deploy L1 contracts: ``` L2_RESOLVER_ADDRESS=$L2_RESOLVER_ADDRESS npx hardhat run --network localhost scripts/deployL1.ts @@ -39,13 +59,13 @@ L2_RESOLVER_ADDRESS=$L2_RESOLVER_ADDRESS npx hardhat run --network localhost scr ### Start Gateway server -Then start the gateway. +Once smart contracts are deployed, start the gateway: ```bash cd ../gateway yarn yarn build -yarn start --l2_resolver_address $L2_RESOLVER_ADDRESS --l1_provider_url http://127.0.0.1:8545/ --l1_chain_id 5 --l2_provider_url YOUR_GOERLI_L2_RPC_URL --l2_chain_id 59140 +yarn start --l2_resolver_address $L2_RESOLVER_ADDRESS --l1_provider_url http://127.0.0.1:8545/ --l2_provider_url $GOERLI_LINEA_URL --l2_chain_id 59140 ``` ### Run Client test script @@ -54,10 +74,10 @@ In a third terminal, run the demo app: ```bash cd packages/clients -yarn start -r $ENS_REGISTRY_ADDRESS test.test --l1_provider_url http://127.0.0.1:8545/ --chainId 5 --l2_provider_url YOUR_GOERLI_L2_RPC_URL +yarn start julink.lineatest.eth ``` -If sucessful, it should show the following output +If successful, it should show the following output: ```bash addr(bytes32) 0xF110a41f75edEb224227747b64Be7f6A7f140abc @@ -73,14 +93,14 @@ addr(bytes32) 0xF110a41f75edEb224227747b64Be7f6A7f140abc ## Deploy gateway -Create secret.yaml and update credentials +Create secret.yaml and update credentials: ``` cd gateway cp secret.yaml.org secret.yaml ``` -Deploy to app engine +Deploy to app engine: ``` gcloud app deploy goeril.app.yml diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index e5a58e01e..ea7ba5cdd 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -1,23 +1,20 @@ import { Command } from "commander"; import { ethers } from "ethers"; -import fetch from "cross-fetch"; +import { REGISTRY_ADDRESS } from "./constants"; const namehash = require("eth-ens-namehash"); -const StubAbi = require("../abi/LineaResolverStub.json").abi; const program = new Command(); -const { defaultAbiCoder, hexConcat } = require("ethers/lib/utils"); program - .requiredOption("-r --registry
", "ENS registry address") + .requiredOption( + "-r --registry
", + "ENS registry address", + REGISTRY_ADDRESS + ) .option( "-l1 --l1_provider_url ", "L1_PROVIDER_URL", "http://127.0.0.1:8545/" ) - .option( - "-l2 --l2_provider_url ", - "L2_PROVIDER_URL", - "http://127.0.0.1:8545/" - ) .option("-i --chainId ", "chainId", "31337") .option("-n --chainName ", "chainName", "unknown") .option("-d --debug", "debug", false) @@ -35,16 +32,12 @@ console.log("options", { chainName, debug, }); -let provider: ethers.providers.JsonRpcProvider; -if (chainId && chainName) { - provider = new ethers.providers.JsonRpcProvider(l1_provider_url, { - chainId, - name: chainName, - ensAddress, - }); -} else { - provider = new ethers.providers.JsonRpcProvider(options.l1_provider_url); -} + +const provider = new ethers.providers.JsonRpcProvider(l1_provider_url, { + chainId, + name: chainName, + ensAddress, +}); (async () => { const name = program.args[0]; diff --git a/packages/contracts/.env.example b/packages/contracts/.env.example index 1cfa6b6f6..fe52f20e4 100644 --- a/packages/contracts/.env.example +++ b/packages/contracts/.env.example @@ -1,4 +1,7 @@ GOERLI_URL= GOERLI_LINEA_URL= PRIVATE_KEY= -ETHERSCAN_API_KEY= \ No newline at end of file +ETHERSCAN_API_KEY= +L1_ENS_NAME=lineatest.eth +L2_ENS_NAME=julink.lineatest.eth +GATEWAY_URL=https://www.ensgateway.amineharty.me/{sender}/{data}.json diff --git a/packages/contracts/contracts/l1/LineaResolverStub.sol b/packages/contracts/contracts/l1/LineaResolverStub.sol index 14f2de0cb..083f09297 100644 --- a/packages/contracts/contracts/l1/LineaResolverStub.sol +++ b/packages/contracts/contracts/l1/LineaResolverStub.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; -pragma abicoder v2; import { Lib_OVMCodec } from "@eth-optimism/contracts/libraries/codec/Lib_OVMCodec.sol"; import { Lib_SecureMerkleTrie } from "@eth-optimism/contracts/libraries/trie/Lib_SecureMerkleTrie.sol"; import { Lib_RLPReader } from "@eth-optimism/contracts/libraries/rlp/Lib_RLPReader.sol"; import { Lib_BytesUtils } from "@eth-optimism/contracts/libraries/utils/Lib_BytesUtils.sol"; -import "hardhat/console.sol"; - struct L2StateProof { uint64 nodeIndex; bytes32 blockHash; @@ -76,7 +73,6 @@ contract LineaResolverStub is IExtendedResolver, SupportsInterface { bytes calldata name, bytes calldata data ) external view override returns (bytes memory) { - console.log("name %s", string(name)); bytes memory callData = abi.encodeWithSelector( IResolverService.resolve.selector, name, @@ -119,6 +115,11 @@ contract LineaResolverStub is IExtendedResolver, SupportsInterface { proof.storageTrieWitness ); + require( + keccak256(proof.result) == keccak256(abi.encode(value)), + "LineaResolverStub: value different from expected result" + ); + return proof.result; } diff --git a/packages/contracts/contracts/l1/utils/BytesUtils.sol b/packages/contracts/contracts/l1/utils/BytesUtils.sol deleted file mode 100644 index 392b1a261..000000000 --- a/packages/contracts/contracts/l1/utils/BytesUtils.sol +++ /dev/null @@ -1,61 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.9; - -library BytesUtils { - /* - * @dev Returns the keccak-256 hash of a byte range. - * @param self The byte string to hash. - * @param offset The position to start hashing at. - * @param len The number of bytes to hash. - * @return The hash of the byte range. - */ - function keccak( - bytes memory self, - uint256 offset, - uint256 len - ) internal pure returns (bytes32 ret) { - require(offset + len <= self.length); - assembly { - ret := keccak256(add(add(self, 32), offset), len) - } - } - - /** - * @dev Returns the ENS namehash of a DNS-encoded name. - * @param self The DNS-encoded name to hash. - * @param offset The offset at which to start hashing. - * @return The namehash of the name. - */ - function namehash( - bytes memory self, - uint256 offset - ) internal pure returns (bytes32) { - (bytes32 labelhash, uint256 newOffset) = readLabel(self, offset); - if (labelhash == bytes32(0)) { - require(offset == self.length - 1, "namehash: Junk at end of name"); - return bytes32(0); - } - return keccak256(abi.encodePacked(namehash(self, newOffset), labelhash)); - } - - /** - * @dev Returns the keccak-256 hash of a DNS-encoded label, and the offset to the start of the next label. - * @param self The byte string to read a label from. - * @param idx The index to read a label at. - * @return labelhash The hash of the label at the specified index, or 0 if it is the last label. - * @return newIdx The index of the start of the next label. - */ - function readLabel( - bytes memory self, - uint256 idx - ) internal pure returns (bytes32 labelhash, uint256 newIdx) { - require(idx < self.length, "readLabel: Index out of bounds"); - uint256 len = uint256(uint8(self[idx])); - if (len > 0) { - labelhash = keccak(self, idx + 1, len); - } else { - labelhash = bytes32(0); - } - newIdx = idx + len + 1; - } -} diff --git a/packages/contracts/contracts/l1/utils/NameEncoder.sol b/packages/contracts/contracts/l1/utils/NameEncoder.sol deleted file mode 100644 index 4d916e0f7..000000000 --- a/packages/contracts/contracts/l1/utils/NameEncoder.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.9; - -import { BytesUtils } from "./BytesUtils.sol"; - -library NameEncoder { - using BytesUtils for bytes; - - function dnsEncodeName( - string memory name - ) internal pure returns (bytes memory dnsName, bytes32 node) { - uint8 labelLength = 0; - bytes memory bytesName = bytes(name); - uint256 length = bytesName.length; - dnsName = new bytes(length + 2); - node = 0; - if (length == 0) { - dnsName[0] = 0; - return (dnsName, node); - } - - // use unchecked to save gas since we check for an underflow - // and we check for the length before the loop - unchecked { - for (uint256 i = length - 1; i >= 0; i--) { - if (bytesName[i] == ".") { - dnsName[i + 1] = bytes1(labelLength); - node = keccak256( - abi.encodePacked(node, bytesName.keccak(i + 1, labelLength)) - ); - labelLength = 0; - } else { - labelLength += 1; - dnsName[i + 1] = bytesName[i]; - } - if (i == 0) { - break; - } - } - } - - node = keccak256(abi.encodePacked(node, bytesName.keccak(0, labelLength))); - - dnsName[0] = bytes1(labelLength); - return (dnsName, node); - } -} diff --git a/packages/contracts/contracts/l2/LineaResolver.sol b/packages/contracts/contracts/l2/LineaResolver.sol index 569dba499..6230eff1f 100644 --- a/packages/contracts/contracts/l2/LineaResolver.sol +++ b/packages/contracts/contracts/l2/LineaResolver.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.9; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; contract LineaResolver is Ownable { - mapping(bytes32 => address) addresses; + mapping(bytes32 => address) addresses; - event AddrChanged(bytes32 indexed node, address a); + event AddrChanged(bytes32 indexed node, address a); - function setAddr(bytes32 node, address _addr) public onlyOwner { - addresses[node] = _addr; - emit AddrChanged(node, _addr); - } + function setAddr(bytes32 node, address _addr) public { + addresses[node] = _addr; + emit AddrChanged(node, _addr); + } - function addr(bytes32 node) public view returns (address) { - return addresses[node]; - } + function addr(bytes32 node) public view returns (address) { + return addresses[node]; + } } diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts index c2d78a0d9..bf5ab10af 100644 --- a/packages/contracts/hardhat.config.ts +++ b/packages/contracts/hardhat.config.ts @@ -3,6 +3,8 @@ import * as dotenv from "dotenv"; import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; +const hardhatPrivateKey = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + dotenv.config(); const config: HardhatUserConfig = { @@ -10,17 +12,14 @@ const config: HardhatUserConfig = { networks: { goerli: { url: process.env.GOERLI_URL, - accounts: - process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], + accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], }, goerliLinea: { url: process.env.GOERLI_LINEA_URL, - accounts: - process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], + accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], }, localhost: { - accounts: - process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], + accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY, hardhatPrivateKey] : [], }, }, etherscan: { diff --git a/packages/contracts/scripts/deployL1.ts b/packages/contracts/scripts/deployL1.ts index cf9ec1df5..ae9f26786 100644 --- a/packages/contracts/scripts/deployL1.ts +++ b/packages/contracts/scripts/deployL1.ts @@ -3,11 +3,18 @@ import { REGISTRY_ADDRESS, ROLLUP_ADDRESSES } from "./constants"; const ensRegistryAbi = require("../abi/ENSRegistry.json"); const namehash = require("eth-ens-namehash"); +const HARDHAT_NETWORK_CHAIN_ID = 31337; + let L2_RESOLVER_ADDRESS: string; async function main() { - const [owner] = await ethers.getSigners(); + const [owner, hardhatAccount] = await ethers.getSigners(); const chainId = await owner.getChainId(); + // If on localhost we can send ETH to the owner so that we make sure we have enough for deployment + if (chainId === HARDHAT_NETWORK_CHAIN_ID) { + await hardhatAccount.sendTransaction({ value: ethers.utils.parseEther("100"), to: owner.address }); + } + if (process.env.L2_RESOLVER_ADDRESS) { L2_RESOLVER_ADDRESS = process.env.L2_RESOLVER_ADDRESS; } else { @@ -23,12 +30,13 @@ async function main() { console.log(`LineaResolverStub deployed to ${lineaResolverStub.address}`); const registryAddr = REGISTRY_ADDRESS[network.name as keyof typeof REGISTRY_ADDRESS]; const registry = await new ethers.Contract(registryAddr, ensRegistryAbi, owner); - const node = namehash.hash("lineatest.eth"); - console.log("node", node); + const name = process.env.L1_ENS_NAME ? process.env.L1_ENS_NAME : "lineatest.eth"; + const node = namehash.hash(name); let tx = await registry.setResolver(node, lineaResolverStub.address); await tx.wait(); + console.log("L1 ENS name:", name, ", set to LineaResolverStub: ", lineaResolverStub.address); - if (chainId !== 31337) { + if (chainId !== HARDHAT_NETWORK_CHAIN_ID) { // Only verify on "live" blockchain setTimeout(async () => { await run("verify:verify", { diff --git a/packages/contracts/scripts/deployL2.ts b/packages/contracts/scripts/deployL2.ts index d1f461992..5bc4c8f2e 100644 --- a/packages/contracts/scripts/deployL2.ts +++ b/packages/contracts/scripts/deployL2.ts @@ -10,8 +10,9 @@ async function main() { const lineaResolver = await LineaResolver.deploy(); await lineaResolver.deployed(); - // Test with subdomain "julink.lineatest.eth" assuming we still control lineatest.eth on L1 - const node = namehash.hash("julink.lineatest.eth"); + // Test with subdomain with default "julink.lineatest.eth", assuming we still control lineatest.eth on L1 + const name = process.env.L2_ENS_NAME ? process.env.L2_ENS_NAME : "julink.lineatest.eth"; + const node = namehash.hash(name); const tx = await lineaResolver.setAddr(node, owner.address); await tx.wait(); console.log(`LineaResolver deployed to, L2_RESOLVER_ADDRESS: ${lineaResolver.address}`); diff --git a/packages/gateway/src/index.ts b/packages/gateway/src/index.ts index 36af3bc1a..f847979fa 100644 --- a/packages/gateway/src/index.ts +++ b/packages/gateway/src/index.ts @@ -25,8 +25,6 @@ program "L2_PROVIDER_URL", "http://127.0.0.1:8545/" ) - .option("-l1c --l1_chain_id ", "L1_CHAIN_ID", "31337") - .option("-l2c --l2_chain_id ", "L2_CHAIN_ID", "31337") .option( "-ru --rollup_address ", "ROLLUP_ADDRESS", @@ -42,8 +40,6 @@ const { l2_provider_url, rollup_address, l2_resolver_address, - l1_chain_id, - l2_chain_id, debug, } = options; if (l2_resolver_address === undefined) { @@ -75,8 +71,6 @@ server.add(IResolverAbi, [ l1_provider_url, l2_provider_url, l2_resolver_address, - l1_chain_id, - l2_chain_id, }); const blockNumber = (await l2provider.getBlock("latest")).number; console.log(2, { blockNumber, addrSlot }); @@ -149,7 +143,7 @@ server.add(IResolverAbi, [ stateRoot, storageTrieWitness: storageProof, node, - result: result.toString(), + result, }; console.log(7, { finalProof }); return [finalProof]; @@ -174,9 +168,10 @@ function decodeDnsName(dnsname: Buffer) { async function getResult( name: string, data: string -): Promise<{ result: BytesLike; validUntil: number }> { +): Promise<{ result: BytesLike }> { // Parse the data nested inside the second argument to `resolve` const { signature, args } = Resolver.parseTransaction({ data }); + console.log("signature", signature); if (ethers.utils.nameprep(name) !== name) { throw new Error("Name must be normalised"); @@ -196,6 +191,5 @@ async function getResult( return { result: Resolver.encodeFunctionResult(signature, [result]), - validUntil: Math.floor(Date.now() / 1000), }; }