mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Test ERC20 OutInstructions
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -317,6 +317,7 @@ dependencies = [
|
|||||||
"alloy-network-primitives",
|
"alloy-network-primitives",
|
||||||
"alloy-primitives",
|
"alloy-primitives",
|
||||||
"alloy-rpc-client",
|
"alloy-rpc-client",
|
||||||
|
"alloy-rpc-types-debug",
|
||||||
"alloy-rpc-types-eth",
|
"alloy-rpc-types-eth",
|
||||||
"alloy-rpc-types-trace",
|
"alloy-rpc-types-trace",
|
||||||
"alloy-transport",
|
"alloy-transport",
|
||||||
@@ -392,6 +393,16 @@ dependencies = [
|
|||||||
"alloy-serde",
|
"alloy-serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alloy-rpc-types-debug"
|
||||||
|
version = "0.9.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "358d6a8d7340b9eb1a7589a6c1fb00df2c9b26e90737fa5ed0108724dd8dac2c"
|
||||||
|
dependencies = [
|
||||||
|
"alloy-primitives",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloy-rpc-types-eth"
|
name = "alloy-rpc-types-eth"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
|||||||
|
|
||||||
k256 = { version = "0.13", default-features = false, features = ["std"] }
|
k256 = { version = "0.13", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
alloy-provider = { version = "0.9", default-features = false, features = ["trace-api"] }
|
alloy-provider = { version = "0.9", default-features = false, features = ["debug-api", "trace-api"] }
|
||||||
alloy-rpc-client = { version = "0.9", default-features = false }
|
alloy-rpc-client = { version = "0.9", default-features = false }
|
||||||
alloy-node-bindings = { version = "0.9", default-features = false }
|
alloy-node-bindings = { version = "0.9", default-features = false }
|
||||||
|
|
||||||
|
|||||||
@@ -169,8 +169,14 @@ impl Router {
|
|||||||
// Clear the existing return data
|
// Clear the existing return data
|
||||||
interpreter.return_data_buffer.clear();
|
interpreter.return_data_buffer.clear();
|
||||||
|
|
||||||
// If calling an ERC20, trigger the return data's worst-case by returning `true`
|
/*
|
||||||
// (as expected by compliant ERC20s)
|
If calling an ERC20, trigger the return data's worst-case by returning `true`
|
||||||
|
(as expected by compliant ERC20s). Else return none, as we expect none or won't bother
|
||||||
|
copying/decoding the return data.
|
||||||
|
|
||||||
|
This doesn't affect calls to ecrecover as those use STATICCALL and this overrides CALL
|
||||||
|
alone.
|
||||||
|
*/
|
||||||
if Some(address_called) == erc20 {
|
if Some(address_called) == erc20 {
|
||||||
interpreter.return_data_buffer = true.abi_encode().into();
|
interpreter.return_data_buffer = true.abi_encode().into();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ use alloy_consensus::{TxLegacy, Signed};
|
|||||||
use alloy_rpc_types_eth::{BlockNumberOrTag, TransactionInput, TransactionRequest};
|
use alloy_rpc_types_eth::{BlockNumberOrTag, TransactionInput, TransactionRequest};
|
||||||
use alloy_simple_request_transport::SimpleRequest;
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
use alloy_rpc_client::ClientBuilder;
|
use alloy_rpc_client::ClientBuilder;
|
||||||
use alloy_provider::{Provider, RootProvider, ext::TraceApi};
|
use alloy_provider::{
|
||||||
|
Provider, RootProvider,
|
||||||
|
ext::{DebugApi, TraceApi},
|
||||||
|
};
|
||||||
|
|
||||||
use alloy_node_bindings::{Anvil, AnvilInstance};
|
use alloy_node_bindings::{Anvil, AnvilInstance};
|
||||||
|
|
||||||
@@ -120,7 +123,7 @@ impl Test {
|
|||||||
|
|
||||||
async fn new() -> Self {
|
async fn new() -> Self {
|
||||||
// The following is explicitly only evaluated against the cancun network upgrade at this time
|
// The following is explicitly only evaluated against the cancun network upgrade at this time
|
||||||
let anvil = Anvil::new().arg("--hardfork").arg("cancun").spawn();
|
let anvil = Anvil::new().arg("--hardfork").arg("cancun").arg("--tracing").spawn();
|
||||||
|
|
||||||
let provider = Arc::new(RootProvider::new(
|
let provider = Arc::new(RootProvider::new(
|
||||||
ClientBuilder::default().transport(SimpleRequest::new(anvil.endpoint()), true),
|
ClientBuilder::default().transport(SimpleRequest::new(anvil.endpoint()), true),
|
||||||
@@ -435,6 +438,38 @@ impl Test {
|
|||||||
tx.gas_price = 100_000_000_000;
|
tx.gas_price = 100_000_000_000;
|
||||||
tx
|
tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn gas_unused_by_calls(&self, tx: &Signed<TxLegacy>) -> u64 {
|
||||||
|
let mut unused_gas = 0;
|
||||||
|
|
||||||
|
// Handle the difference between the gas limits and gas used values
|
||||||
|
let traces = self.provider.trace_transaction(*tx.hash()).await.unwrap();
|
||||||
|
// Skip the initial call to the Router and the call to ecrecover
|
||||||
|
let mut traces = traces.iter().skip(2);
|
||||||
|
while let Some(trace) = traces.next() {
|
||||||
|
let trace = &trace.trace;
|
||||||
|
// We're tracing the Router's immediate actions, and it doesn't immediately call CREATE
|
||||||
|
// It only makes a call to itself which calls CREATE
|
||||||
|
let gas_provided = trace.action.as_call().as_ref().unwrap().gas;
|
||||||
|
let gas_spent = trace.result.as_ref().unwrap().gas_used();
|
||||||
|
unused_gas += gas_provided - gas_spent;
|
||||||
|
for _ in 0 .. trace.subtraces {
|
||||||
|
// Skip the subtraces for this call (such as CREATE)
|
||||||
|
traces.next().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also handle any refunds
|
||||||
|
{
|
||||||
|
let trace =
|
||||||
|
self.provider.debug_trace_transaction(*tx.hash(), Default::default()).await.unwrap();
|
||||||
|
let refund =
|
||||||
|
trace.try_into_default_frame().unwrap().struct_logs.last().unwrap().refund_counter;
|
||||||
|
unused_gas += refund.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
unused_gas
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -772,11 +807,32 @@ async fn test_eth_address_out_instruction() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_erc20_address_out_instruction() {
|
async fn test_erc20_address_out_instruction() {
|
||||||
todo!("TODO")
|
let mut test = Test::new().await;
|
||||||
/*
|
test.confirm_next_serai_key().await;
|
||||||
|
|
||||||
|
let erc20 = Erc20::deploy(&test).await;
|
||||||
|
let coin = Coin::Erc20(erc20.address());
|
||||||
|
|
||||||
|
let mut rand_address = [0xff; 20];
|
||||||
|
OsRng.fill_bytes(&mut rand_address);
|
||||||
|
let amount_out = U256::from(2);
|
||||||
|
let out_instructions =
|
||||||
|
OutInstructions::from([(SeraiEthereumAddress::Address(rand_address), amount_out)].as_slice());
|
||||||
|
|
||||||
|
let gas = test.router.execute_gas(coin, U256::from(1), &out_instructions);
|
||||||
|
let fee = U256::from(gas);
|
||||||
|
|
||||||
|
// Mint to the Router the necessary amount of the ERC20
|
||||||
|
erc20.mint(&test, test.router.address(), amount_out + fee).await;
|
||||||
|
|
||||||
|
let (tx, gas_used) = test.execute(coin, fee, out_instructions, vec![true]).await;
|
||||||
|
// Uses traces due to the complexity of modeling Erc20::transfer
|
||||||
|
let unused_gas = test.gas_unused_by_calls(&tx).await;
|
||||||
|
assert_eq!(gas_used + unused_gas, gas);
|
||||||
|
|
||||||
assert_eq!(erc20.balance_of(&test, test.router.address()).await, U256::from(0));
|
assert_eq!(erc20.balance_of(&test, test.router.address()).await, U256::from(0));
|
||||||
assert_eq!(erc20.balance_of(&test, test.state.escaped_to.unwrap()).await, amount);
|
assert_eq!(erc20.balance_of(&test, tx.recover_signer().unwrap()).await, U256::from(fee));
|
||||||
*/
|
assert_eq!(erc20.balance_of(&test, rand_address.into()).await, amount_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -806,24 +862,7 @@ async fn test_eth_code_out_instruction() {
|
|||||||
|
|
||||||
// We use call-traces here to determine how much gas was allowed but unused due to the complexity
|
// We use call-traces here to determine how much gas was allowed but unused due to the complexity
|
||||||
// of modeling the call to the Router itself and the following CREATE
|
// of modeling the call to the Router itself and the following CREATE
|
||||||
let mut unused_gas = 0;
|
let unused_gas = test.gas_unused_by_calls(&tx).await;
|
||||||
{
|
|
||||||
let traces = test.provider.trace_transaction(*tx.hash()).await.unwrap();
|
|
||||||
// Skip the call to the Router and the ecrecover
|
|
||||||
let mut traces = traces.iter().skip(2);
|
|
||||||
while let Some(trace) = traces.next() {
|
|
||||||
let trace = &trace.trace;
|
|
||||||
// We're tracing the Router's immediate actions, and it doesn't immediately call CREATE
|
|
||||||
// It only makes a call to itself which calls CREATE
|
|
||||||
let gas_provided = trace.action.as_call().as_ref().unwrap().gas;
|
|
||||||
let gas_spent = trace.result.as_ref().unwrap().gas_used();
|
|
||||||
unused_gas += gas_provided - gas_spent;
|
|
||||||
for _ in 0 .. trace.subtraces {
|
|
||||||
// Skip the subtraces for this call (such as CREATE)
|
|
||||||
traces.next().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert_eq!(gas_used + unused_gas, gas);
|
assert_eq!(gas_used + unused_gas, gas);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
Reference in New Issue
Block a user