From 2f5c0c68d0ed9be95fb5084c88b43a9475d9f610 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Sat, 2 Nov 2024 18:11:09 -0400 Subject: [PATCH] Add selector collisions to Router to make it IRouter compatible --- processor/ethereum/router/Cargo.toml | 2 + processor/ethereum/router/build.rs | 2 +- .../ethereum/router/contracts/Router.sol | 22 +++++-- processor/ethereum/router/src/lib.rs | 63 ++++++++++--------- processor/ethereum/router/src/tests/mod.rs | 12 ++++ 5 files changed, 65 insertions(+), 36 deletions(-) diff --git a/processor/ethereum/router/Cargo.toml b/processor/ethereum/router/Cargo.toml index 132a9fa4..32f112c9 100644 --- a/processor/ethereum/router/Cargo.toml +++ b/processor/ethereum/router/Cargo.toml @@ -20,7 +20,9 @@ workspace = true group = { version = "0.13", default-features = false } alloy-core = { version = "0.8", default-features = false } + alloy-sol-types = { version = "0.8", default-features = false } +alloy-sol-macro = { version = "0.8", default-features = false } alloy-consensus = { version = "0.3", default-features = false } diff --git a/processor/ethereum/router/build.rs b/processor/ethereum/router/build.rs index c931b865..26a2bee6 100644 --- a/processor/ethereum/router/build.rs +++ b/processor/ethereum/router/build.rs @@ -33,7 +33,7 @@ fn main() { ) .unwrap(); - // This cannot be handled with the sol! macro. The Solidity requires an import + // This cannot be handled with the sol! macro. The Router requires an import // https://github.com/alloy-rs/core/issues/602 sol( &[ diff --git a/processor/ethereum/router/contracts/Router.sol b/processor/ethereum/router/contracts/Router.sol index 4be55114..c908cc3e 100644 --- a/processor/ethereum/router/contracts/Router.sol +++ b/processor/ethereum/router/contracts/Router.sol @@ -171,10 +171,14 @@ contract Router { } /// @notice Update the key representing Serai's Ethereum validators - /// @dev This assumes the key is correct. No checks on it are performed + /** + * @dev This assumes the key is correct. No checks on it are performed. + * + * The hex bytes are to cause a collision with `IRouter.updateSeraiKey`. + */ // @param signature The signature by the current key authorizing this update // @param newSeraiKey The key to update to - function updateSeraiKey() external { + function updateSeraiKey5A8542A2() external { (uint256 nonceUsed, bytes memory args,) = verifySignature(); /* We could replace this with a length check (if we don't simply assume the calldata is valid as @@ -341,7 +345,9 @@ contract Router { /// @notice Execute a batch of `OutInstruction`s /** * @dev All `OutInstruction`s in a batch are only for a single coin to simplify handling of the - * fee + * fee. + * + * The hex bytes are to cause a function selector collision with `IRouter.execute`. */ // @param signature The signature by the current key for Serai's Ethereum validators // @param coin The coin all of these `OutInstruction`s are for @@ -349,7 +355,7 @@ contract Router { // @param outs The `OutInstruction`s to act on // Each individual call is explicitly metered to ensure there isn't a DoS here // slither-disable-next-line calls-loop - function execute() external { + function execute4DE42904() external { (uint256 nonceUsed, bytes memory args, bytes32 message) = verifySignature(); (,, address coin, uint256 fee, IRouter.OutInstruction[] memory outs) = abi.decode(args, (bytes32, bytes32, address, uint256, IRouter.OutInstruction[])); @@ -418,10 +424,14 @@ contract Router { } /// @notice Escapes to a new smart contract - /// @dev This should be used upon an invariant being reached or new functionality being needed + /** + * @dev This should be used upon an invariant being reached or new functionality being needed. + * + * The hex bytes are to cause a collision with `IRouter.updateSeraiKey`. + */ // @param signature The signature by the current key for Serai's Ethereum validators // @param escapeTo The address to escape to - function escapeHatch() external { + function escapeHatchDCDD91CC() external { // Verify the signature (, bytes memory args,) = verifySignature(); diff --git a/processor/ethereum/router/src/lib.rs b/processor/ethereum/router/src/lib.rs index b2e78b96..ef5bdbcf 100644 --- a/processor/ethereum/router/src/lib.rs +++ b/processor/ethereum/router/src/lib.rs @@ -28,15 +28,23 @@ use serai_client::networks::ethereum::Address as SeraiAddress; #[expect(clippy::all)] #[expect(clippy::ignored_unit_patterns)] #[expect(clippy::redundant_closure_for_method_calls)] -mod _abi { +pub mod _irouter_abi { + alloy_sol_macro::sol!("contracts/IRouter.sol"); +} + +#[rustfmt::skip] +#[expect(warnings)] +#[expect(needless_pass_by_value)] +#[expect(clippy::all)] +#[expect(clippy::ignored_unit_patterns)] +#[expect(clippy::redundant_closure_for_method_calls)] +mod _router_abi { include!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-router/router.rs")); } + mod abi { - pub use super::_abi::IRouter::{ - Signature, DestinationType, CodeDestination, OutInstruction, SeraiKeyUpdated, InInstruction, - Executed, EscapeHatch, Escaped, - }; - pub use super::_abi::Router::*; + pub use super::_router_abi::IRouter::*; + pub use super::_router_abi::Router::constructorCall; } use abi::{ SeraiKeyUpdated as SeraiKeyUpdatedEvent, InInstruction as InInstructionEvent, @@ -315,23 +323,22 @@ impl Router { /// Get the message to be signed in order to update the key for Serai. pub fn update_serai_key_message(nonce: u64, key: &PublicKey) -> Vec { - [ - abi::updateSeraiKeyCall::SELECTOR.as_slice(), - &(U256::try_from(nonce).unwrap(), U256::ZERO, key.eth_repr()).abi_encode_params(), - ] - .concat() + abi::updateSeraiKeyCall::new(( + abi::Signature { c: U256::try_from(nonce).unwrap().into(), s: U256::ZERO.into() }, + key.eth_repr().into(), + )) + .abi_encode() } /// Construct a transaction to update the key representing Serai. pub fn update_serai_key(&self, public_key: &PublicKey, sig: &Signature) -> TxLegacy { TxLegacy { to: TxKind::Call(self.1), - input: [ - abi::updateSeraiKeyCall::SELECTOR.as_slice(), - &(abi::Signature::from(sig), public_key.eth_repr()).abi_encode_params(), - ] - .concat() - .into(), + input: abi::updateSeraiKeyCall::new(( + abi::Signature::from(sig), + public_key.eth_repr().into(), + )) + .abi_encode().into(), gas_limit: 40_889 * 120 / 100, ..Default::default() } @@ -339,12 +346,13 @@ impl Router { /// Get the message to be signed in order to execute a series of `OutInstruction`s. pub fn execute_message(nonce: u64, coin: Coin, fee: U256, outs: OutInstructions) -> Vec { - [ - abi::executeCall::SELECTOR.as_slice(), - &(U256::try_from(nonce).unwrap(), U256::ZERO, coin.address(), fee, outs.0) - .abi_encode_params(), - ] - .concat() + abi::executeCall::new(( + abi::Signature { c: U256::try_from(nonce).unwrap().into(), s: U256::ZERO.into() }, + coin.address(), + fee, + outs.0, + )) + .abi_encode() } /// Construct a transaction to execute a batch of `OutInstruction`s. @@ -352,12 +360,9 @@ impl Router { let outs_len = outs.0.len(); TxLegacy { to: TxKind::Call(self.1), - input: [ - abi::executeCall::SELECTOR.as_slice(), - &(abi::Signature::from(sig), coin.address(), fee, outs.0).abi_encode_params(), - ] - .concat() - .into(), + input: abi::executeCall::new((abi::Signature::from(sig), coin.address(), fee, outs.0)) + .abi_encode() + .into(), // TODO gas_limit: (45_501 + ((200_000 + 10_000) * u128::try_from(outs_len).unwrap())) * 120 / 100, ..Default::default() diff --git a/processor/ethereum/router/src/tests/mod.rs b/processor/ethereum/router/src/tests/mod.rs index fcd22ec6..78215d95 100644 --- a/processor/ethereum/router/src/tests/mod.rs +++ b/processor/ethereum/router/src/tests/mod.rs @@ -22,6 +22,18 @@ use ethereum_deployer::Deployer; use crate::{Coin, OutInstructions, Router}; +#[test] +fn selector_collisions() { + assert_eq!( + crate::_irouter_abi::IRouter::executeCall::SELECTOR, + crate::_router_abi::Router::execute4DE42904Call::SELECTOR + ); + assert_eq!( + crate::_irouter_abi::IRouter::updateSeraiKeyCall::SELECTOR, + crate::_router_abi::Router::updateSeraiKey5A8542A2Call::SELECTOR + ); +} + pub(crate) fn test_key() -> (Scalar, PublicKey) { loop { let key = Scalar::random(&mut OsRng);