Skip to content

Commit 56f28ae

Browse files
LeoPatOZnikeshnazarethggonzalez94
authored
[2/2] Decouple ETH Bridge from signal service (#113)
Co-authored-by: Nikesh Nazareth <[email protected]> Co-authored-by: Gustavo Gonzalez <[email protected]>
1 parent a5ad617 commit 56f28ae

13 files changed

+182
-237
lines changed

foundry.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ gas_reports_ignore = ["MockDelayedInclusionStore", "MockCheckpointTracker", ""]
1111

1212
remappings = [
1313
"@optimism/=lib/optimism/",
14-
"@vendor/=src/vendor/"
14+
"@vendor/=src/vendor/",
15+
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/"
1516
]
1617

1718
[fmt]

offchain/deposit_signal_proof.rs

+16-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use alloy::primitives::{address, bytes, U256};
1+
use alloy::primitives::{address, bytes, B256, U256};
22
use eyre::Result;
33

44
mod utils;
5-
use utils::{deploy_signal_service, get_proofs, get_provider};
5+
use utils::{deploy_eth_bridge, deploy_signal_service, get_proofs, get_provider};
66

77
mod signal_slot;
8-
use signal_slot::{get_signal_slot, NameSpaceConst};
8+
use signal_slot::get_signal_slot;
99

1010
#[tokio::main]
1111
async fn main() -> Result<()> {
@@ -23,6 +23,9 @@ async fn main() -> Result<()> {
2323
let sender = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
2424
let amount = U256::from(4000000000000000000_u128);
2525

26+
// This is the checkpoint tracker
27+
let trusted_publisher = address!("0xCafac3dD18aC6c6e92c921884f9E4176737C052c");
28+
2629
let (provider, _anvil) = get_provider()?;
2730

2831
let signal_service = deploy_signal_service(&provider).await?;
@@ -32,16 +35,23 @@ async fn main() -> Result<()> {
3235
signal_service.address()
3336
);
3437

35-
println!("Sending ETH deposit signal...");
36-
let builder = signal_service.deposit(sender, data).value(amount);
38+
let eth_bridge =
39+
deploy_eth_bridge(&provider, *signal_service.address(), trusted_publisher).await?;
40+
41+
println!("Deployed ETH bridge at address: {}", eth_bridge.address());
42+
43+
println!("Sending ETH deposit...");
44+
let builder = eth_bridge.deposit(sender, data).value(amount);
3745
let tx = builder.send().await?.get_receipt().await?;
3846

3947
// Get deposit ID from the transaction receipt logs
4048
// possibly a better way to do this, but this works :)
4149
let receipt_logs = tx.logs().get(0).unwrap().topics();
4250
let deposit_id = receipt_logs.get(1).unwrap();
51+
let depid: B256 =
52+
"0xf9c183d2de58fbeb1a8917170139e980fa1b6e5a358ec83721e11c9f6e25eb18".parse()?;
4353

44-
let slot = get_signal_slot(deposit_id, &sender, NameSpaceConst::ETHBridge);
54+
let slot = get_signal_slot(&depid, &eth_bridge.address());
4555
get_proofs(&provider, slot, &signal_service).await?;
4656

4757
Ok(())

offchain/generic_signal_proof.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use alloy::primitives::{Address, B256};
44
use eyre::{eyre, Result};
55

66
mod signal_slot;
7-
use signal_slot::{get_signal_slot, NameSpaceConst};
7+
use signal_slot::get_signal_slot;
88

99
mod utils;
1010
use utils::{deploy_signal_service, get_proofs, get_provider};
@@ -48,7 +48,7 @@ async fn main() -> Result<()> {
4848
let builder = signal_service.sendSignal(signal);
4949
builder.send().await?.watch().await?;
5050

51-
let slot = get_signal_slot(&signal, &sender, NameSpaceConst::Signal);
51+
let slot = get_signal_slot(&signal, &sender);
5252
get_proofs(&provider, slot, &signal_service).await?;
5353

5454
Ok(())

offchain/signal_slot.rs

+4-31
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,6 @@ use alloy::{
66
};
77
use eyre::{eyre, Result};
88

9-
pub enum NameSpaceConst {
10-
Signal,
11-
ETHBridge,
12-
}
13-
14-
impl NameSpaceConst {
15-
pub fn value(&self) -> B256 {
16-
match self {
17-
NameSpaceConst::Signal => keccak256("generic-signal"),
18-
NameSpaceConst::ETHBridge => keccak256("eth-bridge"),
19-
}
20-
}
21-
22-
pub fn from_arg(arg: &str) -> Result<Self> {
23-
match arg {
24-
"1" => Ok(NameSpaceConst::Signal),
25-
"2" => Ok(NameSpaceConst::ETHBridge),
26-
_ => Err(eyre!("Invalid namespace selection: must be '1' for normal signals or '2' for eth deposits")),
27-
}
28-
}
29-
}
30-
319
pub fn erc7201_slot(namespace: &Vec<u8>) -> B256 {
3210
let namespace_hash = keccak256(namespace);
3311

@@ -41,8 +19,8 @@ pub fn erc7201_slot(namespace: &Vec<u8>) -> B256 {
4119
aligned_slot
4220
}
4321

44-
pub fn get_signal_slot(signal: &B256, sender: &Address, namespace: NameSpaceConst) -> B256 {
45-
let namespace = (signal, sender, namespace.value()).abi_encode_packed();
22+
pub fn get_signal_slot(signal: &B256, sender: &Address) -> B256 {
23+
let namespace = (signal, sender).abi_encode_packed();
4624
return erc7201_slot(namespace.as_ref());
4725
}
4826

@@ -51,7 +29,7 @@ fn main() -> Result<()> {
5129
let args: Vec<String> = env::args().collect();
5230
if args.len() != 4 {
5331
return Err(eyre!(
54-
"Usage: cargo run --bin signal_slot <signal (0x...)> <sender (0x...)> <namespace (1 or 2)>"
32+
"Usage: cargo run --bin signal_slot <signal (0x...)> <sender (0x...)>"
5533
));
5634
}
5735

@@ -65,11 +43,6 @@ fn main() -> Result<()> {
6543
.parse()
6644
.map_err(|_| eyre!("Invalid sender format: {}", sender_str))?;
6745

68-
let namespace = NameSpaceConst::from_arg(&args[3])?;
69-
70-
println!(
71-
"Signal Slot: {:?}",
72-
get_signal_slot(&signal, &sender, namespace)
73-
);
46+
println!("Signal Slot: {:?}", get_signal_slot(&signal, &sender));
7447
Ok(())
7548
}

offchain/utils.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,28 @@ use alloy::{
66
sol,
77
};
88

9+
use ETHBridge::ETHBridgeInstance;
910
use SignalService::SignalServiceInstance;
1011

1112
use eyre::Result;
1213
use serde_json::to_string_pretty;
1314

1415
const BLOCK_TIME: u64 = 5;
1516

16-
// NOTE: This needs to match the address of the rollup operator in the tests
17-
// to ensure the signal service is deployed to the same address.
18-
const ROLLUP_OPERATOR: Address = address!("0xCf03Dd0a894Ef79CB5b601A43C4b25E3Ae4c67eD");
19-
2017
sol!(
2118
#[allow(missing_docs)]
2219
#[sol(rpc)]
2320
SignalService,
2421
"./out/SignalService.sol/SignalService.json",
2522
);
2623

24+
sol!(
25+
#[allow(missing_docs)]
26+
#[sol(rpc)]
27+
ETHBridge,
28+
"./out/ETHBridge.sol/ETHBridge.json"
29+
);
30+
2731
pub fn get_provider() -> Result<(impl Provider, AnvilInstance)> {
2832
let anvil = Anvil::new()
2933
.block_time(BLOCK_TIME)
@@ -40,7 +44,23 @@ pub fn get_provider() -> Result<(impl Provider, AnvilInstance)> {
4044
pub async fn deploy_signal_service(
4145
provider: &impl Provider,
4246
) -> Result<SignalServiceInstance<(), &impl Provider>> {
43-
let contract = SignalService::deploy(provider, ROLLUP_OPERATOR).await?;
47+
let contract = SignalService::deploy(provider).await?;
48+
Ok(contract)
49+
}
50+
51+
pub async fn deploy_eth_bridge(
52+
provider: &impl Provider,
53+
signal_service: Address,
54+
trusted_publisher: Address,
55+
) -> Result<ETHBridgeInstance<(), &impl Provider>> {
56+
// L2 Eth bridge address
57+
let counterpart = address!("0xDC9e4C83bDe3912E9B63A9BF9cE263F3309aB5d4");
58+
// WARN: This is a slight hack for now to make sure the contract is deployed on the correct address
59+
ETHBridge::deploy(provider, signal_service, trusted_publisher, counterpart).await?;
60+
61+
let contract =
62+
ETHBridge::deploy(provider, signal_service, trusted_publisher, counterpart).await?;
63+
4464
Ok(contract)
4565
}
4666

src/libs/LibSignal.sol

+20-29
Original file line numberDiff line numberDiff line change
@@ -12,66 +12,57 @@ library LibSignal {
1212
using SlotDerivation for string;
1313
using SafeCast for uint256;
1414

15-
bytes32 constant SIGNAL_NAMESPACE = keccak256("generic-signal");
16-
1715
/// @dev A `value` was signaled at a namespaced slot for the current `msg.sender`.
1816
function signaled(bytes32 value) internal view returns (bool) {
19-
return signaled(value, SIGNAL_NAMESPACE);
20-
}
21-
22-
/// @dev A `value` was signaled at a namespaced slot for the current `msg.sender` and
23-
/// namespace.
24-
function signaled(bytes32 value, bytes32 namespace) internal view returns (bool) {
25-
return signaled(value, msg.sender, namespace);
17+
return signaled(value, msg.sender);
2618
}
2719

2820
/// @dev A `value` was signaled at a namespaced slot. See `deriveSlot`.
29-
function signaled(bytes32 value, address account, bytes32 namespace) internal view returns (bool) {
30-
bytes32 slot = deriveSlot(value, account, namespace);
21+
function signaled(bytes32 value, address account) internal view returns (bool) {
22+
bytes32 slot = deriveSlot(value, account);
3123
return slot.getBooleanSlot().value == true;
3224
}
3325

34-
/// @dev Signal a `value` at a namespaced slot for the current `msg.sender` and namespace.
26+
/// @dev Signal a `value` at a namespaced slot for the current `msg.sender`.
3527
function signal(bytes32 value) internal returns (bytes32) {
36-
return signal(value, msg.sender, SIGNAL_NAMESPACE);
28+
return signal(value, msg.sender);
3729
}
3830

3931
/// @dev Signal a `value` at a namespaced slot. See `deriveSlot`.
40-
function signal(bytes32 value, address account, bytes32 namespace) internal returns (bytes32) {
41-
bytes32 slot = deriveSlot(value, account, namespace);
32+
function signal(bytes32 value, address account) internal returns (bytes32) {
33+
bytes32 slot = deriveSlot(value, account);
4234
slot.getBooleanSlot().value = true;
4335
return slot;
4436
}
4537

46-
/// @dev Returns the storage slot for a signal. Namespaced to the msg.sender, value and namespace.
47-
function deriveSlot(bytes32 value, bytes32 namespace) internal view returns (bytes32) {
48-
return deriveSlot(value, msg.sender, namespace);
38+
/// @dev Returns the storage slot for a signal. Namespaced to the msg.sender and value
39+
function deriveSlot(bytes32 value) internal view returns (bytes32) {
40+
return deriveSlot(value, msg.sender);
4941
}
5042

51-
/// @dev Returns the storage slot for a signal. Namespaced to the current account and value and namespace.
52-
function deriveSlot(bytes32 value, address account, bytes32 namespace) internal pure returns (bytes32) {
53-
return string(abi.encodePacked(value, account, namespace)).erc7201Slot();
43+
/// @dev Returns the storage slot for a signal. Namespaced to the current account and value.
44+
function deriveSlot(bytes32 value, address account) internal pure returns (bytes32) {
45+
return string(abi.encodePacked(value, account)).erc7201Slot();
5446
}
5547

5648
/// @dev Performs a storage proof verification for a signal stored on the contract using this library
5749
/// @param value The signal value to verify
58-
/// @param namespace The namespace of the signal
5950
/// @param sender The address that originally sent the signal on the source chain
60-
/// @param root The state root or storage root from the source chain to verify against
61-
/// @param accountProof Merkle proof for the contract's account against the state root. Empty if we are using a
62-
/// storage root.
63-
/// @param storageProof Merkle proof for the derived storage slot against the account's storage root
51+
/// @param root The state root from the source chain to verify against
52+
/// @param accountProof Merkle proof for the SignalService account against the state root.
53+
/// @param storageProof Merkle proof for the derived storage slot against the SignalService's storage root
54+
/// @dev We can use `address(this)` as the SignalService address, even when `root` refers to a different chain,
55+
/// because we assume the SignalService is deployed at the same address on every chain.
6456
function verifySignal(
6557
bytes32 value,
66-
bytes32 namespace,
6758
address sender,
6859
bytes32 root,
6960
bytes[] memory accountProof,
7061
bytes[] memory storageProof
71-
) internal pure {
62+
) internal view {
7263
bytes32 encodedBool = bytes32(uint256(1));
7364
LibTrieProof.verifyMerkleProof(
74-
root, sender, deriveSlot(value, sender, namespace), encodedBool, accountProof, storageProof
65+
root, address(this), deriveSlot(value, sender), encodedBool, accountProof, storageProof
7566
);
7667
}
7768
}

src/protocol/ETHBridge.sol

+35-19
Original file line numberDiff line numberDiff line change
@@ -2,62 +2,78 @@
22
pragma solidity ^0.8.28;
33

44
import {IETHBridge} from "./IETHBridge.sol";
5+
import {ISignalService} from "./ISignalService.sol";
6+
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
57

6-
/// @dev Abstract ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs.
8+
/// @dev ETH bridging contract to send native ETH between L1 <-> L2 using storage proofs.
79
///
810
/// IMPORTANT: No recovery mechanism is implemented in case an account creates a deposit that can't be claimed.
9-
contract ETHBridge is IETHBridge {
11+
contract ETHBridge is IETHBridge, ReentrancyGuardTransient {
1012
mapping(bytes32 id => bool claimed) private _claimed;
1113

1214
/// Incremental nonce to generate unique deposit IDs.
1315
uint256 private _globalDepositNonce;
1416

15-
/// Namespace for the ETH bridge.
16-
bytes32 internal constant ETH_BRIDGE_NAMESPACE = keccak256("eth-bridge");
17+
ISignalService public immutable signalService;
18+
19+
/// @dev Trusted source of commitments in the `CommitmentStore` that the bridge will use to validate withdrawals
20+
/// @dev This is the Anchor on L2 and the Checkpoint Tracker on the L1
21+
address public immutable trustedCommitmentPublisher;
22+
23+
/// @dev The counterpart bridge contract on the other chain.
24+
/// This is used to locate deposit signals inside the other chain's state root.
25+
/// WARN: This address has no significance (and may be untrustworthy) on this chain.
26+
address public immutable counterpart;
27+
28+
constructor(address _signalService, address _trustedCommitmentPublisher, address _counterpart) {
29+
require(_signalService != address(0), "Empty signal service");
30+
require(_trustedCommitmentPublisher != address(0), "Empty trusted publisher");
31+
32+
signalService = ISignalService(_signalService);
33+
trustedCommitmentPublisher = _trustedCommitmentPublisher;
34+
counterpart = _counterpart;
35+
}
1736

1837
/// @inheritdoc IETHBridge
19-
function claimed(bytes32 id) public view virtual returns (bool) {
38+
function claimed(bytes32 id) public view returns (bool) {
2039
return _claimed[id];
2140
}
2241

2342
/// @inheritdoc IETHBridge
24-
function getDepositId(ETHDeposit memory ethDeposit) public view virtual returns (bytes32 id) {
43+
function getDepositId(ETHDeposit memory ethDeposit) public pure returns (bytes32 id) {
2544
return _generateId(ethDeposit);
2645
}
2746

2847
/// @inheritdoc IETHBridge
29-
function deposit(address to, bytes memory data) public payable virtual returns (bytes32 id) {
48+
function deposit(address to, bytes memory data) public payable returns (bytes32 id) {
3049
ETHDeposit memory ethDeposit = ETHDeposit(_globalDepositNonce, msg.sender, to, msg.value, data);
3150
id = _generateId(ethDeposit);
3251
unchecked {
3352
++_globalDepositNonce;
3453
}
54+
55+
signalService.sendSignal(id);
3556
emit DepositMade(id, ethDeposit);
3657
}
3758

3859
/// @inheritdoc IETHBridge
39-
function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof)
40-
external
41-
returns (bytes32 id)
42-
{
43-
id = _generateId(ethDeposit);
44-
}
45-
46-
/// @dev Processes deposit claim by id.
47-
/// @param id Identifier of the deposit
48-
/// @param ethDeposit Deposit to process
49-
function _processClaimDepositWithId(bytes32 id, ETHDeposit memory ethDeposit) internal virtual {
60+
function claimDeposit(ETHDeposit memory ethDeposit, uint256 height, bytes memory proof) external nonReentrant {
61+
bytes32 id = _generateId(ethDeposit);
5062
require(!claimed(id), AlreadyClaimed());
63+
64+
signalService.verifySignal(height, trustedCommitmentPublisher, counterpart, id, proof);
65+
5166
_claimed[id] = true;
5267
_sendETH(ethDeposit.to, ethDeposit.amount, ethDeposit.data);
68+
5369
emit DepositClaimed(id, ethDeposit);
5470
}
5571

5672
/// @dev Function to transfer ETH to the receiver but ignoring the returndata.
5773
/// @param to Address to send the ETH to
5874
/// @param value Amount of ETH to send
5975
/// @param data Data to send to the receiver
60-
function _sendETH(address to, uint256 value, bytes memory data) internal virtual {
76+
function _sendETH(address to, uint256 value, bytes memory data) internal {
6177
(bool success,) = to.call{value: value}(data);
6278
require(success, FailedClaim());
6379
}

0 commit comments

Comments
 (0)