mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Test Ether InInstructions
This commit is contained in:
@@ -39,6 +39,7 @@ ethereum-primitives = { package = "serai-processor-ethereum-primitives", path =
|
||||
ethereum-deployer = { package = "serai-processor-ethereum-deployer", path = "../deployer", default-features = false }
|
||||
erc20 = { package = "serai-processor-ethereum-erc20", path = "../erc20", default-features = false }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||
serai-client = { path = "../../../substrate/client", default-features = false, features = ["ethereum"] }
|
||||
|
||||
futures-util = { version = "0.3", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -24,7 +24,10 @@ use alloy_transport::{TransportErrorKind, RpcError};
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
use serai_client::networks::ethereum::Address as SeraiAddress;
|
||||
use scale::Encode;
|
||||
use serai_client::{
|
||||
in_instructions::primitives::Shorthand, networks::ethereum::Address as SeraiAddress,
|
||||
};
|
||||
|
||||
use ethereum_primitives::LogIndex;
|
||||
use ethereum_schnorr::{PublicKey, Signature};
|
||||
@@ -309,6 +312,8 @@ impl Router {
|
||||
}
|
||||
|
||||
/// Construct a transaction to confirm the next key representing Serai.
|
||||
///
|
||||
/// The gas price is not set and is left to the caller.
|
||||
pub fn confirm_next_serai_key(&self, sig: &Signature) -> TxLegacy {
|
||||
TxLegacy {
|
||||
to: TxKind::Call(self.address),
|
||||
@@ -328,6 +333,8 @@ impl Router {
|
||||
}
|
||||
|
||||
/// Construct a transaction to update the key representing Serai.
|
||||
///
|
||||
/// The gas price is not set and is left to the caller.
|
||||
pub fn update_serai_key(&self, public_key: &PublicKey, sig: &Signature) -> TxLegacy {
|
||||
TxLegacy {
|
||||
to: TxKind::Call(self.address),
|
||||
@@ -342,6 +349,37 @@ impl Router {
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a transaction to send coins with an InInstruction to Serai.
|
||||
///
|
||||
/// If coin is an ERC20, this will not create a transaction calling the Router but will create a
|
||||
/// top-level transfer of the ERC20 to the Router. This avoids needing to call `approve` before
|
||||
/// publishing the transaction calling the Router.
|
||||
///
|
||||
/// The gas limit and gas price are not set and are left to the caller.
|
||||
pub fn in_instruction(&self, coin: Coin, amount: U256, in_instruction: &Shorthand) -> TxLegacy {
|
||||
match coin {
|
||||
Coin::Ether => TxLegacy {
|
||||
to: self.address.into(),
|
||||
input: abi::inInstructionCall::new((coin.into(), amount, in_instruction.encode().into()))
|
||||
.abi_encode()
|
||||
.into(),
|
||||
value: amount,
|
||||
..Default::default()
|
||||
},
|
||||
Coin::Erc20(erc20) => TxLegacy {
|
||||
to: erc20.into(),
|
||||
input: erc20::transferWithInInstructionCall::new((
|
||||
self.address,
|
||||
amount,
|
||||
in_instruction.encode().into(),
|
||||
))
|
||||
.abi_encode()
|
||||
.into(),
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the message to be signed in order to execute a series of `OutInstruction`s.
|
||||
pub fn execute_message(
|
||||
chain_id: U256,
|
||||
@@ -360,6 +398,8 @@ impl Router {
|
||||
}
|
||||
|
||||
/// Construct a transaction to execute a batch of `OutInstruction`s.
|
||||
///
|
||||
/// The gas limit and gas price are not set and are left to the caller.
|
||||
pub fn execute(&self, coin: Coin, fee: U256, outs: OutInstructions, sig: &Signature) -> TxLegacy {
|
||||
// TODO
|
||||
let gas_limit = Self::EXECUTE_BASE_GAS + outs.0.iter().map(|_| 200_000 + 10_000).sum::<u64>();
|
||||
@@ -383,6 +423,8 @@ impl Router {
|
||||
}
|
||||
|
||||
/// Construct a transaction to trigger the escape hatch.
|
||||
///
|
||||
/// The gas price is not set and is left to the caller.
|
||||
pub fn escape_hatch(&self, escape_to: Address, sig: &Signature) -> TxLegacy {
|
||||
TxLegacy {
|
||||
to: TxKind::Call(self.address),
|
||||
@@ -393,6 +435,8 @@ impl Router {
|
||||
}
|
||||
|
||||
/// Construct a transaction to escape coins via the escape hatch.
|
||||
///
|
||||
/// The gas limit and gas price are not set and are left to the caller.
|
||||
pub fn escape(&self, coin: Coin) -> TxLegacy {
|
||||
TxLegacy {
|
||||
to: TxKind::Call(self.address),
|
||||
|
||||
@@ -18,6 +18,14 @@ use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
use alloy_node_bindings::{Anvil, AnvilInstance};
|
||||
|
||||
use scale::Encode;
|
||||
use serai_client::{
|
||||
primitives::SeraiAddress,
|
||||
in_instructions::primitives::{
|
||||
InInstruction as SeraiInInstruction, RefundableInInstruction, Shorthand,
|
||||
},
|
||||
};
|
||||
|
||||
use ethereum_primitives::LogIndex;
|
||||
use ethereum_schnorr::{PublicKey, Signature};
|
||||
use ethereum_deployer::Deployer;
|
||||
@@ -26,7 +34,7 @@ use crate::{
|
||||
_irouter_abi::IRouterWithoutCollisions::{
|
||||
self as IRouter, IRouterWithoutCollisionsErrors as IRouterErrors,
|
||||
},
|
||||
Coin, OutInstructions, Router, Executed, Escape,
|
||||
Coin, InInstruction, OutInstructions, Router, Executed, Escape,
|
||||
};
|
||||
|
||||
mod constants;
|
||||
@@ -165,6 +173,8 @@ impl Test {
|
||||
let tx = ethereum_primitives::deterministically_sign(tx);
|
||||
let receipt = ethereum_test_primitives::publish_tx(&self.provider, tx.clone()).await;
|
||||
assert!(receipt.status());
|
||||
// Only check the gas is equal when writing to a previously unallocated storage slot, as this
|
||||
// is the highest possible gas cost and what the constant is derived from
|
||||
if self.state.key.is_none() {
|
||||
assert_eq!(
|
||||
CalldataAgnosticGas::calculate(tx.tx(), receipt.gas_used),
|
||||
@@ -231,6 +241,21 @@ impl Test {
|
||||
self.verify_state().await;
|
||||
}
|
||||
|
||||
fn eth_in_instruction_tx(&self) -> (Coin, U256, Shorthand, TxLegacy) {
|
||||
let coin = Coin::Ether;
|
||||
let amount = U256::from(1);
|
||||
let shorthand = Shorthand::Raw(RefundableInInstruction {
|
||||
origin: None,
|
||||
instruction: SeraiInInstruction::Transfer(SeraiAddress([0xff; 32])),
|
||||
});
|
||||
|
||||
let mut tx = self.router.in_instruction(coin, amount, &shorthand);
|
||||
tx.gas_limit = 1_000_000;
|
||||
tx.gas_price = 100_000_000_000;
|
||||
|
||||
(coin, amount, shorthand, tx)
|
||||
}
|
||||
|
||||
fn escape_hatch_tx(&self, escape_to: Address) -> TxLegacy {
|
||||
let msg = Router::escape_hatch_message(self.chain_id, self.state.next_nonce, escape_to);
|
||||
let sig = sign(self.state.key.unwrap(), &msg);
|
||||
@@ -297,7 +322,43 @@ async fn test_update_serai_key() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_eth_in_instruction() {
|
||||
todo!("TODO")
|
||||
let mut test = Test::new().await;
|
||||
test.confirm_next_serai_key().await;
|
||||
|
||||
let (coin, amount, shorthand, tx) = test.eth_in_instruction_tx();
|
||||
|
||||
// This should fail if the value mismatches the amount
|
||||
{
|
||||
let mut tx = tx.clone();
|
||||
tx.value = U256::ZERO;
|
||||
assert!(matches!(
|
||||
test.call_and_decode_err(tx).await,
|
||||
IRouterErrors::AmountMismatchesMsgValue(IRouter::AmountMismatchesMsgValue {})
|
||||
));
|
||||
}
|
||||
|
||||
let tx = ethereum_primitives::deterministically_sign(tx);
|
||||
let receipt = ethereum_test_primitives::publish_tx(&test.provider, tx.clone()).await;
|
||||
assert!(receipt.status());
|
||||
|
||||
let block = receipt.block_number.unwrap();
|
||||
let in_instructions =
|
||||
test.router.in_instructions_unordered(block, block, &HashSet::new()).await.unwrap();
|
||||
assert_eq!(in_instructions.len(), 1);
|
||||
assert_eq!(
|
||||
in_instructions[0],
|
||||
InInstruction {
|
||||
id: LogIndex {
|
||||
block_hash: *receipt.block_hash.unwrap(),
|
||||
index_within_block: receipt.inner.logs()[0].log_index.unwrap(),
|
||||
},
|
||||
transaction_hash: **tx.hash(),
|
||||
from: tx.recover_signer().unwrap(),
|
||||
coin,
|
||||
amount,
|
||||
data: shorthand.encode(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -379,7 +440,10 @@ async fn test_escape_hatch() {
|
||||
test.call_and_decode_err(test.confirm_next_serai_key_tx()).await,
|
||||
IRouterErrors::EscapeHatchInvoked(IRouter::EscapeHatchInvoked {})
|
||||
));
|
||||
// TODO inInstruction
|
||||
assert!(matches!(
|
||||
test.call_and_decode_err(test.eth_in_instruction_tx().3).await,
|
||||
IRouterErrors::EscapeHatchInvoked(IRouter::EscapeHatchInvoked {})
|
||||
));
|
||||
// TODO execute
|
||||
// We reject further attempts to update the escape hatch to prevent the last key from being
|
||||
// able to switch from the honest escape hatch to siphoning via a malicious escape hatch (such
|
||||
|
||||
Reference in New Issue
Block a user