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 }
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 }

View file

@ -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(
&[

View file

@ -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();

View file

@ -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<u8> {
[
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<u8> {
[
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()

View file

@ -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);