Add selector collisions to Router to make it IRouter compatible

This commit is contained in:
Luke Parker 2024-11-02 18:11:09 -04:00
parent 8de42cc2d4
commit 2f5c0c68d0
No known key found for this signature in database
5 changed files with 65 additions and 36 deletions

View file

@ -20,7 +20,9 @@ workspace = true
group = { version = "0.13", default-features = false } group = { version = "0.13", default-features = false }
alloy-core = { version = "0.8", default-features = false } alloy-core = { version = "0.8", default-features = false }
alloy-sol-types = { 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 } alloy-consensus = { version = "0.3", default-features = false }

View file

@ -33,7 +33,7 @@ fn main() {
) )
.unwrap(); .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 // https://github.com/alloy-rs/core/issues/602
sol( sol(
&[ &[

View file

@ -171,10 +171,14 @@ contract Router {
} }
/// @notice Update the key representing Serai's Ethereum validators /// @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 signature The signature by the current key authorizing this update
// @param newSeraiKey The key to update to // @param newSeraiKey The key to update to
function updateSeraiKey() external { function updateSeraiKey5A8542A2() external {
(uint256 nonceUsed, bytes memory args,) = verifySignature(); (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 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 /// @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 * @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 signature The signature by the current key for Serai's Ethereum validators
// @param coin The coin all of these `OutInstruction`s are for // @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 // @param outs The `OutInstruction`s to act on
// Each individual call is explicitly metered to ensure there isn't a DoS here // Each individual call is explicitly metered to ensure there isn't a DoS here
// slither-disable-next-line calls-loop // slither-disable-next-line calls-loop
function execute() external { function execute4DE42904() external {
(uint256 nonceUsed, bytes memory args, bytes32 message) = verifySignature(); (uint256 nonceUsed, bytes memory args, bytes32 message) = verifySignature();
(,, address coin, uint256 fee, IRouter.OutInstruction[] memory outs) = (,, address coin, uint256 fee, IRouter.OutInstruction[] memory outs) =
abi.decode(args, (bytes32, bytes32, address, uint256, IRouter.OutInstruction[])); abi.decode(args, (bytes32, bytes32, address, uint256, IRouter.OutInstruction[]));
@ -418,10 +424,14 @@ contract Router {
} }
/// @notice Escapes to a new smart contract /// @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 signature The signature by the current key for Serai's Ethereum validators
// @param escapeTo The address to escape to // @param escapeTo The address to escape to
function escapeHatch() external { function escapeHatchDCDD91CC() external {
// Verify the signature // Verify the signature
(, bytes memory args,) = verifySignature(); (, bytes memory args,) = verifySignature();

View file

@ -28,15 +28,23 @@ use serai_client::networks::ethereum::Address as SeraiAddress;
#[expect(clippy::all)] #[expect(clippy::all)]
#[expect(clippy::ignored_unit_patterns)] #[expect(clippy::ignored_unit_patterns)]
#[expect(clippy::redundant_closure_for_method_calls)] #[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")); include!(concat!(env!("OUT_DIR"), "/serai-processor-ethereum-router/router.rs"));
} }
mod abi { mod abi {
pub use super::_abi::IRouter::{ pub use super::_router_abi::IRouter::*;
Signature, DestinationType, CodeDestination, OutInstruction, SeraiKeyUpdated, InInstruction, pub use super::_router_abi::Router::constructorCall;
Executed, EscapeHatch, Escaped,
};
pub use super::_abi::Router::*;
} }
use abi::{ use abi::{
SeraiKeyUpdated as SeraiKeyUpdatedEvent, InInstruction as InInstructionEvent, 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. /// 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<u8> { pub fn update_serai_key_message(nonce: u64, key: &PublicKey) -> Vec<u8> {
[ abi::updateSeraiKeyCall::new((
abi::updateSeraiKeyCall::SELECTOR.as_slice(), abi::Signature { c: U256::try_from(nonce).unwrap().into(), s: U256::ZERO.into() },
&(U256::try_from(nonce).unwrap(), U256::ZERO, key.eth_repr()).abi_encode_params(), key.eth_repr().into(),
] ))
.concat() .abi_encode()
} }
/// Construct a transaction to update the key representing Serai. /// Construct a transaction to update the key representing Serai.
pub fn update_serai_key(&self, public_key: &PublicKey, sig: &Signature) -> TxLegacy { pub fn update_serai_key(&self, public_key: &PublicKey, sig: &Signature) -> TxLegacy {
TxLegacy { TxLegacy {
to: TxKind::Call(self.1), to: TxKind::Call(self.1),
input: [ input: abi::updateSeraiKeyCall::new((
abi::updateSeraiKeyCall::SELECTOR.as_slice(), abi::Signature::from(sig),
&(abi::Signature::from(sig), public_key.eth_repr()).abi_encode_params(), public_key.eth_repr().into(),
] ))
.concat() .abi_encode().into(),
.into(),
gas_limit: 40_889 * 120 / 100, gas_limit: 40_889 * 120 / 100,
..Default::default() ..Default::default()
} }
@ -339,12 +346,13 @@ impl Router {
/// Get the message to be signed in order to execute a series of `OutInstruction`s. /// 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<u8> { pub fn execute_message(nonce: u64, coin: Coin, fee: U256, outs: OutInstructions) -> Vec<u8> {
[ abi::executeCall::new((
abi::executeCall::SELECTOR.as_slice(), abi::Signature { c: U256::try_from(nonce).unwrap().into(), s: U256::ZERO.into() },
&(U256::try_from(nonce).unwrap(), U256::ZERO, coin.address(), fee, outs.0) coin.address(),
.abi_encode_params(), fee,
] outs.0,
.concat() ))
.abi_encode()
} }
/// Construct a transaction to execute a batch of `OutInstruction`s. /// Construct a transaction to execute a batch of `OutInstruction`s.
@ -352,12 +360,9 @@ impl Router {
let outs_len = outs.0.len(); let outs_len = outs.0.len();
TxLegacy { TxLegacy {
to: TxKind::Call(self.1), to: TxKind::Call(self.1),
input: [ input: abi::executeCall::new((abi::Signature::from(sig), coin.address(), fee, outs.0))
abi::executeCall::SELECTOR.as_slice(), .abi_encode()
&(abi::Signature::from(sig), coin.address(), fee, outs.0).abi_encode_params(), .into(),
]
.concat()
.into(),
// TODO // TODO
gas_limit: (45_501 + ((200_000 + 10_000) * u128::try_from(outs_len).unwrap())) * 120 / 100, gas_limit: (45_501 + ((200_000 + 10_000) * u128::try_from(outs_len).unwrap())) * 120 / 100,
..Default::default() ..Default::default()

View file

@ -22,6 +22,18 @@ use ethereum_deployer::Deployer;
use crate::{Coin, OutInstructions, Router}; 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) { pub(crate) fn test_key() -> (Scalar, PublicKey) {
loop { loop {
let key = Scalar::random(&mut OsRng); let key = Scalar::random(&mut OsRng);