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),
};
}