mirror of
https://github.com/serai-dex/serai.git
synced 2025-04-22 22:18:15 +00:00
Test ERC20 OutInstructions
This commit is contained in:
parent
5164a710a2
commit
e742a6b0ec
4 changed files with 83 additions and 27 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -317,6 +317,7 @@ dependencies = [
|
|||
"alloy-network-primitives",
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-client",
|
||||
"alloy-rpc-types-debug",
|
||||
"alloy-rpc-types-eth",
|
||||
"alloy-rpc-types-trace",
|
||||
"alloy-transport",
|
||||
|
@ -392,6 +393,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "alloy-rpc-types-eth"
|
||||
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"] }
|
||||
|
||||
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-node-bindings = { version = "0.9", default-features = false }
|
||||
|
||||
|
|
|
@ -169,8 +169,14 @@ impl Router {
|
|||
// Clear the existing return data
|
||||
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 {
|
||||
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_simple_request_transport::SimpleRequest;
|
||||
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};
|
||||
|
||||
|
@ -120,7 +123,7 @@ impl Test {
|
|||
|
||||
async fn new() -> Self {
|
||||
// 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(
|
||||
ClientBuilder::default().transport(SimpleRequest::new(anvil.endpoint()), true),
|
||||
|
@ -435,6 +438,38 @@ impl Test {
|
|||
tx.gas_price = 100_000_000_000;
|
||||
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]
|
||||
|
@ -772,11 +807,32 @@ async fn test_eth_address_out_instruction() {
|
|||
|
||||
#[tokio::test]
|
||||
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.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]
|
||||
|
@ -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
|
||||
// of modeling the call to the Router itself and the following CREATE
|
||||
let mut unused_gas = 0;
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
let unused_gas = test.gas_unused_by_calls(&tx).await;
|
||||
assert_eq!(gas_used + unused_gas, gas);
|
||||
|
||||
assert_eq!(
|
||||
|
|
Loading…
Reference in a new issue