diff --git a/contracts/README.md b/contracts/README.md index 18b5d10cb..ebd019c8e 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -20,7 +20,7 @@ Refresh the list of deployed contracts by running `./scripts/generateDeployments - [PNK](https://goerli-rollup-explorer.arbitrum.io/token/0x4DEeeFD054434bf6721eF39Aa18EfB3fd0D12610/token-transfers) - [DisputeKitClassic](https://goerli-rollup-explorer.arbitrum.io/address/0x8F1a2B8F9b04320375856580Fc6B1669Cb12a9EE) - [DisputeResolver](https://goerli-rollup-explorer.arbitrum.io/address/0x04Fb43F2Ce076867b5ba38750Ecb2cc6BDe78D61) -- [FastBridgeSenderToEthereum](https://goerli-rollup-explorer.arbitrum.io/address/0xcFc0b84419583ff7b32fD5139B789cE858517d4C) +- [FastBridgeSender](https://goerli-rollup-explorer.arbitrum.io/address/0xcFc0b84419583ff7b32fD5139B789cE858517d4C) - [HomeGatewayToEthereum](https://goerli-rollup-explorer.arbitrum.io/address/0xc7e3BF90299f6BD9FA7c3703837A9CAbB5743636) - [IncrementalNG](https://goerli-rollup-explorer.arbitrum.io/address/0x99c1f883f0f5de1737099F1BCB268d1f8D450f8b) - [KlerosCore](https://goerli-rollup-explorer.arbitrum.io/address/0x87142b7E9C7D026776499120D902AF8896C07894) @@ -38,7 +38,7 @@ Refresh the list of deployed contracts by running `./scripts/generateDeployments - [PNK](https://testnet.arbiscan.io/token/0x364530164a2338cdba211f72c1438eb811b5c639) - [DisputeKitClassic](https://testnet.arbiscan.io/address/0xA2c538AA05BBCc44c213441f6f3777223D2BF9e5) - [DisputeResolver](https://testnet.arbiscan.io/address/0x67e8191F61466c57A17542A52F9f39f336A242fD) -- [FastBridgeSenderToEthereum](https://testnet.arbiscan.io/address/0xf8A4a85e7153374A1b9BDA763a84252eC286843b) +- [FastBridgeSender](https://testnet.arbiscan.io/address/0xf8A4a85e7153374A1b9BDA763a84252eC286843b) - [HomeGatewayToEthereum](https://testnet.arbiscan.io/address/0x4e894c2B60214beC53B60D09F39544518296C07B) - [IncrementalNG](https://testnet.arbiscan.io/address/0x078dAd05373d19d7fd6829735b765F12242a4300) - [KlerosCore](https://testnet.arbiscan.io/address/0x815d709EFCF5E69e2e9E2F8d3815d762496a2f0F) diff --git a/contracts/deploy/02-home-chain.ts b/contracts/deploy/02-home-chain.ts index e5795579e..93bc9410d 100644 --- a/contracts/deploy/02-home-chain.ts +++ b/contracts/deploy/02-home-chain.ts @@ -21,7 +21,7 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const fastBridgeReceiver = await deployments.get("FastBridgeReceiverOnEthereum"); const arbSysMock = await deploy("ArbSysMock", { from: deployer, log: true }); - const fastBridgeSender = await deploy("FastBridgeSenderToEthereumMock", { + const fastBridgeSender = await deploy("FastBridgeSenderMock", { from: deployer, contract: "FastBridgeSenderMock", args: [epochPeriod, fastBridgeReceiver.address, arbSysMock.address], @@ -63,7 +63,7 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const liveDeployer = async () => { const fastBridgeReceiver = await hre.companionNetworks.foreign.deployments.get("FastBridgeReceiverOnEthereum"); - const fastBridgeSender = await deploy("FastBridgeSenderToEthereum", { + const fastBridgeSender = await deploy("FastBridgeSender", { from: deployer, contract: "FastBridgeSender", args: [epochPeriod, fastBridgeReceiver.address], diff --git a/contracts/deployments/arbitrumGoerli/FastBridgeSenderToEthereum.json b/contracts/deployments/arbitrumGoerli/FastBridgeSender.json similarity index 100% rename from contracts/deployments/arbitrumGoerli/FastBridgeSenderToEthereum.json rename to contracts/deployments/arbitrumGoerli/FastBridgeSender.json diff --git a/contracts/deployments/arbitrumRinkeby/FastBridgeSenderToEthereum.json b/contracts/deployments/arbitrumRinkeby/FastBridgeSender.json similarity index 100% rename from contracts/deployments/arbitrumRinkeby/FastBridgeSenderToEthereum.json rename to contracts/deployments/arbitrumRinkeby/FastBridgeSender.json diff --git a/contracts/test/integration/index.ts b/contracts/test/integration/index.ts index c02394e13..e2303788b 100644 --- a/contracts/test/integration/index.ts +++ b/contracts/test/integration/index.ts @@ -8,7 +8,7 @@ import { FastBridgeReceiverOnEthereum, ForeignGatewayOnEthereum, ArbitrableExample, - FastBridgeSenderToEthereumMock, + FastBridgeSenderMock, HomeGatewayToEthereum, DisputeKitClassic, InboxMock, @@ -62,7 +62,7 @@ describe("Integration tests", async () => { fastBridgeReceiver = (await ethers.getContract("FastBridgeReceiverOnEthereum")) as FastBridgeReceiverOnEthereum; foreignGateway = (await ethers.getContract("ForeignGatewayOnEthereum")) as ForeignGatewayOnEthereum; arbitrable = (await ethers.getContract("ArbitrableExample")) as ArbitrableExample; - fastBridgeSender = (await ethers.getContract("FastBridgeSenderToEthereumMock")) as FastBridgeSenderToEthereumMock; + fastBridgeSender = (await ethers.getContract("FastBridgeSenderMock")) as FastBridgeSenderMock; homeGateway = (await ethers.getContract("HomeGatewayToEthereum")) as HomeGatewayToEthereum; inbox = (await ethers.getContract("InboxMock")) as InboxMock; }); diff --git a/package.json b/package.json index 70734b25a..40a49620c 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "evidence-display", "klerosjs", "subgraph", + "subgraph-fastbridge", "web" ], "packageManager": "yarn@3.1.1", diff --git a/subgraph-fastbridge/.eslintrc.json b/subgraph-fastbridge/.eslintrc.json new file mode 100644 index 000000000..8256ebd88 --- /dev/null +++ b/subgraph-fastbridge/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": [ + "plugin:@typescript-eslint/recommended", + "prettier", + "prettier/@typescript-eslint" + ] +} diff --git a/subgraph-fastbridge/.prettierrc b/subgraph-fastbridge/.prettierrc new file mode 100644 index 000000000..9d4892be8 --- /dev/null +++ b/subgraph-fastbridge/.prettierrc @@ -0,0 +1,3 @@ +{ + "printWidth": 80 +} diff --git a/subgraph-fastbridge/README.md b/subgraph-fastbridge/README.md new file mode 100644 index 000000000..a14a5257d --- /dev/null +++ b/subgraph-fastbridge/README.md @@ -0,0 +1,108 @@ +# @kleros/kleros-v2-subgraph-fastbridge + +## Deployments + +- [kleros/fastbridge-arbitrum-rinkeby](https://thegraph.com/hosted-service/subgraph/kleros/fastbridge-arbitrum-rinkeby) + +## Build + +```bash +$ yarn + +$ yarn codegen + +$ yarn build +``` + +## Deployment to The Graph (hosted service) + +### Authentication + +Get an API key from the thegraph.com, then authenticate. + +```bash +$ yarn run graph auth --product hosted-service +``` + +### Deployment + +```bash +yarn deploy +``` + +## Deployment to a local Graph node + +_Credits to the [scaffold-eth service package](https://github.com/scaffold-eth/scaffold-eth/tree/b03d07f15882db626300ffa04f222736b2a22f81/packages/services/graph-node)_ + +Preconfigured Docker image for running a Graph Node. + +## Usage + +**Prerequisite**: docker and docker-compose. + +```bash +$ docker-compose up -d +Starting graph-node_postgres_1 ... done +Starting graph-node_ipfs_1 ... done +Starting graph-node_graph-node_1 ... done + +$ docker-compose logs -f +... +``` + +This will start docker containers for the following services: + +- IPFS, +- Postgres +- **Graph Node** connecting to the Arbitrum Rinkeby official RPC + +This also creates persistent data directories for IPFS and Postgres in `./data/ipfs` and `./data/postgres`. + +Once this is up and running, you can use [`graph-cli`](https://github.com/graphprotocol/graph-cli) to create and deploy your subgraph to the running Graph Node. + +```bash +# First time only. +$ yarn create-local +Created subgraph: fastbridge + +$ yarn deploy-local --version-label v0.0.1 +✔ Apply migrations +✔ Load subgraph from subgraph.yaml + Compile data source: FastBridgeSender => build/FastBridgeSender/FastBridgeSender.wasm +✔ Compile subgraph + Copy schema file build/schema.graphql + Write subgraph file build/FastBridgeSender/abis/FastBridgeSender.json + Write subgraph manifest build/subgraph.yaml +✔ Write compiled subgraph to build/ + Add file to IPFS build/schema.graphql + .. Qmb3Uahj4qKh5u3V4KuraXJqsrUVwPtvva1KQQSb5tLov9 + Add file to IPFS build/FastBridgeSender/abis/FastBridgeSender.json + .. QmQas2SuTQH6zybTVMGBym76kyBoTp7MwkogcmtHAeMoRj + Add file to IPFS build/FastBridgeSender/FastBridgeSender.wasm + .. QmSLfCYp19WW5JEiaKdmcGFgBCJ5DA723dEkM6QvXE2eCa +✔ Upload subgraph to IPFS + +Build completed: QmWjRVXec6auQdnpvYJ7F8vW7PzkJHYJhFtorWZAhtP9A3 + +Deployed to http://localhost:8000/subgraphs/name/fastbridge/graphql + +Subgraph endpoints: +Queries (HTTP): http://localhost:8000/subgraphs/name/fastbridge +``` + +## Access + +### Graph Node + +- GraphiQL: `http://localhost:8000/` +- HTTP: `http://localhost:8000/subgraphs/name/` +- WebSockets: `ws://localhost:8001/subgraphs/name/` +- Admin: `http://localhost:8020/` + +### IPFS + +- `127.0.0.1:5001` or `/ip4/127.0.0.1/tcp/5001` + +### Postgres + +- `postgresql://graph-node:let-me-in@localhost:5432/graph-node` diff --git a/subgraph-fastbridge/abis/FastBridgeSender.json b/subgraph-fastbridge/abis/FastBridgeSender.json new file mode 100644 index 000000000..4fed10ef1 --- /dev/null +++ b/subgraph-fastbridge/abis/FastBridgeSender.json @@ -0,0 +1,231 @@ +{ + "address": "0xf8A4a85e7153374A1b9BDA763a84252eC286843b", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epochPeriod", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_safeBridgeReceiver", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "batchID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "batchSize", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "batchMerkleRoot", + "type": "bytes32" + } + ], + "name": "BatchOutgoing", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "fastMessage", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "fastMessageHash", + "type": "bytes32" + } + ], + "name": "MessageReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "canonicalBridgeMessageID", + "type": "bytes32" + } + ], + "name": "SentSafe", + "type": "event" + }, + { + "inputs": [], + "name": "ARB_SYS", + "outputs": [ + { + "internalType": "contract IArbSys", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "batch", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "batchSize", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentBatchID", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "fastOutbox", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "safeBridgeReceiver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sendBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_receiver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_calldata", + "type": "bytes" + } + ], + "name": "sendFast", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + } + ], + "name": "sendSafeFallback", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } + ] +} diff --git a/subgraph-fastbridge/abis/FastBridgeSenderToEthereum.json b/subgraph-fastbridge/abis/FastBridgeSenderToEthereum.json new file mode 100644 index 000000000..4fed10ef1 --- /dev/null +++ b/subgraph-fastbridge/abis/FastBridgeSenderToEthereum.json @@ -0,0 +1,231 @@ +{ + "address": "0xf8A4a85e7153374A1b9BDA763a84252eC286843b", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epochPeriod", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_safeBridgeReceiver", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "batchID", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "batchSize", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "batchMerkleRoot", + "type": "bytes32" + } + ], + "name": "BatchOutgoing", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "fastMessage", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "fastMessageHash", + "type": "bytes32" + } + ], + "name": "MessageReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "canonicalBridgeMessageID", + "type": "bytes32" + } + ], + "name": "SentSafe", + "type": "event" + }, + { + "inputs": [], + "name": "ARB_SYS", + "outputs": [ + { + "internalType": "contract IArbSys", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "batch", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "batchSize", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "currentBatchID", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "fastOutbox", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "safeBridgeReceiver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sendBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_receiver", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_calldata", + "type": "bytes" + } + ], + "name": "sendFast", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + } + ], + "name": "sendSafeFallback", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } + ] +} diff --git a/subgraph-fastbridge/docker-compose.yml b/subgraph-fastbridge/docker-compose.yml new file mode 100644 index 000000000..4b269237d --- /dev/null +++ b/subgraph-fastbridge/docker-compose.yml @@ -0,0 +1,40 @@ +version: "3" +services: + graph-node: + image: graphprotocol/graph-node:latest + ports: + - "8000:8000" + - "8001:8001" + - "8020:8020" + - "8030:8030" + - "8040:8040" + depends_on: + - ipfs + - postgres + environment: + postgres_host: postgres + postgres_user: graph-node + postgres_pass: let-me-in + postgres_db: graph-node + ipfs: "ipfs:5001" + ethereum: "arbitrum-rinkeby:https://rinkeby.arbitrum.io/rpc" + GRAPH_LOG: debug + extra_hosts: + - "host.docker.internal:host-gateway" + ipfs: + image: ipfs/go-ipfs:v0.10.0 + ports: + - "5001:5001" + volumes: + - ./data/ipfs:/data/ipfs + postgres: + image: postgres + ports: + - "5432:5432" + command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"] + environment: + POSTGRES_USER: graph-node + POSTGRES_PASSWORD: let-me-in + POSTGRES_DB: graph-node + volumes: + - ./data/postgres:/var/lib/postgresql/data diff --git a/subgraph-fastbridge/package.json b/subgraph-fastbridge/package.json new file mode 100644 index 000000000..8c76fa70b --- /dev/null +++ b/subgraph-fastbridge/package.json @@ -0,0 +1,19 @@ +{ + "name": "@kleros/kleros-v2-subgraph-fastbridge", + "license": "MIT", + "scripts": { + "codegen": "graph codegen", + "build": "graph build", + "deploy": "graph deploy --node https://api.thegraph.com/deploy/ kleros/fastbridge-arbitrum-rinkeby", + "create-local": "graph create --node http://localhost:8020/ kleros/fastbridge-arbitrum-rinkeby", + "remove-local": "graph remove --node http://localhost:8020/ kleros/fastbridge-arbitrum-rinkeby", + "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 kleros/fastbridge-arbitrum-rinkeby" + }, + "dependencies": { + "@graphprotocol/graph-cli": "^0.33.0", + "@graphprotocol/graph-ts": "^0.27.0" + }, + "volta": { + "node": "16.17.0" + } +} diff --git a/subgraph-fastbridge/schema.graphql b/subgraph-fastbridge/schema.graphql new file mode 100644 index 000000000..5957a4d09 --- /dev/null +++ b/subgraph-fastbridge/schema.graphql @@ -0,0 +1,36 @@ +type FastMessage @entity(immutable: true) { + id: ID! + batchID: BigInt! + nonce: BigInt! + message: Bytes! + receiver: Receiver! + sender: Sender! + hash: Bytes! +} + +type Proof @entity(immutable: true) { + id: ID! + batchID: BigInt! + nonce: BigInt! + data: Bytes! + fastMessage: FastMessage! +} + +type Receiver @entity(immutable: true) { + id: ID! + fastMessages: [FastMessage!]! @derivedFrom(field: "receiver") +} + +type Sender @entity(immutable: true) { + id: ID! + fastMessages: [FastMessage!]! @derivedFrom(field: "sender") +} + +type Batch @entity { + id: ID! + epochFinal: BigInt! + batchSize: BigInt! + merkleRoot: Bytes + sentSafe: Boolean + canonicalBridgeMessageID: Bytes +} \ No newline at end of file diff --git a/subgraph-fastbridge/scripts/updateAbis.sh b/subgraph-fastbridge/scripts/updateAbis.sh new file mode 100755 index 000000000..292a23064 --- /dev/null +++ b/subgraph-fastbridge/scripts/updateAbis.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +function update() #file #dataSourceIndex +{ + local f="$1" + local dataSourceIndex="$2" + cat $f | jq '. | {address: .address, abi: .abi}' > abis/$(basename $f) + + address=$(cat abis/$(basename $f) | jq '.address') + yq -i ".dataSources[$dataSourceIndex].source.address=$address" $SCRIPT_DIR/../subgraph.yaml + + blockNumber="$(cat $f | jq '.receipt.blockNumber')" + yq -i ".dataSources[$dataSourceIndex].source.startBlock=$blockNumber" $SCRIPT_DIR/../subgraph.yaml +} + +update "$SCRIPT_DIR/../../contracts/deployments/arbitrumRinkeby/FastBridgeSender.json" 0 diff --git a/subgraph-fastbridge/src/FastBridgeSender.ts b/subgraph-fastbridge/src/FastBridgeSender.ts new file mode 100644 index 000000000..284fce806 --- /dev/null +++ b/subgraph-fastbridge/src/FastBridgeSender.ts @@ -0,0 +1,156 @@ +import { Bytes, ByteArray, BigInt, crypto } from "@graphprotocol/graph-ts"; + +import { + FastBridgeSender, + MessageReceived, + BatchOutgoing, + SentSafe, +} from "../generated/FastBridgeSender/FastBridgeSender"; +import { + FastMessage, + Sender, + Batch, + Proof, + Receiver, +} from "../generated/schema"; + +export function handleSentSafe(event: SentSafe): void { + const batch = Batch.load(event.params.epoch.toString()); + if (batch) { + batch.sentSafe = true; + batch.canonicalBridgeMessageID = event.params.canonicalBridgeMessageID; + batch.save(); + } +} + +export function handleMessageReceived(event: MessageReceived): void { + const contract = FastBridgeSender.bind(event.address); + + const batchID = contract.currentBatchID(); + const leafHash = event.params.fastMessageHash; + const message = event.params.fastMessage; + const nonce = BigInt.fromByteArray(firstSlotReverse(message)); + + const fastMessage = new FastMessage( + batchID.toString() + "," + nonce.toString() + ); + + fastMessage.message = message; + fastMessage.hash = leafHash; + fastMessage.batchID = batchID; + fastMessage.nonce = nonce; + + const receiverAddress = getAddress(message, 44).toHexString(); + const senderAddress = getAddress(message, 144).toHexString(); + + const receiver = new Receiver(receiverAddress); + receiver.save(); + + const sender = new Sender(senderAddress); + sender.save(); + + fastMessage.receiver = receiverAddress; + fastMessage.sender = senderAddress; + fastMessage.save(); +} + +export function handleBatchOutgoing(event: BatchOutgoing): void { + const layers: ByteArray[][] = []; + const layerZero: ByteArray[] = []; + + const epochInitial = event.params.batchID; + const epochFinal = event.params.epoch; + const batchSize = event.params.batchSize; + const currentBatchID = event.params.batchID; + + const batch = new Batch(currentBatchID.toString()); + + batch.epochFinal = epochFinal; + batch.batchSize = batchSize; + + let count = 0; + const _epochFinal = epochFinal.toU32(); + + for (let i = epochInitial.toU32(); i <= _epochFinal; i++) { + let fastMessage = FastMessage.load(i.toString() + "," + count.toString()); + while (fastMessage != null) { + layerZero.push(fastMessage.hash); + count++; + fastMessage = FastMessage.load(i.toString() + "," + count.toString()); + } + } + layers.push(layerZero); + // Get next layer until we reach the root + while (layers[layers.length - 1].length > 1) { + layers.push(getNextLayer(layers[layers.length - 1])); + } + + // PROOF + for (let idx = 0; idx < layerZero.length; idx++) { + const proof: ByteArray[] = []; + const _proof = new Proof(currentBatchID.toString() + "," + idx.toString()); + let _idx = idx; + for (let i = 0; i < layers.length; i++) { + const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; + if (pairIdx < layers[i].length) { + proof.push(layers[i][pairIdx]); + } + _idx /= 2; + } + _proof.data = Bytes.fromByteArray(Flatten(proof)); + _proof.fastMessage = currentBatchID.toString() + "," + idx.toString(); + _proof.batchID = currentBatchID; + _proof.nonce = BigInt.fromU32(idx); + _proof.save(); + } + batch.merkleRoot = event.params.batchMerkleRoot; + batch.sentSafe = false; + batch.save(); +} + +function Flatten(a: ByteArray[]): ByteArray { + const out = new ByteArray(32 * a.length); + for (let i = 0; i < a.length; i++) out.set(a[i], i * 32); + return out; +} + +function getNextLayer(elements: ByteArray[]): ByteArray[] { + return elements.reduce((layer, el, idx, arr) => { + if (idx % 2 === 0) { + // Hash the current element with its pair element + if (idx === arr.length - 1) { + layer.push(el); + } else { + layer.push(crypto.keccak256(concatAndSortByteArrays(el, arr[idx + 1]))); + } + } + + return layer; + }, [] as ByteArray[]); +} + +function firstSlotReverse(a: ByteArray): ByteArray { + const out = new ByteArray(32); + for (let i = 0; i < 32; i++) out[i] = a[31 - i]; + return out; +} + +function getAddress(input: ByteArray, offset: i32): ByteArray { + const out = new ByteArray(20); + for (let i = 0; i < 20; i++) out[i] = input[i + offset]; + return out; +} + +function concatAndSortByteArrays(a: ByteArray, b: ByteArray): ByteArray { + let out: ByteArray; + for (let i = 0; i < 32; i++) { + if (a[i] > b[i]) { + out = b.concat(a); + return out; + } else if (b[i] < a[i]) { + out = a.concat(b); + return out; + } + } + return a; +} diff --git a/subgraph-fastbridge/subgraph.yaml b/subgraph-fastbridge/subgraph.yaml new file mode 100644 index 000000000..8b24a0d3c --- /dev/null +++ b/subgraph-fastbridge/subgraph.yaml @@ -0,0 +1,34 @@ +specVersion: 0.0.4 +description: Fast Bridge Sender on Arbitrum Rinkeby. +repository: https://github.com/kleros/kleros-v2/tree/master/subgraph-fastbridge +schema: + file: ./schema.graphql +dataSources: + - kind: ethereum + name: FastBridgeSender + network: arbitrum-rinkeby + source: + address: "0xf8A4a85e7153374A1b9BDA763a84252eC286843b" + abi: FastBridgeSender + startBlock: 14453120 + mapping: + kind: ethereum/events + apiVersion: 0.0.6 + language: wasm/assemblyscript + entities: + - FastMessage + - Proof + - Sender + - Receiver + - Batch + abis: + - name: FastBridgeSender + file: ./abis/FastBridgeSender.json + eventHandlers: + - event: SentSafe(indexed uint256,bytes32) + handler: handleSentSafe + - event: MessageReceived(bytes,bytes32) + handler: handleMessageReceived + - event: BatchOutgoing(indexed uint256,uint256,uint256,bytes32) + handler: handleBatchOutgoing + file: ./src/FastBridgeSender.ts diff --git a/subgraph-fastbridge/tsconfig.json b/subgraph-fastbridge/tsconfig.json new file mode 100644 index 000000000..5c5d17c92 --- /dev/null +++ b/subgraph-fastbridge/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json", + "include": ["src"] +} diff --git a/yarn.lock b/yarn.lock index a0a194809..07b4dd297 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2429,6 +2429,15 @@ __metadata: languageName: unknown linkType: soft +"@kleros/kleros-v2-subgraph-fastbridge@workspace:subgraph-fastbridge": + version: 0.0.0-use.local + resolution: "@kleros/kleros-v2-subgraph-fastbridge@workspace:subgraph-fastbridge" + dependencies: + "@graphprotocol/graph-cli": ^0.33.0 + "@graphprotocol/graph-ts": ^0.27.0 + languageName: unknown + linkType: soft + "@kleros/kleros-v2-subgraph@workspace:subgraph": version: 0.0.0-use.local resolution: "@kleros/kleros-v2-subgraph@workspace:subgraph"