Define IRouterWithoutCollisions which Router inherits from

This ensures Router implements most of IRouterWithoutCollisions. It solely
leaves us to confirm Router implements the extensions defined in IRouter.
This commit is contained in:
Luke Parker 2024-11-02 19:03:47 -04:00
parent 2f5c0c68d0
commit 26230377b0
No known key found for this signature in database
5 changed files with 84 additions and 83 deletions

1
Cargo.lock generated
View file

@ -8724,6 +8724,7 @@ dependencies = [
"alloy-rpc-client",
"alloy-rpc-types-eth",
"alloy-simple-request-transport",
"alloy-sol-macro",
"alloy-sol-macro-expander",
"alloy-sol-macro-input",
"alloy-sol-types",

View file

@ -26,13 +26,6 @@ fn main() {
fs::create_dir(&artifacts_path).unwrap();
}
build_solidity_contracts::build(
&["../../../networks/ethereum/schnorr/contracts", "../erc20/contracts", "contracts"],
"contracts",
&artifacts_path,
)
.unwrap();
// This cannot be handled with the sol! macro. The Router requires an import
// https://github.com/alloy-rs/core/issues/602
sol(

View file

@ -1,44 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
/// @title Serai Router
/// @title Serai Router (without functions overriden by selector collisions)
/// @author Luke Parker <lukeparker@serai.exchange>
/// @notice Intakes coins for the Serai network and handles relaying batches of transfers out
interface IRouter {
/// @title A signature
/// @dev Thin wrapper around `c, s` to simplify the API
struct Signature {
bytes32 c;
bytes32 s;
}
/// @title The type of destination
/// @dev A destination is either an address or a blob of code to deploy and call
enum DestinationType {
Address,
Code
}
/// @title A code destination
/**
* @dev If transferring an ERC20 to this destination, it will be transferred to the address the
* code will be deployed to. If transferring ETH, it will be transferred with the deployment of
* the code. `code` is deployed with CREATE (calling its constructor). The entire deployment
* (and associated sandboxing) must consume less than `gasLimit` units of gas or it will revert.
*/
struct CodeDestination {
uint32 gasLimit;
bytes code;
}
/// @title An instruction to transfer coins out
/// @dev Specifies a destination and amount but not the coin as that's assumed to be contextual
struct OutInstruction {
DestinationType destinationType;
bytes destination;
uint256 amount;
}
interface IRouterWithoutCollisions {
/// @notice Emitted when the key for Serai's Ethereum validators is updated
/// @param nonce The nonce consumed to update this key
/// @param key The key updated to
@ -80,12 +46,6 @@ interface IRouter {
/// @notice Escaping when escape hatch wasn't invoked.
error EscapeHatchNotInvoked();
/// @notice Update the key representing Serai's Ethereum validators
/// @dev This assumes the key is correct. No checks on it are performed
/// @param signature The signature by the current key authorizing this update
/// @param newSeraiKey The key to update to
function updateSeraiKey(Signature calldata signature, bytes32 newSeraiKey) external;
/// @notice Transfer coins into Serai with an instruction
/// @param coin The coin to transfer in (address(0) if Ether)
/// @param amount The amount to transfer in (msg.value if Ether)
@ -107,6 +67,67 @@ interface IRouter {
/// @param code The code to execute
function executeArbitraryCode(bytes memory code) external payable;
/// @notice Escape coins after the escape hatch has been invoked
/// @param coin The coin to escape
function escape(address coin) external;
/// @notice Fetch the next nonce to use by an action published to this contract
/// return The next nonce to use by an action published to this contract
function nextNonce() external view returns (uint256);
/// @notice Fetch the current key for Serai's Ethereum validator set
/// @return The current key for Serai's Ethereum validator set
function seraiKey() external view returns (bytes32);
/// @notice Fetch the address escaped to
/// @return The address which was escaped to (address(0) if the escape hatch hasn't been invoked)
function escapedTo() external view returns (address);
}
/// @title Serai Router
/// @author Luke Parker <lukeparker@serai.exchange>
/// @notice Intakes coins for the Serai network and handles relaying batches of transfers out
interface IRouter is IRouterWithoutCollisions {
/// @title A signature
/// @dev Thin wrapper around `c, s` to simplify the API
struct Signature {
bytes32 c;
bytes32 s;
}
/// @title The type of destination
/// @dev A destination is either an address or a blob of code to deploy and call
enum DestinationType {
Address,
Code
}
/// @title A code destination
/**
* @dev If transferring an ERC20 to this destination, it will be transferred to the address the
* code will be deployed to. If transferring ETH, it will be transferred with the deployment of
* the code. `code` is deployed with CREATE (calling its constructor). The entire deployment
* (and associated sandboxing) must consume less than `gasLimit` units of gas or it will revert.
*/
struct CodeDestination {
uint32 gasLimit;
bytes code;
}
/// @title An instruction to transfer coins out
/// @dev Specifies a destination and amount but not the coin as that's assumed to be contextual
struct OutInstruction {
DestinationType destinationType;
bytes destination;
uint256 amount;
}
/// @notice Update the key representing Serai's Ethereum validators
/// @dev This assumes the key is correct. No checks on it are performed
/// @param signature The signature by the current key authorizing this update
/// @param newSeraiKey The key to update to
function updateSeraiKey(Signature calldata signature, bytes32 newSeraiKey) external;
/// @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
@ -128,20 +149,4 @@ interface IRouter {
/// @param signature The signature by the current key for Serai's Ethereum validators
/// @param escapeTo The address to escape to
function escapeHatch(Signature calldata signature, address escapeTo) external;
/// @notice Escape coins after the escape hatch has been invoked
/// @param coin The coin to escape
function escape(address coin) external;
/// @notice Fetch the next nonce to use by an action published to this contract
/// return The next nonce to use by an action published to this contract
function nextNonce() external view returns (uint256);
/// @notice Fetch the current key for Serai's Ethereum validator set
/// @return The current key for Serai's Ethereum validator set
function seraiKey() external view returns (bytes32);
/// @notice Fetch the address escaped to
/// @return The address which was escaped to (address(0) if the escape hatch hasn't been invoked)
function escapedTo() external view returns (address);
}

View file

@ -24,7 +24,7 @@ import "IRouter.sol";
/// @title Serai Router
/// @author Luke Parker <lukeparker@serai.exchange>
/// @notice Intakes coins for the Serai network and handles relaying batches of transfers out
contract Router {
contract Router is IRouterWithoutCollisions {
/**
* @dev The next nonce used to determine the address of contracts deployed with CREATE. This is
* used to predict the addresses of deployed contracts ahead of time.
@ -32,7 +32,7 @@ contract Router {
/*
We don't expose a getter for this as it shouldn't be expected to have any specific value at a
given moment in time. If someone wants to know the address of their deployed contract, they can
have it emit IRouter.an event and verify the emitting contract is the expected one.
have it emit an event and verify the emitting contract is the expected one.
*/
uint256 private _smartContractNonce;
@ -56,7 +56,7 @@ contract Router {
/// @param newSeraiKey The key updated to
function _updateSeraiKey(uint256 nonceUpdatedWith, bytes32 newSeraiKey) private {
_seraiKey = newSeraiKey;
emit IRouter.SeraiKeyUpdated(nonceUpdatedWith, newSeraiKey);
emit SeraiKeyUpdated(nonceUpdatedWith, newSeraiKey);
}
/// @notice The constructor for the relayer
@ -88,7 +88,7 @@ contract Router {
{
// If the escape hatch was triggered, reject further signatures
if (_escapedTo != address(0)) {
revert IRouter.EscapeHatchInvoked();
revert EscapeHatchInvoked();
}
message = msg.data;
@ -100,7 +100,7 @@ contract Router {
(triggering undefined behavior).
*/
if (messageLen < 68) {
revert IRouter.InvalidSignature();
revert InvalidSignature();
}
// Read _nextNonce into memory as the nonce we'll use
@ -127,7 +127,7 @@ contract Router {
// Verify the signature
if (!Schnorr.verify(_seraiKey, messageHash, signatureC, signatureS)) {
revert IRouter.InvalidSignature();
revert InvalidSignature();
}
// Set the next nonce
@ -200,7 +200,7 @@ contract Router {
function inInstruction(address coin, uint256 amount, bytes memory instruction) external payable {
// Check the transfer
if (coin == address(0)) {
if (amount != msg.value) revert IRouter.AmountMismatchesMsgValue();
if (amount != msg.value) revert AmountMismatchesMsgValue();
} else {
(bool success, bytes memory res) = address(coin).call(
abi.encodeWithSelector(IERC20.transferFrom.selector, msg.sender, address(this), amount)
@ -211,7 +211,7 @@ contract Router {
ERC20 contract did in fact return true
*/
bool nonStandardResOrTrue = (res.length == 0) || abi.decode(res, (bool));
if (!(success && nonStandardResOrTrue)) revert IRouter.TransferFromFailed();
if (!(success && nonStandardResOrTrue)) revert TransferFromFailed();
}
/*
@ -236,7 +236,7 @@ contract Router {
It is the Serai network's role not to add support for any non-standard implementations.
*/
emit IRouter.InInstruction(msg.sender, coin, amount, instruction);
emit InInstruction(msg.sender, coin, amount, instruction);
}
/// @dev Perform an ERC20 transfer out
@ -361,7 +361,7 @@ contract Router {
abi.decode(args, (bytes32, bytes32, address, uint256, IRouter.OutInstruction[]));
// TODO: Also include a bit mask here
emit IRouter.Executed(nonceUsed, message);
emit Executed(nonceUsed, message);
/*
Since we don't have a re-entrancy guard, it is possible for instructions from later batches to
@ -427,7 +427,7 @@ contract Router {
/**
* @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`.
* The hex bytes are to cause a collision with `IRouter.escapeHatch`.
*/
// @param signature The signature by the current key for Serai's Ethereum validators
// @param escapeTo The address to escape to
@ -438,7 +438,7 @@ contract Router {
(,, address escapeTo) = abi.decode(args, (bytes32, bytes32, address));
if (escapeTo == address(0)) {
revert IRouter.InvalidEscapeAddress();
revert InvalidEscapeAddress();
}
/*
We want to define the escape hatch so coins here now, and latently received, can be forwarded.
@ -446,21 +446,21 @@ contract Router {
received coins without penalty (if they update the escape hatch after unstaking).
*/
if (_escapedTo != address(0)) {
revert IRouter.EscapeHatchInvoked();
revert EscapeHatchInvoked();
}
_escapedTo = escapeTo;
emit IRouter.EscapeHatch(escapeTo);
emit EscapeHatch(escapeTo);
}
/// @notice Escape coins after the escape hatch has been invoked
/// @param coin The coin to escape
function escape(address coin) external {
if (_escapedTo == address(0)) {
revert IRouter.EscapeHatchNotInvoked();
revert EscapeHatchNotInvoked();
}
emit IRouter.Escaped(coin);
emit Escaped(coin);
// Fetch the amount to escape
uint256 amount = address(this).balance;

View file

@ -28,7 +28,7 @@ use serai_client::networks::ethereum::Address as SeraiAddress;
#[expect(clippy::all)]
#[expect(clippy::ignored_unit_patterns)]
#[expect(clippy::redundant_closure_for_method_calls)]
pub mod _irouter_abi {
mod _irouter_abi {
alloy_sol_macro::sol!("contracts/IRouter.sol");
}
@ -43,6 +43,7 @@ mod _router_abi {
}
mod abi {
pub use super::_router_abi::IRouterWithoutCollisions::*;
pub use super::_router_abi::IRouter::*;
pub use super::_router_abi::Router::constructorCall;
}
@ -338,7 +339,8 @@ impl Router {
abi::Signature::from(sig),
public_key.eth_repr().into(),
))
.abi_encode().into(),
.abi_encode()
.into(),
gas_limit: 40_889 * 120 / 100,
..Default::default()
}