Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions offchain/deposit_signal_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ async fn main() -> Result<()> {
let data = bytes!();
let sender = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
let amount = U256::from(4000000000000000000_u128);
let relayer = Address::ZERO;

let (provider, _, _) = get_provider()?;

Expand All @@ -38,7 +37,10 @@ async fn main() -> Result<()> {
println!("Deployed ETH bridge at address: {}", eth_bridge.address());

println!("Sending ETH deposit...");
let builder = eth_bridge.deposit(sender, data, bytes!()).value(amount);
let zero_canceler = Address::ZERO;
let builder = eth_bridge
.deposit(sender, data, bytes!(), zero_canceler)
.value(amount);
let tx = builder.send().await?.get_receipt().await?;

// Get deposit ID from the transaction receipt logs
Expand Down
10 changes: 7 additions & 3 deletions offchain/sample_deposit_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod utils;
use utils::{deploy_eth_bridge, deploy_signal_service, get_proofs, get_provider, SignalProof};

use alloy::hex::decode;
use alloy::primitives::{Address, Bytes, FixedBytes, U256};
use alloy::primitives::{address, Address, Bytes, FixedBytes, U256};
use std::fs;

fn expand_vector(vec: Vec<Bytes>, name: &str) -> String {
Expand All @@ -28,6 +28,7 @@ fn create_deposit_call(
amount: U256,
data: &str,
context: &str,
canceler: &str,
id: &FixedBytes<32>,
) -> String {
let mut result = String::new();
Expand All @@ -50,6 +51,7 @@ fn create_deposit_call(
result += format!("\t\tdeposit.amount = {};\n", amount).as_str();
result += format!("\t\tdeposit.data = bytes(hex\"{}\");\n", data).as_str();
result += format!("\t\tdeposit.context = bytes(hex\"{}\");\n", context).as_str();
result += format!("\t\tdeposit.canceler = address({});\n", canceler).as_str();
result += format!("\t\t_createDeposit(\n\t\t\taccountProof,\n\t\t\tstorageProof,\n\t\t\tdeposit,\n\t\t\tbytes32({}),\n\t\t\tbytes32({})\n\t\t);\n", proof.slot, id).as_str();
return result;
}
Expand All @@ -76,7 +78,8 @@ fn deposit_specification() -> Vec<DepositSpecification> {
"5932a71200000000000000000000000000000000000000000000000000000000000004d2", // (valid) call to `someNonPayableFunction(1234)`
];

let zero_canceler = Address::ZERO;
// _randomAddress("canceler");
let canceler = address!("0xf9f5C5411F0bEf1880cE3B051BD14196479764D2");

let mut specifications = vec![];
for amount in amounts {
Expand All @@ -86,7 +89,7 @@ fn deposit_specification() -> Vec<DepositSpecification> {
amount: U256::from(amount),
data: data.to_string(),
context: String::from(""),
canceler: zero_canceler,
canceler,
});
}
}
Expand Down Expand Up @@ -145,6 +148,7 @@ async fn main() -> Result<()> {
d.amount,
d.data.as_str(),
d.context.as_str(),
&d.canceler.to_string(),
id,
)
.as_str();
Expand Down
2 changes: 1 addition & 1 deletion src/protocol/ETHBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ contract ETHBridge is IETHBridge, ReentrancyGuardTransient {
bytes memory proof
) internal returns (bytes32 id) {
id = _generateId(ethDeposit);
require(!processed(id), AlreadyClaimed());
require(!processed(id), AlreadyProcessed());

signalService.verifySignal(height, trustedCommitmentPublisher, counterpart, id, proof);

Expand Down
4 changes: 2 additions & 2 deletions src/protocol/IETHBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ interface IETHBridge {
/// @dev Failed to call the receiver with value.
error FailedClaim();

/// @dev A deposit was already claimed.
error AlreadyClaimed();
/// @dev A deposit was already claimed or cancelled
error AlreadyProcessed();

/// @dev Only canceler can cancel a deposit.
error OnlyCanceler();
Expand Down
77 changes: 77 additions & 0 deletions test/ETHBridge/CancellableScenarios.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {CrossChainDepositExists} from "./CrossChainDepositExists.t.sol";
import {IETHBridge} from "src/protocol/IETHBridge.sol";

/// This contract describes behaviours that should be valid when the deposit is cancellable.
abstract contract DepositIsCancellable is CrossChainDepositExists {
function test_cancelDeposit_shouldSucceed() public {
IETHBridge.ETHDeposit memory deposit = sampleDepositProof.getEthDeposit(_depositIdx());
bytes memory proof = abi.encode(sampleDepositProof.getDepositSignalProof(_depositIdx()));
vm.prank(cancellerAddress);
bridge.cancelDeposit(deposit, cancellationRecipient, HEIGHT, proof);
}

function test_cancelDeposit_shouldSetClaimedFlag() public {
IETHBridge.ETHDeposit memory deposit = sampleDepositProof.getEthDeposit(_depositIdx());
bytes memory proof = abi.encode(sampleDepositProof.getDepositSignalProof(_depositIdx()));
(, bytes32 id) = sampleDepositProof.getDepositInternals(_depositIdx());

assertFalse(bridge.processed(id), "deposit already marked as claimed");

vm.prank(cancellerAddress);
bridge.cancelDeposit(deposit, cancellationRecipient, HEIGHT, proof);
assertTrue(bridge.processed(id), "deposit not marked as claimed");
}

function test_cancelDeposit_shouldEmitEvent() public {
IETHBridge.ETHDeposit memory deposit = sampleDepositProof.getEthDeposit(_depositIdx());
bytes memory proof = abi.encode(sampleDepositProof.getDepositSignalProof(_depositIdx()));
(, bytes32 id) = sampleDepositProof.getDepositInternals(_depositIdx());

vm.expectEmit();
emit IETHBridge.DepositCancelled(id, cancellationRecipient);

vm.prank(cancellerAddress);
bridge.cancelDeposit(deposit, cancellationRecipient, HEIGHT, proof);
}

function test_cancelDeposit_shouldSendETH() public {
IETHBridge.ETHDeposit memory deposit = sampleDepositProof.getEthDeposit(_depositIdx());
bytes memory proof = abi.encode(sampleDepositProof.getDepositSignalProof(_depositIdx()));

uint256 initialCancellationRecipientBalance = cancellationRecipient.balance;
uint256 initialRecipientBalance = recipient.balance;
uint256 initialBridgeBalance = address(bridge).balance;

vm.prank(cancellerAddress);
bridge.cancelDeposit(deposit, cancellationRecipient, HEIGHT, proof);
assertEq(recipient.balance, initialRecipientBalance, "recipient balance mismatch");
assertEq(
cancellationRecipient.balance,
initialCancellationRecipientBalance + deposit.amount,
"cancel recipient balance mismatch"
);
assertEq(address(bridge).balance, initialBridgeBalance - deposit.amount, "bridge balance mismatch");
}

function test_claimDeposit_shouldRevertWhen_DepositIsCancelled() public {
IETHBridge.ETHDeposit memory deposit = sampleDepositProof.getEthDeposit(_depositIdx());
bytes memory proof = abi.encode(sampleDepositProof.getDepositSignalProof(_depositIdx()));

vm.prank(cancellerAddress);
bridge.cancelDeposit(deposit, cancellationRecipient, HEIGHT, proof);
vm.expectRevert(IETHBridge.AlreadyProcessed.selector);
bridge.claimDeposit(deposit, HEIGHT, proof);
}

function test_cancelDeposit_shouldRevertWhen_CancellerIsNotCaller() public {
IETHBridge.ETHDeposit memory deposit = sampleDepositProof.getEthDeposit(_depositIdx());
bytes memory proof = abi.encode(sampleDepositProof.getDepositSignalProof(_depositIdx()));

vm.expectRevert(IETHBridge.OnlyCanceler.selector);
vm.prank(_randomAddress("notCanceller"));
bridge.cancelDeposit(deposit, cancellationRecipient, HEIGHT, proof);
}
}
3 changes: 3 additions & 0 deletions test/ETHBridge/CrossChainDepositExists.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ abstract contract CrossChainDepositExists is UniversalTest {
// the sample deposit is to this address
address recipient = _randomAddress("recipient");

// the recipient that receives the cancelled deposit
address cancellationRecipient = _randomAddress("cancellationRecipient");

uint256 HEIGHT = 1;

function setUp() public virtual override {
Expand Down
12 changes: 12 additions & 0 deletions test/ETHBridge/EnumeratedScenarios.t.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {DepositIsCancellable} from "./CancellableScenarios.t.sol";
import {BridgeHasNoEther, BridgeSufficientlyCapitalized} from "./CapitalizationScenarios.t.sol";
import {DepositIsClaimable, DepositIsNotClaimable} from "./ClaimableScenarios.t.sol";
import {DepositIsInvalidContractCall, DepositIsValidContractCall} from "./ContractCallValidityScenarios.t.sol";
Expand Down Expand Up @@ -41,6 +42,17 @@ contract SimpleDepositToEOA is
}
}

contract CancelDepositToEOA is
NonzeroETH_NoCalldata,
RecipientIsAnEOA,
BridgeSufficientlyCapitalized,
DepositIsCancellable
{
function setUp() public override(CrossChainDepositExists, BridgeSufficientlyCapitalized) {
super.setUp();
}
}

// Same transfer as above, but the bridge does not have ETH. It should fail.
contract SimpleDepositToEOA_BridgeUndercollateralized is
NonzeroETH_NoCalldata,
Expand Down
4 changes: 3 additions & 1 deletion test/ETHBridge/InitialState.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {SampleDepositProof} from "./SampleDepositProof.t.sol";
import {ETHBridge} from "src/protocol/ETHBridge.sol";
import {SignalService} from "src/protocol/SignalService.sol";

import {console} from "forge-std/console.sol";

abstract contract InitialState is Test {
ETHBridge bridge;
SignalService signalService;
Expand All @@ -16,7 +18,7 @@ abstract contract InitialState is Test {
bytes anyRelayer = new bytes(0);

// zero address means deposit is uncancellable
address nonCancellableAddress = address(0);
address cancellerAddress = _randomAddress("canceler");
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

address trustedCommitmentPublisher = _randomAddress("trustedCommitmentPublisher");

Expand Down
Loading