Skip to content

Commit

Permalink
feat(bridge-withdrawer): PoC astria-bridge-withdrawer implementation (#…
Browse files Browse the repository at this point in the history
…984)

## Summary
implement `ethereum` and `submitter` modules for
astria-bridge-withdrawer.

## Background
required to implement rollup bridge withdrawals.

## Changes

ethereum:
- implement `AstriaWithdrawer.sol` contract, put inside `ethereum/` dir
as a foundry project
- implement `ethereum` module which contains a `Watcher` that watches
the contract for `Withdrawal` events
- implement event to action conversion
- the events are sent to a `Batcher` which batches all events by block
number and sends a batch of actions to a sequencer handler

sequencer: 
- submit txs to sequencer when a batch of actions is received

## Testing
unit tests

manual testing:
1. run sequencer+cometbft as normal (`just run` + `just run-cometbft` in
astria-sequencer)
2. run anvil as normal (`anvil` in a terminal)
3.  init the bridge account: 
```sh
export SEQUENCER_PRIVATE_KEY=2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90
./target/debug/astria-cli sequencer init-bridge-account --sequencer-url=http://localhost:26657 --rollup-name=astria --sequencer.chain-id=astria
```
4. `just copy-env` in `astria-bridge-withdrawer`, put the private key
from above into a file, update .env to use that
5. go to `astria-bridge-withdrawer/ethereum`, `cp local.env.example
.env`
6. deploy the withdrawer contract:
```sh
source .env
forge create --rpc-url $RPC_URL --private-key $PRIVATE_KEY src/AstriaWithdrawer.sol:AstriaWithdrawer
```
7. run this twice, makes 2 withdrawals (forces anvil to make 2 blocks)
note: the withdrawer will only submit the withdraw for the first tx, as
it's waiting for the next block before submitting the second and anvil
doesn't auto-build blocks by default
```sh
forge script script/AstriaWithdrawer.s.sol:AstriaWithdrawerScript \
   --rpc-url $RPC_URL --broadcast --sig "withdrawToSequencer()" -vvvv
```
8. in `astria-bridge-withdrawer`, update
`ASTRIA_BRIDGE_WITHDRAWER_ETHEREUM_CONTRACT_ADDRESS` to be the contract
address deployed in step 6.
9. `just run` and you should see a withdrawal being submitted:
```sh
2024-05-24T19:36:22.952210Z DEBUG astria_bridge_withdrawer::withdrawer::submitter: fetched latest nonce nonce=1
2024-05-24T19:36:23.003380Z DEBUG astria_bridge_withdrawer::withdrawer::submitter: signed transaction tx_hash=faad567d4f71ba20bdd9a1f679c4c3875c4b172c96d236155d52eb7ca02cd389
2024-05-24T19:36:23.003742Z DEBUG submit_tx: astria_bridge_withdrawer::withdrawer::submitter: submitting signed transaction to sequencer nonce=1 transaction.hash=faad567d4f71ba20bdd9a1f679c4c3875c4b172c96d236155d52eb7ca02cd389
2024-05-24T19:36:24.049943Z  INFO astria_bridge_withdrawer::withdrawer::submitter: withdraw batch successfully executed. sequencer.block=881 sequencer.tx_hash=FAAD567D4F71BA20BDD9A1F679C4C3875C4B172C96D236155D52EB7CA02CD389 rollup.height=2
```

## Related Issues

 #913

closes #1109

---------

Co-authored-by: elizabeth <[email protected]>
Co-authored-by: noot <[email protected]>
  • Loading branch information
3 people authored Jun 1, 2024
1 parent d54b5bf commit afe4901
Show file tree
Hide file tree
Showing 37 changed files with 2,450 additions and 16 deletions.
32 changes: 31 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,37 @@ jobs:
- name: Run tests
timeout-minutes: 20
run: |
cargo nextest run --archive-file=archive.tar.zst -- --include-ignored
cargo nextest run --archive-file=archive.tar.zst
rust-ethereum:
runs-on: buildjet-8vcpu-ubuntu-2204
needs: run_checker
if: needs.run_checker.outputs.run_tests == 'true'
steps:
- uses: actions/checkout@v4
- uses: dtolnay/[email protected]
- uses: Swatinem/[email protected]
with:
cache-provider: "buildjet"
- name: Install nextest
uses: taiki-e/install-action@nextest
- uses: arduino/setup-protoc@v3
with:
version: "24.4"
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Install python and solc
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Run tests
timeout-minutes: 20
run: |
pip install solc-select
solc-select install 0.8.21
solc-select use 0.8.21
cargo nextest run --package astria-bridge-withdrawer -- --include-ignored
doctest:
runs-on: buildjet-8vcpu-ubuntu-2204
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "crates/astria-bridge-withdrawer/ethereum/lib/forge-std"]
path = crates/astria-bridge-withdrawer/ethereum/lib/forge-std
url = https://github.com/foundry-rs/forge-std
33 changes: 33 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
exclude = ["tools/protobuf-compiler"]

members = [
"crates/astria-bridge-withdrawer",
"crates/astria-build-info",
"crates/astria-cli",
"crates/astria-composer",
Expand All @@ -24,6 +25,7 @@ members = [
# Specify default members so that cargo invocations in github actions will
# not act on lints
default-members = [
"crates/astria-bridge-withdrawer",
"crates/astria-build-info",
"crates/astria-cli",
"crates/astria-composer",
Expand Down Expand Up @@ -67,6 +69,7 @@ itertools = "0.12.1"
itoa = "1.0.10"
jsonrpsee = { version = "0.20" }
once_cell = "1.17.1"
pin-project-lite = "0.2.13"
sha2 = "0.10"
serde = "1"
serde_json = "1"
Expand Down
55 changes: 55 additions & 0 deletions crates/astria-bridge-withdrawer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[package]
name = "astria-bridge-withdrawer"
version = "0.1.0"
edition = "2021"
rust-version = "1.73"
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/astriaorg/astria"
homepage = "https://astria.org"

[[bin]]
name = "astria-bridge-withdrawer"

[dependencies]
http = "0.2.9"

axum = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
ethers = { workspace = true, features = ["ethers-solc", "ws"] }
hyper = { workspace = true }
humantime = { workspace = true }
ibc-types = { workspace = true }
metrics = { workspace = true }
pin-project-lite = { workspace = true }
prost = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
sha2 = { workspace = true }
tendermint = { workspace = true }
tracing = { workspace = true }
tryhard = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] }
tokio-util = { workspace = true }

astria-build-info = { path = "../astria-build-info", features = ["runtime"] }
astria-core = { path = "../astria-core", features = ["serde", "server"] }
astria-eyre = { path = "../astria-eyre" }
config = { package = "astria-config", path = "../astria-config" }
sequencer-client = { package = "astria-sequencer-client", path = "../astria-sequencer-client", features = [
"http",
] }
telemetry = { package = "astria-telemetry", path = "../astria-telemetry", features = [
"display",
] }

[dev-dependencies]
astria-core = { path = "../astria-core", features = ["server", "test-utils"] }
astria-grpc-mock = { path = "../astria-grpc-mock" }
config = { package = "astria-config", path = "../astria-config", features = [
"tests",
] }

[build-dependencies]
astria-build-info = { path = "../astria-build-info", features = ["build"] }
4 changes: 4 additions & 0 deletions crates/astria-bridge-withdrawer/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
astria_build_info::emit("bridge-withdrawer-v")?;
Ok(())
}
14 changes: 14 additions & 0 deletions crates/astria-bridge-withdrawer/ethereum/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
38 changes: 38 additions & 0 deletions crates/astria-bridge-withdrawer/ethereum/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# astria-bridge-withdrawer

Forge project for the bridge withdrawer contract.

Requirements:

- foundry

Build:

```sh
forge build
```

Copy the example .env: `cp local.example.env .env`

Put your private key in `.env` and `source .env`.

Deploy `AstriaWithdrawer.sol`:

```sh
forge script script/AstriaWithdrawer.s.sol:AstriaWithdrawerScript \
--rpc-url $RPC_URL --broadcast --sig "deploy()" -vvvv
```

Call `withdrawToSequencer` in `AstriaWithdrawer.sol`:

```sh
forge script script/AstriaWithdrawer.s.sol:AstriaWithdrawerScript \
--rpc-url $RPC_URL --broadcast --sig "withdrawToSequencer()" -vvvv
```

Call `withdrawToOriginChain` in `AstriaWithdrawer.sol`:

```sh
forge script script/AstriaWithdrawer.s.sol:AstriaWithdrawerScript \
--rpc-url $RPC_URL --broadcast --sig "withdrawToOriginChain()" -vvvv
```
6 changes: 6 additions & 0 deletions crates/astria-bridge-withdrawer/ethereum/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions crates/astria-bridge-withdrawer/ethereum/lib/forge-std
Submodule forge-std added at 978ac6
20 changes: 20 additions & 0 deletions crates/astria-bridge-withdrawer/ethereum/local.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# private key to submit txs with
PRIVATE_KEY=0x

# default local rpc url
RPC_URL="http://localhost:8545"

# divide withdrawn values by 10^ASSET_WITHDRAWAL_DECIMALS
ASSET_WITHDRAWAL_DECIMALS=12

# contract address
ASTRIA_WITHDRAWER=0x5FbDB2315678afecb367f032d93F642f64180aa3

# destination chain address to withdraw to on the sequencer
SEQUENCER_DESTINATION_CHAIN_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

# destination chain address to withdraw to on the origin chain
ORIGIN_DESTINATION_CHAIN_ADDRESS="astring"

# amount to withdraw
AMOUNT=1000000000

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {AstriaWithdrawer} from "../src/AstriaWithdrawer.sol";

contract AstriaWithdrawerScript is Script {
function setUp() public {}

function deploy() public {
uint32 assetWithdrawalDecimals = uint32(vm.envUint("ASSET_WITHDRAWAL_DECIMALS"));
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
AstriaWithdrawer astriaWithdrawer = new AstriaWithdrawer(assetWithdrawalDecimals);
console.logAddress(address(astriaWithdrawer));
vm.stopBroadcast();
}

function withdrawToSequencer() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

address contractAddress = vm.envAddress("ASTRIA_WITHDRAWER");
AstriaWithdrawer astriaWithdrawer = AstriaWithdrawer(contractAddress);

address destinationChainAddress = vm.envAddress("SEQUENCER_DESTINATION_CHAIN_ADDRESS");
uint256 amount = vm.envUint("AMOUNT");
astriaWithdrawer.withdrawToSequencer{value: amount}(destinationChainAddress);

vm.stopBroadcast();
}

function withdrawToOriginChain() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

address contractAddress = vm.envAddress("ASTRIA_WITHDRAWER");
AstriaWithdrawer astriaWithdrawer = AstriaWithdrawer(contractAddress);

string memory destinationChainAddress = vm.envString("ORIGIN_DESTINATION_CHAIN_ADDRESS");
uint256 amount = vm.envUint("AMOUNT");
astriaWithdrawer.withdrawToOriginChain{value: amount}(destinationChainAddress, "");

vm.stopBroadcast();
}
}
41 changes: 41 additions & 0 deletions crates/astria-bridge-withdrawer/ethereum/src/AstriaWithdrawer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT or Apache-2.0
pragma solidity ^0.8.21;

// This contract facilitates withdrawals of the native asset from the rollup to the base chain.
//
// Funds can be withdrawn to either the sequencer or the origin chain via IBC.
contract AstriaWithdrawer {
// the number of decimal places more the asset has on the rollup versus the base chain.
//
// the amount transferred on the base chain will be divided by 10^ASSET_WITHDRAWAL_DECIMALS.
//
// for example, if the rollup specifies the asset has 18 decimal places and the base chain specifies 6,
// the ASSET_WITHDRAWAL_DECIMALS would be 12.
uint32 public immutable ASSET_WITHDRAWAL_DECIMALS;

constructor(uint32 assetWithdrawalDecimals) {
ASSET_WITHDRAWAL_DECIMALS = assetWithdrawalDecimals;
}

// emitted when a withdrawal to the sequencer is initiated
//
// the `sender` is the evm address that initiated the withdrawal
// the `destinationChainAddress` is the address on the sequencer the funds will be sent to
event SequencerWithdrawal(address indexed sender, uint256 indexed amount, address destinationChainAddress);

// emitted when a withdrawal to the origin chain is initiated.
// the withdrawal is sent to the origin chain via IBC from the sequencer using the denomination trace.
//
// the `sender` is the evm address that initiated the withdrawal
// the `destinationChainAddress` is the address on the origin chain the funds will be sent to
// the `memo` is an optional field that will be used as the ICS20 packet memo
event Ics20Withdrawal(address indexed sender, uint256 indexed amount, string destinationChainAddress, string memo);

function withdrawToSequencer(address destinationChainAddress) external payable {
emit SequencerWithdrawal(msg.sender, msg.value, destinationChainAddress);
}

function withdrawToOriginChain(string calldata destinationChainAddress, string calldata memo) external payable {
emit Ics20Withdrawal(msg.sender, msg.value, destinationChainAddress, memo);
}
}
Loading

0 comments on commit afe4901

Please sign in to comment.