diff --git a/Cargo.toml b/Cargo.toml index ddbfb20f..09c51314 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,16 @@ [package] - name = "minimal-rollup" version = "0.1.0" edition = "2021" +[lib] +name = "minimal_rollup" +path = "offchain/lib.rs" + [[bin]] name = "signal_slot" path = "offchain/signal_slot.rs" -[[bin]] -name = "utils" -path = "offchain/utils.rs" - [[bin]] name = "sample_signal_proof" path = "offchain/sample_signal_proof.rs" diff --git a/offchain/utils.rs b/offchain/lib.rs similarity index 100% rename from offchain/utils.rs rename to offchain/lib.rs diff --git a/offchain/sample_deposit_proof.rs b/offchain/sample_deposit_proof.rs index 5f38f5bf..990565d1 100644 --- a/offchain/sample_deposit_proof.rs +++ b/offchain/sample_deposit_proof.rs @@ -1,15 +1,25 @@ +use alloy::primitives::utils::parse_units; +use alloy::sol_types::SolCall; use eyre::Result; mod signal_slot; use signal_slot::get_signal_slot; -mod utils; -use utils::{deploy_eth_bridge, deploy_signal_service, get_proofs, get_provider, SignalProof}; +use minimal_rollup::{ + deploy_eth_bridge, deploy_signal_service, get_proofs, get_provider, SignalProof, +}; -use alloy::hex::decode; +use alloy::hex::{self, decode}; use alloy::primitives::{address, Address, Bytes, FixedBytes, U256}; use std::fs; +use alloy::sol; + +sol! { + function somePayableFunction(uint256 someArg) external payable returns (uint256); + function someNonpayableFunction(uint256 someArg) external returns (uint256); +} + fn expand_vector(vec: Vec, name: &str) -> String { let mut expanded = String::new(); for (i, item) in vec.iter().enumerate() { @@ -20,7 +30,7 @@ fn expand_vector(vec: Vec, name: &str) -> String { return expanded; } -fn create_deposit_call( +pub fn create_deposit_call( proof: SignalProof, nonce: usize, signer: Address, @@ -69,13 +79,31 @@ fn deposit_specification() -> Vec { // In this case, the CrossChainDepositExists.sol test case defines makeAddr("recipient"); let recipient = "0x006217c47ffA5Eb3F3c92247ffFE22AD998242c5"; // Use both zero and non-zero amounts (in this case 4 ether) - let amounts = vec![0_u128, 4000000000000000000_u128]; + let amounts: Vec = vec![U256::ZERO, parse_units("4", "ether").unwrap().into()]; + // Use different calldata to try different functions and inputs + let valid_payable_function_call = somePayableFunctionCall { + someArg: U256::from(1234), + } + .abi_encode(); + let invalid_payable_function_call = somePayableFunctionCall { + someArg: U256::from(1235), + } + .abi_encode(); + let valid_nonpayable_function_call = someNonpayableFunctionCall { + someArg: U256::from(1234), + } + .abi_encode(); + + let valid_payable_encoded = hex::encode(valid_payable_function_call); + let invalid_payable_encoded = hex::encode(invalid_payable_function_call); + let valid_nonpayable_encoded = hex::encode(valid_nonpayable_function_call); + let calldata = vec![ - "", // empty - "9b28f6fb00000000000000000000000000000000000000000000000000000000000004d2", // (valid) call to somePayableFunction(1234) - "9b28f6fb00000000000000000000000000000000000000000000000000000000000004d3", // (invalid) call to somePayableFunction(1235) - "5932a71200000000000000000000000000000000000000000000000000000000000004d2", // (valid) call to `someNonPayableFunction(1234)` + "", + &valid_payable_encoded, + &invalid_payable_encoded, + &valid_nonpayable_encoded, ]; // makeAddr("canceler"); @@ -126,6 +154,7 @@ async fn main() -> Result<()> { let deposits = deposit_specification(); assert!(deposits.len() > 0, "No deposits to prove"); let mut ids: Vec> = vec![]; + // Perform all deposits for (_i, spec) in deposits.iter().enumerate() { let tx = eth_bridge @@ -175,7 +204,7 @@ async fn main() -> Result<()> { .as_str(); } - let template = fs::read_to_string("offchain/sample_deposit_proof.tmpl")?; + let template = fs::read_to_string("offchain/tmpl/sample_deposit_proof.tmpl")?; let formatted = template .replace( "{signal_service_address}", diff --git a/offchain/sample_signal_proof.rs b/offchain/sample_signal_proof.rs index ab7aa6de..db618470 100644 --- a/offchain/sample_signal_proof.rs +++ b/offchain/sample_signal_proof.rs @@ -3,8 +3,7 @@ use eyre::Result; mod signal_slot; use signal_slot::get_signal_slot; -mod utils; -use utils::{deploy_signal_service, get_proofs, get_provider}; +use minimal_rollup::{deploy_signal_service, get_proofs, get_provider}; use alloy::primitives::Bytes; use std::fs; @@ -32,7 +31,7 @@ async fn main() -> Result<()> { let proof = get_proofs(&provider, slot, &signal_service).await?; - let template = fs::read_to_string("offchain/sample_proof.tmpl")?; + let template = fs::read_to_string("offchain/tmpl/sample_proof.tmpl")?; let formatted = template .replace("{signal_service_address}", signal_service.address().to_string().as_str()) .replace("{block_hash}", proof.block_hash.to_string().as_str()) @@ -47,4 +46,3 @@ async fn main() -> Result<()> { println!("{}", formatted); Ok(()) } - diff --git a/offchain/sample_deposit_proof.tmpl b/offchain/tmpl/sample_deposit_proof.tmpl similarity index 100% rename from offchain/sample_deposit_proof.tmpl rename to offchain/tmpl/sample_deposit_proof.tmpl diff --git a/offchain/sample_proof.tmpl b/offchain/tmpl/sample_proof.tmpl similarity index 100% rename from offchain/sample_proof.tmpl rename to offchain/tmpl/sample_proof.tmpl diff --git a/src/protocol/IMessageRelayer.sol b/src/protocol/IMessageRelayer.sol index 6e219887..81ec1b79 100644 --- a/src/protocol/IMessageRelayer.sol +++ b/src/protocol/IMessageRelayer.sol @@ -19,6 +19,9 @@ interface IMessageRelayer { /// @dev Message forwarding failed error MessageForwardingFailed(); + /// @dev Value sent is lower than tip amount + error InsufficientValue(); + /// @dev Tip transfer failed error TipTransferFailed(); diff --git a/src/protocol/taiko_alethia/MessageRelayer.sol b/src/protocol/taiko_alethia/MessageRelayer.sol index e1920fd8..2febe7c9 100644 --- a/src/protocol/taiko_alethia/MessageRelayer.sol +++ b/src/protocol/taiko_alethia/MessageRelayer.sol @@ -92,6 +92,7 @@ contract MessageRelayer is ReentrancyGuardTransient, IMessageRelayer { require(tipRecipient != address(0), NoTipRecipient()); } + require(msg.value >= tip, InsufficientValue()); uint256 valueToSend = msg.value - tip; bool forwardMessageSuccess; diff --git a/test/MessageRelayer/DepositRecipientScenarios.t.sol b/test/MessageRelayer/DepositRecipientScenarios.t.sol new file mode 100644 index 00000000..bff2801b --- /dev/null +++ b/test/MessageRelayer/DepositRecipientScenarios.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {GenericRecipient} from "./GenericRecipient.t.sol"; +import {InitialState} from "./InitialState.t.sol"; + +import {IETHBridge} from "src/protocol/IETHBridge.sol"; +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; + +abstract contract DepositRecipientScenarios is InitialState {} + +contract DepositRecipientIsMessageRelayer is DepositRecipientScenarios { + function test_DepositRecipientIsMessageRelayer_relayMessage_shouldInvokeReceiveMessage() public ifRelaySucceeds { + vm.expectCall(address(messageRelayer), ethDeposit.data); + _relayMessage(); + } + + function test_DepositRecipientIsMessageRelayer_claimDeposit_shouldInvokeReceiveMessage() public ifClaimSucceeds { + vm.expectCall(address(messageRelayer), ethDeposit.data); + _claimDeposit(); + } +} + +contract DepositRecipientIsNotMessageRelayer is DepositRecipientScenarios { + function setUp() public override { + super.setUp(); + // bypass the relayer and send the message directly to the recipient + // do not bother changing the default message encoding (to a `receiveMessage` function) + // because the recipient handles any message + ethDeposit.to = address(to); + } + + function test_DepositRecipientIsNotMessageRelayer_relayMessage_shouldInvokeRecipient() public { + vm.expectEmit(); + emit GenericRecipient.FunctionCalled(); + _relayMessage(); + } + + function test_DepositRecipientIsNotMessageRelayer_relayMessage_shouldNotInvokeReceiveMessage() public { + vm.expectCall(address(messageRelayer), ethDeposit.data, 0); + _relayMessage(); + } +} + +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultRecipientScenario is DepositRecipientScenarios {} diff --git a/test/MessageRelayer/FundAmountScenarios.t.sol b/test/MessageRelayer/FundAmountScenarios.t.sol new file mode 100644 index 00000000..8ec38a43 --- /dev/null +++ b/test/MessageRelayer/FundAmountScenarios.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {GenericRecipient} from "./GenericRecipient.t.sol"; +import {DefaultTipRecipientScenario} from "./TipRecipientScenarios.t.sol"; +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; + +import {InitialState} from "./InitialState.t.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; + +abstract contract FundAmountScenarios is DefaultTipRecipientScenario { + function test_FundAmountScenarios_relayMessage_shouldInvokeRecipient() public ifRelaySucceeds { + vm.expectEmit(); + emit GenericRecipient.FunctionCalled(); + _relayMessage(); + } + + function test_FundAmountScenarios_relayMessage_shouldNotRetainFundsInRelayer() public ifRelaySucceeds { + assertEq(address(messageRelayer).balance, 0, "relayer should not have funds"); + _relayMessage(); + assertEq(address(messageRelayer).balance, 0, "relayer should not retain funds"); + } + + function test_FundAmountScenarios_relayMessage_shouldSendAmountToRecipient() public ifRelaySucceeds { + uint256 balanceBefore = address(to).balance; + uint256 transferAmount = ethDeposit.amount - tip; + _relayMessage(); + assertEq(address(to).balance, balanceBefore + transferAmount, "recipient balance mismatch"); + } + + function redundant_FundAmountScenarios_relayMessage_shouldSendTipToRecipient() public { + // This test (if it were implemented) would be redundant with the tip recipient scenarios + // It is included for completeness, so this file accounts for all the distributed funds + } +} + +contract AmountExceedsTip is FundAmountScenarios {} + +contract NoAmountNoTip is FundAmountScenarios { + function setUp() public override { + super.setUp(); + ethDeposit.amount = 0; + tip = 0; + _encodeReceiveCall(); + } +} + +contract NoAmountNonzeroTip is FundAmountScenarios { + function setUp() public override { + super.setUp(); + ethDeposit.amount = 0; + relayShouldSucceed = false; + claimShouldSucceed = false; + } +} + +contract AmountLessThanTip is FundAmountScenarios { + function setUp() public override { + super.setUp(); + ethDeposit.amount = tip - 1 wei; + relayShouldSucceed = false; + claimShouldSucceed = false; + } +} + +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultFundAmountScenario is AmountExceedsTip {} diff --git a/test/MessageRelayer/GasLimitScenarios.t.sol b/test/MessageRelayer/GasLimitScenarios.t.sol new file mode 100644 index 00000000..c55cc062 --- /dev/null +++ b/test/MessageRelayer/GasLimitScenarios.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {DefaultFundAmountScenario} from "./FundAmountScenarios.t.sol"; +import {GenericRecipient} from "./GenericRecipient.t.sol"; + +import {InitialState} from "./InitialState.t.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; + +// Found by experimentation +uint256 constant OOG_INSIDE_RECIPIENT = 70_000; + +abstract contract GasLimitScenarios is DefaultFundAmountScenario {} + +contract NoGasLimit_SufficientGasProvided is GasLimitScenarios {} + +contract NoGasLimit_InsufficientGasProvided is GasLimitScenarios { + function setUp() public override { + super.setUp(); + gasProvidedWithCall = OOG_INSIDE_RECIPIENT; + relayShouldSucceed = false; + claimShouldSucceed = false; + } +} + +contract SufficientGasLimit_SufficientGasProvided is GasLimitScenarios { + function setUp() public override { + super.setUp(); + gasLimit = to.GAS_REQUIRED() + 100; + _encodeReceiveCall(); + } +} + +contract SufficientGasLimit_InsufficientGasProvided is GasLimitScenarios { + function setUp() public override { + super.setUp(); + gasLimit = to.GAS_REQUIRED() + 100; + _encodeReceiveCall(); + gasProvidedWithCall = OOG_INSIDE_RECIPIENT; + relayShouldSucceed = false; + claimShouldSucceed = false; + } +} + +contract InsufficientGasLimit_SufficientGasProvided is GasLimitScenarios { + function setUp() public override { + super.setUp(); + // the amount forwarded to the recipient is slightly higher than gasLimit so deduct 150 as compensation + // TODO: understand why this is necessary + gasLimit = to.GAS_REQUIRED() - 150; + _encodeReceiveCall(); + relayShouldSucceed = false; + claimShouldSucceed = false; + } +} + +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultGasLimitScenario is NoGasLimit_SufficientGasProvided {} diff --git a/test/MessageRelayer/GenericRecipient.t.sol b/test/MessageRelayer/GenericRecipient.t.sol new file mode 100644 index 00000000..bb38070c --- /dev/null +++ b/test/MessageRelayer/GenericRecipient.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; + +interface IGenericRecipient { + function setSuccess(bool _callWillSucceed) external; + function setReentrancyAttack(bool _shouldAttack) external; +} + +contract GenericRecipient is IGenericRecipient { + bool private callWillSucceed = true; + bool private shouldReenterAttack = false; + address private relayer; + + // Consume a minimum amount of gas so we can test gas limits + uint256 public constant GAS_REQUIRED = 20_000; + + error CallFailed(); + + event FunctionCalled(); + + constructor(address _relayer) { + relayer = _relayer; + } + + function setSuccess(bool _callWillSucceed) external { + callWillSucceed = _callWillSucceed; + } + + function setReentrancyAttack(bool _shouldAttack) external { + shouldReenterAttack = _shouldAttack; + } + + fallback() external payable { + _simulateFunctionCall(); + } + + receive() external payable { + _simulateFunctionCall(); + } + + function _simulateFunctionCall() internal { + require(callWillSucceed, CallFailed()); + require(gasleft() >= GAS_REQUIRED, "Insufficient gas"); + + emit FunctionCalled(); + + if (shouldReenterAttack) { + IMessageRelayer(relayer).receiveMessage(address(this), 0, address(this), 0, "0x"); + } + } +} diff --git a/test/MessageRelayer/InitialState.t.sol b/test/MessageRelayer/InitialState.t.sol new file mode 100644 index 00000000..6ff5cb0c --- /dev/null +++ b/test/MessageRelayer/InitialState.t.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import "forge-std/Test.sol"; + +import {ETHBridge} from "src/protocol/ETHBridge.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; + +import {GenericRecipient} from "./GenericRecipient.t.sol"; +import {IMessageRelayer} from "src/protocol/IMessageRelayer.sol"; +import {MessageRelayer} from "src/protocol/taiko_alethia/MessageRelayer.sol"; +import {MockSignalService} from "test/mocks/MockSignalService.sol"; + +// An explanation of the test structure: +// - we want to enumerate over several different configurations (eg. valid/invalid/zero tip recipients, +// sufficient/insufficient funds, eventual call succeeds/fails) +// - however, many of the tests are only relevant if the overall transaction succeeds, and this depends on settings +// defined in other files +// - ideally, we would only run the tests in the relevant scenario, but this would require less encapsulated logic +// - instead, the ifRelaySucceeds and ifClaimSucceeds modifiers are used to turn irrelevant tests into no-ops +// - the tests in this file ensure the transaction reverts when it is expected to + +abstract contract InitialState is Test { + MessageRelayer messageRelayer; + ETHBridge bridge; + + // Default message parameters + IETHBridge.ETHDeposit ethDeposit; + uint256 height = 0; + bytes proof = "0x"; + GenericRecipient to; + uint256 amount = 2 ether; + uint256 tip = 0.1 ether; + GenericRecipient relayerSelectedTipRecipient; + GenericRecipient userSelectedTipRecipient; + uint256 gasLimit = 0; + bytes data = "0x"; + + // `claimDeposit` may fail when `relayMessage` succeeds, so these are separate flags + bool relayShouldSucceed = true; + bool claimShouldSucceed = true; + + uint256 gasProvidedWithCall = 150_000; // covers a full relayMessage call with some overhead + + function setUp() public virtual { + MockSignalService signalService = new MockSignalService(); + signalService.setVerifyResult(true); + address trustedCommitmentPublisher = makeAddr("trustedCommitmentPublisher"); + address counterpart = makeAddr("counterpart"); + bridge = new ETHBridge(address(signalService), trustedCommitmentPublisher, counterpart); + vm.deal(address(bridge), amount); + + messageRelayer = new MessageRelayer(address(bridge)); + + to = new GenericRecipient(address(messageRelayer)); + relayerSelectedTipRecipient = new GenericRecipient(address(messageRelayer)); + userSelectedTipRecipient = new GenericRecipient(address(messageRelayer)); + + ethDeposit = IETHBridge.ETHDeposit({ + nonce: 0, + from: makeAddr("from"), + to: address(messageRelayer), + amount: 2 ether, + data: "", + context: "", + canceler: address(0) + }); + _encodeReceiveCall(); + } + + function test_InitialState_relayMessage_shouldRevertWhenExpected() public { + if (!relayShouldSucceed) { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _relayMessage(); + } + } + + function test_InitialState_claimDeposit_shouldRevertWhenExpected() public { + if (!claimShouldSucceed) { + vm.expectRevert(IETHBridge.FailedClaim.selector); + _claimDeposit(); + } + } + + function _encodeReceiveCall() internal { + ethDeposit.data = abi.encodeCall( + IMessageRelayer.receiveMessage, (address(to), tip, address(userSelectedTipRecipient), gasLimit, data) + ); + } + + function _relayMessage() internal { + messageRelayer.relayMessage{gas: gasProvidedWithCall}( + ethDeposit, height, proof, address(relayerSelectedTipRecipient) + ); + } + + function _claimDeposit() internal { + bridge.claimDeposit{gas: gasProvidedWithCall}(ethDeposit, height, proof); + } + + modifier ifRelaySucceeds() { + if (relayShouldSucceed) { + _; + } + } + + modifier ifClaimSucceeds() { + if (claimShouldSucceed) { + _; + } + } +} diff --git a/test/MessageRelayer/RelayRecipientScenarios.t.sol b/test/MessageRelayer/RelayRecipientScenarios.t.sol new file mode 100644 index 00000000..bb516603 --- /dev/null +++ b/test/MessageRelayer/RelayRecipientScenarios.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {DefaultGasLimitScenario} from "./GasLimitScenarios.t.sol"; +import {GenericRecipient} from "./GenericRecipient.t.sol"; + +import {InitialState} from "./InitialState.t.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; + +abstract contract RelayRecipientScenarios is DefaultGasLimitScenario {} + +contract RelayRecipientAcceptsMessage is RelayRecipientScenarios {} + +contract RelayRecipientRejectsMessage is RelayRecipientScenarios { + function setUp() public override { + super.setUp(); + to.setSuccess(false); + relayShouldSucceed = false; + claimShouldSucceed = false; + } +} + +contract RelayRecipientReentersReceiveMessage is RelayRecipientScenarios { + function setUp() public override { + super.setUp(); + to.setReentrancyAttack(true); + relayShouldSucceed = false; + claimShouldSucceed = false; + } +} + +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultRelayRecipientScenario is RelayRecipientAcceptsMessage {} diff --git a/test/MessageRelayer/TipRecipientScenarios.t.sol b/test/MessageRelayer/TipRecipientScenarios.t.sol new file mode 100644 index 00000000..9d2e58d6 --- /dev/null +++ b/test/MessageRelayer/TipRecipientScenarios.t.sol @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +import {DefaultRecipientScenario} from "./DepositRecipientScenarios.t.sol"; +import {GenericRecipient} from "./GenericRecipient.t.sol"; + +import {InitialState} from "./InitialState.t.sol"; +import {IETHBridge} from "src/protocol/IETHBridge.sol"; + +abstract contract TipRecipientScenarios is DefaultRecipientScenario { + function test_TipRecipientScenarios_relayMessage_shouldTipCorrectRecipient() public ifRelaySucceeds { + (GenericRecipient correctRecipient,) = _recipients(); + uint256 balanceBefore = address(correctRecipient).balance; + _relayMessage(); + assertEq(address(correctRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); + } + + function test_TipRecipientScenarios_relayMessage_shouldNotTipIncorrectRecipient() public ifRelaySucceeds { + (, GenericRecipient incorrectRecipient) = _recipients(); + uint256 balanceBefore = address(incorrectRecipient).balance; + _relayMessage(); + assertEq(address(incorrectRecipient).balance, balanceBefore, "incorrect tip recipient paid"); + } + + /// @param correctRecipient The user-selected recipient if it is set. The relayer-selected recipient otherwise. + /// @param incorrectRecipient The other recipient, which should not receive the tip. + /// @dev the only tested case where they are the same is when they are both zero (and the transaction reverts) + function _recipients() + internal + view + returns (GenericRecipient correctRecipient, GenericRecipient incorrectRecipient) + { + return userSelectedTipRecipient == GenericRecipient(payable(0)) + ? (relayerSelectedTipRecipient, userSelectedTipRecipient) + : (userSelectedTipRecipient, relayerSelectedTipRecipient); + } +} + +// User-selected tip recipient scenarios + +abstract contract UserSetValidTipRecipient is TipRecipientScenarios { + function test_UserSetValidTipRecipient_claimDeposit_shouldTipUserSelectedRecipient() public ifClaimSucceeds { + uint256 balanceBefore = address(userSelectedTipRecipient).balance; + _claimDeposit(); + assertEq(address(userSelectedTipRecipient).balance, balanceBefore + tip, "tip recipient balance mismatch"); + } +} + +abstract contract UserSetZeroTipRecipient is TipRecipientScenarios { + function setUp() public virtual override { + super.setUp(); + userSelectedTipRecipient = GenericRecipient(payable(0)); + _encodeReceiveCall(); + claimShouldSucceed = false; + } +} + +abstract contract UserSetInvalidTipRecipient is TipRecipientScenarios { + function setUp() public virtual override { + super.setUp(); + userSelectedTipRecipient.setSuccess(false); + relayShouldSucceed = false; + claimShouldSucceed = false; + } +} + +// Relayer-selected tip recipient scenarios + +abstract contract RelayerSetValidTipRecipient is TipRecipientScenarios {} + +abstract contract RelayerSetZeroTipRecipient is TipRecipientScenarios { + function setUp() public virtual override { + super.setUp(); + relayerSelectedTipRecipient = GenericRecipient(payable(0)); + } +} + +abstract contract RelayerSetInvalidTipRecipient is TipRecipientScenarios { + function setUp() public virtual override { + super.setUp(); + relayerSelectedTipRecipient.setSuccess(false); + } +} + +// Combined scenarios + +contract ValidUserTipRecipientOverrulesRelayer is UserSetValidTipRecipient, RelayerSetValidTipRecipient {} + +contract InvalidUserTipRecipientOverrulesRelayer is UserSetInvalidTipRecipient, RelayerSetValidTipRecipient { + function setUp() public override(InitialState, UserSetInvalidTipRecipient) { + super.setUp(); + } +} + +contract ValidRelayerTipRecipientUsed is UserSetZeroTipRecipient, RelayerSetValidTipRecipient { + function setUp() public override(InitialState, UserSetZeroTipRecipient) { + super.setUp(); + } +} + +contract InvalidRelayerTipRecipientUsed is UserSetZeroTipRecipient, RelayerSetInvalidTipRecipient { + function setUp() public override(UserSetZeroTipRecipient, RelayerSetInvalidTipRecipient) { + super.setUp(); + relayShouldSucceed = false; + } +} + +contract NoTipRecipientSet is UserSetZeroTipRecipient, RelayerSetZeroTipRecipient { + function setUp() public override(UserSetZeroTipRecipient, RelayerSetZeroTipRecipient) { + super.setUp(); + relayShouldSucceed = false; + } +} + +// A valid scenario that can be used as a default scenario by unrelated tests. +abstract contract DefaultTipRecipientScenario is ValidUserTipRecipientOverrulesRelayer {} diff --git a/test/SignalService/SampleProof.t.sol b/test/SignalService/SampleProof.t.sol index ea48ebb4..27b2068e 100644 --- a/test/SignalService/SampleProof.t.sol +++ b/test/SignalService/SampleProof.t.sol @@ -19,16 +19,16 @@ contract SampleProof is ISampleProof { signalProof = ISignalService.SignalProof({ accountProof: new bytes[](3), storageProof: new bytes[](1), - stateRoot: bytes32(0xa5d8725608e3d53dd7af5cad105ee1ff73a476255cb488d8ac7a19380618fae7), - blockHash: bytes32(0x20dd339fcac98e78ba268945313e1eba63bfa9c695b862a3440899e0a29f92ec) + stateRoot: bytes32(0x0c4473332e48e4afdd2d5f8abdbbbdc6fdc4b4acf9418e09cec5b13424a73c42), + blockHash: bytes32(0x9808a73c0e2de3265e88e6d45eb51922ee6096cbdb763295226d83c5db0376d7) }); signalProof.accountProof[0] = - hex"f90131a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a015b03aaf615a8c4fe56b96ecbb199a755d5de56f770648d8c1f10cd4673d4817a0533540b3b96b2a8549d5f7bfe3ad0100d9d3cf1df14daef68805e868255d626f8080a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a04a8e98403c548519920cfc70c3e938c753b79e4c4ec8659fe7cd34fbb1f06b3da0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; + hex"f90131a0b91a8b7a7e9d3eab90afd81da3725030742f663c6ed8c26657bf00d842a9f4aaa01689b2a5203afd9ea0a0ca3765e4a538c7176e53eac1f8307a344ffc3c6176558080a097bff13eeebad2cda5e5d75582a6cdb37619ddc67c76d2b7eb06dea2bf8e62c5a006fbfb86513f6f9e20c355094efb981e17ae76c3472ad5e6c5f4a46d256cd0e98080a04b29efa44ecf50c19b34950cf1d0f05e00568bcc873120fbea9a4e8439de0962a0d0a1bfe5b45d2d863a794f016450a4caca04f3b599e8d1652afca8b752935fd880a0bf9b09e442e044778b354abbadb5ec049d7f5e8b585c3966d476c4fbc9a181d28080a0d566c7264cb6d498c47d8fa6127e05996afedf32737a7718081ac0c583e6b559a0e5c557a0ce3894afeb44c37f3d24247f67dc76a174d8cacc360c1210eef60a7680"; signalProof.accountProof[1] = - hex"f85180808080a0cee4f950aa1b1110d20dc6dabcb25bb1ec9924d1f8388d551b63215381bd7f1180808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; + hex"f85180808080a025b14e1ba874b7c7b7cda60e9000747c9529cc103d4400132182dd5854adbd0d80808080a074ae0767a40fc6fff780050f46a50f6b39ca4edb7faa9669108157a1cd96f40980808080808080"; signalProof.accountProof[2] = - hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a0f1c0f9507a3be8b6070650ae4ef4b2ca7c6325a3fac8694bd135680f94ca43cea0da147a8683b303efc72285d333a0683c61d218e18e8dbc84e4cbf5885d4a9229"; + hex"f869a020e659e60b21cc961f64ad47f20523c1d329d4bbda245ef3940a76dc89d0911bb846f8440180a0f1c0f9507a3be8b6070650ae4ef4b2ca7c6325a3fac8694bd135680f94ca43cea03e02a303e0a64f4588d05eaed8454840cdd10fe15cbfae680aa81923c5b8c9f0"; signalProof.storageProof[0] = hex"e3a120c503e3a6183486f40b26b20720d9fb44997221494d172cd9caf4192bc0b6980601"; }