diff --git a/Cargo.lock b/Cargo.lock index 0550b05e..df7d578e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/processor/ethereum/router/build.rs b/processor/ethereum/router/build.rs index 26a2bee6..dd52985d 100644 --- a/processor/ethereum/router/build.rs +++ b/processor/ethereum/router/build.rs @@ -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( diff --git a/processor/ethereum/router/contracts/IRouter.sol b/processor/ethereum/router/contracts/IRouter.sol index 347553c1..91bedac5 100644 --- a/processor/ethereum/router/contracts/IRouter.sol +++ b/processor/ethereum/router/contracts/IRouter.sol @@ -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 /// @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 +/// @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); } diff --git a/processor/ethereum/router/contracts/Router.sol b/processor/ethereum/router/contracts/Router.sol index c908cc3e..e8f54653 100644 --- a/processor/ethereum/router/contracts/Router.sol +++ b/processor/ethereum/router/contracts/Router.sol @@ -24,7 +24,7 @@ import "IRouter.sol"; /// @title Serai Router /// @author Luke Parker /// @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; diff --git a/processor/ethereum/router/src/lib.rs b/processor/ethereum/router/src/lib.rs index ef5bdbcf..71b4bca4 100644 --- a/processor/ethereum/router/src/lib.rs +++ b/processor/ethereum/router/src/lib.rs @@ -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() }