4 Commits

Author SHA1 Message Date
Luke Parker
26230377b0 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.
2024-11-02 19:10:39 -04:00
Luke Parker
2f5c0c68d0 Add selector collisions to Router to make it IRouter compatible 2024-11-02 18:13:02 -04:00
Luke Parker
8de42cc2d4 Add IRouter 2024-11-02 13:19:07 -04:00
Luke Parker
cf4123b0f8 Update how signatures are handled by the Router 2024-11-02 10:47:09 -04:00
7 changed files with 362 additions and 171 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

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

@@ -26,17 +26,14 @@ fn main() {
fs::create_dir(&artifacts_path).unwrap();
}
build_solidity_contracts::build(
&["../../../networks/ethereum/schnorr/contracts", "../erc20/contracts"],
"contracts",
&artifacts_path,
)
.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(
&["../../../networks/ethereum/schnorr/contracts/Schnorr.sol", "contracts/Router.sol"],
&[
"../../../networks/ethereum/schnorr/contracts/Schnorr.sol",
"contracts/IRouter.sol",
"contracts/Router.sol",
],
&(artifacts_path + "/router.rs"),
);
}

View File

@@ -0,0 +1,152 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
/// @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 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
event SeraiKeyUpdated(uint256 indexed nonce, bytes32 indexed key);
/// @notice Emitted when an InInstruction occurs
/// @param from The address which called `inInstruction` and caused this event to be emitted
/// @param coin The coin transferred in
/// @param amount The amount of the coin transferred in
/// @param instruction The Shorthand-encoded InInstruction for Serai to decode and handle
event InInstruction(
address indexed from, address indexed coin, uint256 amount, bytes instruction
);
/// @notice Emitted when a batch of `OutInstruction`s occurs
/// @param nonce The nonce consumed to execute this batch of transactions
/// @param messageHash The hash of the message signed for the executed batch
event Executed(uint256 indexed nonce, bytes32 indexed messageHash);
/// @notice Emitted when `escapeHatch` is invoked
/// @param escapeTo The address to escape to
event EscapeHatch(address indexed escapeTo);
/// @notice Emitted when coins escape through the escape hatch
/// @param coin The coin which escaped
event Escaped(address indexed coin);
/// @notice The contract has had its escape hatch invoked and won't accept further actions
error EscapeHatchInvoked();
/// @notice The signature was invalid
error InvalidSignature();
/// @notice The amount specified didn't match `msg.value`
error AmountMismatchesMsgValue();
/// @notice The call to an ERC20's `transferFrom` failed
error TransferFromFailed();
/// @notice An invalid address to escape to was specified.
error InvalidEscapeAddress();
/// @notice Escaping when escape hatch wasn't invoked.
error EscapeHatchNotInvoked();
/// @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)
/**
* @param instruction The Shorthand-encoded InInstruction for Serai to associate with this
* transfer in
*/
// Re-entrancy doesn't bork this function
// slither-disable-next-line reentrancy-events
function inInstruction(address coin, uint256 amount, bytes memory instruction) external payable;
/// @notice Execute some arbitrary code within a secure sandbox
/**
* @dev This performs sandboxing by deploying this code with `CREATE`. This is an external
* function as we can't meter `CREATE`/internal functions. We work around this by calling this
* function with `CALL` (which we can meter). This does forward `msg.value` to the newly
* deployed contract.
*/
/// @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
* fee
*/
/// @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 fee The fee to pay (in coin) to the caller for their relaying of this batch
/// @param outs The `OutInstruction`s to act on
function execute(
Signature calldata signature,
address coin,
uint256 fee,
OutInstruction[] calldata outs
) external;
/// @notice Escapes to a new smart contract
/// @dev This should be used upon an invariant being reached or new functionality being needed
/// @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;
}

View File

@@ -1,12 +1,12 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.26;
// TODO: MIT licensed interface
import "IERC20.sol";
import "Schnorr.sol";
import "IRouter.sol";
/*
The Router directly performs low-level calls in order to fine-tune the gas settings. Since this
contract is meant to relay an entire batch of transactions, the ability to exactly meter
@@ -24,7 +24,7 @@ import "Schnorr.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.
@@ -51,151 +51,141 @@ contract Router {
/// @dev The address escaped to
address private _escapedTo;
/// @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;
}
/// @title A signature
/// @dev Thin wrapper around `c, s` to simplify the API
struct Signature {
bytes32 c;
bytes32 s;
}
/// @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
event SeraiKeyUpdated(uint256 indexed nonce, bytes32 indexed key);
/// @notice Emitted when an InInstruction occurs
/// @param from The address which called `inInstruction` and caused this event to be emitted
/// @param coin The coin transferred in
/// @param amount The amount of the coin transferred in
/// @param instruction The Shorthand-encoded InInstruction for Serai to decode and handle
event InInstruction(
address indexed from, address indexed coin, uint256 amount, bytes instruction
);
/// @notice Emitted when a batch of `OutInstruction`s occurs
/// @param nonce The nonce consumed to execute this batch of transactions
/// @param messageHash The hash of the message signed for the executed batch
event Executed(uint256 indexed nonce, bytes32 indexed messageHash);
/// @notice Emitted when `escapeHatch` is invoked
/// @param escapeTo The address to escape to
event EscapeHatch(address indexed escapeTo);
/// @notice Emitted when coins escape through the escape hatch
/// @param coin The coin which escaped
event Escaped(address indexed coin);
/// @notice The contract has had its escape hatch invoked and won't accept further actions
error EscapeHatchInvoked();
/// @notice The signature was invalid
error InvalidSignature();
/// @notice The amount specified didn't match `msg.value`
error AmountMismatchesMsgValue();
/// @notice The call to an ERC20's `transferFrom` failed
error TransferFromFailed();
/// @notice An invalid address to escape to was specified.
error InvalidEscapeAddress();
/// @notice Escaping when escape hatch wasn't invoked.
error EscapeHatchNotInvoked();
/**
* @dev Updates the Serai key at the end of the current function. Executing at the end of the
* current function allows verifying a signature with the current key. This does not update
* `_nextNonce`
*/
/// @dev Updates the Serai key. This does not update `_nextNonce`
/// @param nonceUpdatedWith The nonce used to update the key
/// @param newSeraiKey The key updated to
modifier updateSeraiKeyAtEndOfFn(uint256 nonceUpdatedWith, bytes32 newSeraiKey) {
// Run the function itself
_;
// Update the key
function _updateSeraiKey(uint256 nonceUpdatedWith, bytes32 newSeraiKey) private {
_seraiKey = newSeraiKey;
emit SeraiKeyUpdated(nonceUpdatedWith, newSeraiKey);
}
/// @notice The constructor for the relayer
/// @param initialSeraiKey The initial key for Serai's Ethereum validators
constructor(bytes32 initialSeraiKey) updateSeraiKeyAtEndOfFn(0, initialSeraiKey) {
constructor(bytes32 initialSeraiKey) {
// Nonces are incremented by 1 upon account creation, prior to any code execution, per EIP-161
// This is incompatible with any networks which don't have their nonces start at 0
_smartContractNonce = 1;
// We consumed nonce 0 when setting the initial Serai key
// Set the Serai key
_updateSeraiKey(0, initialSeraiKey);
// We just consumed nonce 0 when setting the initial Serai key
_nextNonce = 1;
// We haven't escaped to any address yet
_escapedTo = address(0);
}
/// @dev Verify a signature
/// @param message The message to pass to the Schnorr contract
/// @param signature The signature by the current key for this message
function verifySignature(bytes32 message, Signature calldata signature) private {
/**
* @dev
* Verify a signature of the calldata, placed immediately after the function selector. The
* calldata should be signed with the nonce taking the place of the signature's commitment to
* its nonce, and the signature solution zeroed.
*/
function verifySignature()
private
returns (uint256 nonceUsed, bytes memory message, bytes32 messageHash)
{
// If the escape hatch was triggered, reject further signatures
if (_escapedTo != address(0)) {
revert EscapeHatchInvoked();
}
// Verify the signature
if (!Schnorr.verify(_seraiKey, message, signature.c, signature.s)) {
message = msg.data;
uint256 messageLen = message.length;
/*
function selector, signature
This check means we don't read memory, and as we attempt to clear portions, write past it
(triggering undefined behavior).
*/
if (messageLen < 68) {
revert InvalidSignature();
}
// Read _nextNonce into memory as the nonce we'll use
nonceUsed = _nextNonce;
// Declare memory to copy the signature out to
bytes32 signatureC;
bytes32 signatureS;
// slither-disable-next-line assembly
assembly {
// Read the signature (placed after the function signature)
signatureC := mload(add(message, 36))
signatureS := mload(add(message, 68))
// Overwrite the signature challenge with the nonce
mstore(add(message, 36), nonceUsed)
// Overwrite the signature response with 0
mstore(add(message, 68), 0)
// Calculate the message hash
messageHash := keccak256(add(message, 32), messageLen)
}
// Verify the signature
if (!Schnorr.verify(_seraiKey, messageHash, signatureC, signatureS)) {
revert InvalidSignature();
}
// Set the next nonce
unchecked {
_nextNonce++;
_nextNonce = nonceUsed + 1;
}
/*
Advance the message past the function selector, enabling decoding the arguments. Ideally, we'd
also advance past the signature (to simplify decoding arguments and save some memory). This
would transfrom message from:
message (pointer)
v
------------------------------------------------------------
| 32-byte length | 4-byte selector | Signature | Arguments |
------------------------------------------------------------
to:
message (pointer)
v
----------------------------------------------
| Junk 68 bytes | 32-byte length | Arguments |
----------------------------------------------
Unfortunately, doing so corrupts the offsets defined within the ABI itself. We settle for a
transform to:
message (pointer)
v
---------------------------------------------------------
| Junk 4 bytes | 32-byte length | Signature | Arguments |
---------------------------------------------------------
*/
// slither-disable-next-line assembly
assembly {
message := add(message, 4)
mstore(message, sub(messageLen, 4))
}
}
/// @notice Update the key representing Serai's Ethereum validators
/// @dev This assumes the key is correct. No checks on it are performed
/// @param newSeraiKey The key to update to
/// @param signature The signature by the current key authorizing this update
function updateSeraiKey(bytes32 newSeraiKey, Signature calldata signature)
external
updateSeraiKeyAtEndOfFn(_nextNonce, newSeraiKey)
{
/**
* @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 updateSeraiKey5A8542A2() external {
(uint256 nonceUsed, bytes memory args,) = verifySignature();
/*
This DST needs a length prefix as well to prevent DSTs potentially being substrings of each
other, yet this is fine for our well-defined, extremely-limited use.
We don't encode the chain ID as Serai generates independent keys for each integration. If
Ethereum L2s are integrated, and they reuse the Ethereum validator set, we would use the
existing Serai key yet we'd apply an off-chain derivation scheme to bind it to specific
networks. This also lets Serai identify EVMs per however it wants, solving the edge case where
two instances of the EVM share a chain ID for whatever horrific reason.
This uses encodePacked as all items present here are of fixed length.
We could replace this with a length check (if we don't simply assume the calldata is valid as
it was properly signed) + mload to save 24 gas but it's not worth the complexity.
*/
bytes32 message = keccak256(abi.encodePacked("updateSeraiKey", _nextNonce, newSeraiKey));
verifySignature(message, signature);
(,, bytes32 newSeraiKey) = abi.decode(args, (bytes32, bytes32, bytes32));
_updateSeraiKey(nonceUsed, newSeraiKey);
}
/// @notice Transfer coins into Serai with an instruction
@@ -355,28 +345,23 @@ 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 coin The coin all of these `OutInstruction`s are for
/// @param fee The fee to pay (in coin) to the caller for their relaying of this batch
/// @param outs The `OutInstruction`s to act on
/// @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 fee The fee to pay (in coin) to the caller for their relaying of this batch
// @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(
address coin,
uint256 fee,
OutInstruction[] calldata outs,
Signature calldata signature
) external {
// Verify the signature
// This uses `encode`, not `encodePacked`, as `outs` is of variable length
// TODO: Use a custom encode in verifySignature here with assembly (benchmarking before/after)
bytes32 message = keccak256(abi.encode("execute", _nextNonce, coin, fee, outs));
verifySignature(message, signature);
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[]));
// TODO: Also include a bit mask here
emit Executed(_nextNonce, message);
emit Executed(nonceUsed, message);
/*
Since we don't have a re-entrancy guard, it is possible for instructions from later batches to
@@ -389,9 +374,9 @@ contract Router {
// slither-disable-next-line reentrancy-events
for (uint256 i = 0; i < outs.length; i++) {
// If the destination is an address, we perform a direct transfer
if (outs[i].destinationType == DestinationType.Address) {
if (outs[i].destinationType == IRouter.DestinationType.Address) {
/*
This may cause a revert if the destination isn't actually a valid address. Serai is
This may cause a revert if the destination isn't actually a valid address. Serai is
trusted to not pass a malformed destination, yet if it ever did, it could simply re-sign a
corrected batch using this nonce.
*/
@@ -415,7 +400,8 @@ contract Router {
erc20TransferOut(nextAddress, coin, outs[i].amount);
}
(CodeDestination memory destination) = abi.decode(outs[i].destination, (CodeDestination));
(IRouter.CodeDestination memory destination) =
abi.decode(outs[i].destination, (IRouter.CodeDestination));
/*
Perform the deployment with the defined gas budget.
@@ -438,10 +424,19 @@ contract Router {
}
/// @notice Escapes to a new smart contract
/// @dev This should be used upon an invariant being reached or new functionality being needed
/// @param escapeTo The address to escape to
/// @param signature The signature by the current key for Serai's Ethereum validators
function escapeHatch(address escapeTo, Signature calldata signature) external {
/**
* @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.escapeHatch`.
*/
// @param signature The signature by the current key for Serai's Ethereum validators
// @param escapeTo The address to escape to
function escapeHatchDCDD91CC() external {
// Verify the signature
(, bytes memory args,) = verifySignature();
(,, address escapeTo) = abi.decode(args, (bytes32, bytes32, address));
if (escapeTo == address(0)) {
revert InvalidEscapeAddress();
}
@@ -454,10 +449,6 @@ contract Router {
revert EscapeHatchInvoked();
}
// Verify the signature
bytes32 message = keccak256(abi.encodePacked("escapeHatch", _nextNonce, escapeTo));
verifySignature(message, signature);
_escapedTo = escapeTo;
emit EscapeHatch(escapeTo);
}

View File

@@ -28,10 +28,25 @@ 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 {
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"));
}
use _abi::Router as abi;
mod abi {
pub use super::_router_abi::IRouterWithoutCollisions::*;
pub use super::_router_abi::IRouter::*;
pub use super::_router_abi::Router::constructorCall;
}
use abi::{
SeraiKeyUpdated as SeraiKeyUpdatedEvent, InInstruction as InInstructionEvent,
Executed as ExecutedEvent,
@@ -309,26 +324,37 @@ 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> {
("updateSeraiKey", U256::try_from(nonce).expect("couldn't convert u64 to u256"), key.eth_repr())
.abi_encode_packed()
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::new((public_key.eth_repr().into(), sig.into()))
.abi_encode()
.into(),
gas_limit: 40748 * 120 / 100,
input: abi::updateSeraiKeyCall::new((
abi::Signature::from(sig),
public_key.eth_repr().into(),
))
.abi_encode()
.into(),
gas_limit: 40_889 * 120 / 100,
..Default::default()
}
}
/// 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> {
("execute".to_string(), U256::try_from(nonce).unwrap(), coin.address(), fee, outs.0)
.abi_encode_sequence()
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.
@@ -336,9 +362,11 @@ impl Router {
let outs_len = outs.0.len();
TxLegacy {
to: TxKind::Call(self.1),
input: abi::executeCall::new((coin.address(), fee, outs.0, sig.into())).abi_encode().into(),
input: abi::executeCall::new((abi::Signature::from(sig), coin.address(), fee, outs.0))
.abi_encode()
.into(),
// TODO
gas_limit: 100_000 + ((200_000 + 10_000) * u128::try_from(outs_len).unwrap()),
gas_limit: (45_501 + ((200_000 + 10_000) * u128::try_from(outs_len).unwrap())) * 120 / 100,
..Default::default()
}
}

View File

@@ -10,7 +10,7 @@ use alloy_sol_types::SolCall;
use alloy_consensus::TxLegacy;
use alloy_rpc_types_eth::BlockNumberOrTag;
use alloy_rpc_types_eth::{BlockNumberOrTag, TransactionReceipt};
use alloy_simple_request_transport::SimpleRequest;
use alloy_rpc_client::ClientBuilder;
use alloy_provider::RootProvider;
@@ -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);
@@ -154,8 +166,16 @@ async fn test_erc20_in_instruction() {
todo!("TODO")
}
async fn publish_outs(key: (Scalar, PublicKey), nonce: u64, coin: Coin, fee: U256, outs: OutInstructions) -> TransactionReceipt {
let msg = Router::execute_message(nonce, coin, fee, instructions.clone());
async fn publish_outs(
provider: &RootProvider<SimpleRequest>,
router: &Router,
key: (Scalar, PublicKey),
nonce: u64,
coin: Coin,
fee: U256,
outs: OutInstructions,
) -> TransactionReceipt {
let msg = Router::execute_message(nonce, coin, fee, outs.clone());
let nonce = Scalar::random(&mut OsRng);
let c = Signature::challenge(ProjectivePoint::GENERATOR * nonce, &key.1, &msg);
@@ -163,10 +183,10 @@ async fn publish_outs(key: (Scalar, PublicKey), nonce: u64, coin: Coin, fee: U25
let sig = Signature::new(c, s).unwrap();
let mut tx = router.execute(coin, fee, instructions, &sig);
let mut tx = router.execute(coin, fee, outs, &sig);
tx.gas_price = 100_000_000_000u128;
let tx = ethereum_primitives::deterministically_sign(&tx);
ethereum_test_primitives::publish_tx(&provider, tx).await
ethereum_test_primitives::publish_tx(provider, tx).await
}
#[tokio::test]
@@ -182,7 +202,7 @@ async fn test_eth_address_out_instruction() {
ethereum_test_primitives::fund_account(&provider, router.address(), amount).await;
let instructions = OutInstructions::from([].as_slice());
let receipt = publish_outs(key, 1, Coin::Ether, fee, instructions);
let receipt = publish_outs(&provider, &router, key, 1, Coin::Ether, fee, instructions).await;
assert!(receipt.status());
println!("empty execute used {} gas:", receipt.gas_used);