Skip to content

Commit

Permalink
Feat(tests): Uniswap multi-hop swap benchmark (#415)
Browse files Browse the repository at this point in the history
  • Loading branch information
birchmd committed Feb 16, 2022
1 parent a922e8e commit ed5238a
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 4 deletions.
46 changes: 46 additions & 0 deletions engine-tests/src/test_utils/uniswap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,16 @@ pub struct ExactOutputSingleParams {
pub price_limit: U256,
}

pub struct ExactInputParams {
pub token_in: Address,
// Vec of poolFee + tokenAddress
pub path: Vec<(u64, Address)>,
pub recipient: Address,
pub deadline: U256,
pub amount_in: U256,
pub amount_out_min: U256,
}

impl SwapRouter {
pub fn exact_output_single(
&self,
Expand Down Expand Up @@ -335,6 +345,42 @@ impl SwapRouter {
data,
}
}

pub fn exact_input(&self, params: ExactInputParams, nonce: U256) -> TransactionLegacy {
let path: Vec<u8> = {
// The encoding here is 32-byte address, then 3-byte (24-bit) fee, alternating
let mut result = Vec::with_capacity(32 + 35 * params.path.len());
result.extend_from_slice(params.token_in.as_bytes());
for (fee, token) in params.path.iter() {
let fee_bytes = fee.to_be_bytes();
result.extend_from_slice(&fee_bytes[5..8]);
result.extend_from_slice(token.as_bytes());
}
result
};
let data = self
.0
.abi
.function("exactInput")
.unwrap()
.encode_input(&[ethabi::Token::Tuple(vec![
ethabi::Token::Bytes(path),
ethabi::Token::Address(params.recipient.raw()),
ethabi::Token::Uint(params.deadline),
ethabi::Token::Uint(params.amount_in),
ethabi::Token::Uint(params.amount_out_min),
])])
.unwrap();

TransactionLegacy {
nonce,
gas_price: Default::default(),
gas_limit: u64::MAX.into(),
to: Some(self.0.address),
value: Default::default(),
data,
}
}
}

fn load_constructor(artifact_path: PathBuf) -> solidity::ContractConstructor {
Expand Down
84 changes: 80 additions & 4 deletions engine-tests/src/tests/uniswap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use crate::test_utils::{
self,
erc20::{ERC20Constructor, ERC20},
uniswap::{
ExactOutputSingleParams, Factory, FactoryConstructor, MintParams, Pool, PositionManager,
PositionManagerConstructor, SwapRouter, SwapRouterConstructor,
ExactInputParams, ExactOutputSingleParams, Factory, FactoryConstructor, MintParams, Pool,
PositionManager, PositionManagerConstructor, SwapRouter, SwapRouterConstructor,
},
AuroraRunner, ExecutionProfile, Signer,
};
Expand All @@ -17,9 +17,33 @@ const INITIAL_NONCE: u64 = 0;
// The "fee" can only be specific values, see
// https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/UniswapV3Factory.sol#L26
const POOL_FEE: u64 = 500;
const MINT_AMOUNT: u64 = 1_000_000_000;
const LIQUIDITY_AMOUNT: u64 = MINT_AMOUNT / 2;
const MINT_AMOUNT: u64 = 1_000_000_000_000;
const LIQUIDITY_AMOUNT: u64 = MINT_AMOUNT / 5;
const OUTPUT_AMOUNT: u64 = LIQUIDITY_AMOUNT / 100;
const INPUT_AMOUNT: u64 = LIQUIDITY_AMOUNT / 100;

#[test]
fn test_uniswap_input_multihop() {
let mut context = UniswapTestContext::new("uniswap");

// evm_gas = 970k
// near total gas = 410 Tgas
// Wish: optimize so that this transaction costs less than 200 Tgas.
// For now we just have to increase the burnt gas limit to make it run to completion.
context.runner.wasm_config.limit_config.max_gas_burnt = 500_000_000_000_000;

let tokens = context.create_tokens(10, MINT_AMOUNT.into());
for (token_a, token_b) in tokens.iter().zip(tokens.iter().skip(1)) {
context.create_pool(token_a, token_b);
context.add_equal_liquidity(LIQUIDITY_AMOUNT.into(), &token_a, &token_b);
}

let (_amount_out, _evm_gas, profile) = context.exact_input(&tokens, INPUT_AMOUNT.into());

println!("{:?}", profile.host_breakdown);
println!("NEAR_GAS_WASM {:?}", profile.wasm_gas());
println!("NEAR_GAS_TOTAL {:?}", profile.all_gas());
}

#[test]
fn test_uniswap_exact_output() {
Expand Down Expand Up @@ -125,6 +149,18 @@ impl UniswapTestContext {
self.runner.wasm_config.regular_op_cost = 0;
}

pub fn create_tokens(&mut self, n: usize, mint_amount: U256) -> Vec<ERC20> {
let names = ('a'..'z').into_iter().map(|c| format!("token_{}", c));
let symbols = ('A'..'Z').into_iter().map(|c| format!("{}{}{}", c, c, c));
let mut result: Vec<ERC20> = names
.zip(symbols)
.take(n)
.map(|(name, symbol)| self.create_token(&name, &symbol, mint_amount))
.collect();
result.sort_by_key(|t| t.0.address);
result
}

pub fn create_token_pair(&mut self, mint_amount: U256) -> (ERC20, ERC20) {
let token_a = self.create_token("token_a", "A", mint_amount);
let token_b = self.create_token("token_b", "B", mint_amount);
Expand Down Expand Up @@ -222,6 +258,46 @@ impl UniswapTestContext {
(result, profile)
}

pub fn exact_input_params(&self, amount_in: U256, token_path: &[ERC20]) -> ExactInputParams {
let path = token_path
.iter()
.skip(1)
.map(|t| (POOL_FEE, t.0.address))
.collect();
ExactInputParams {
token_in: token_path[0].0.address,
path,

recipient: Address::new(H160([0; 20])),
deadline: U256::MAX,
amount_in,
amount_out_min: U256::one(),
}
}

pub fn exact_input(
&mut self,
token_path: &[ERC20],
amount_in: U256,
) -> (U256, u64, ExecutionProfile) {
for token in token_path.iter() {
self.approve_erc20(token, self.swap_router.0.address, U256::MAX);
}
let params = self.exact_input_params(amount_in, token_path);
let swap_router = &self.swap_router;
let (result, profile) = self
.runner
.submit_with_signer_profiled(&mut self.signer, |nonce| {
swap_router.exact_input(params, nonce)
})
.unwrap();
assert!(result.status.is_ok(), "Swap failed");

let evm_gas = result.gas_used;
let amount_out = U256::from_big_endian(&test_utils::unwrap_success(result));
(amount_out, evm_gas, profile)
}

pub fn exact_output_single_params(
&self,
amount_out: U256,
Expand Down

0 comments on commit ed5238a

Please sign in to comment.