arbiter-core 0.11.0

Allowing smart contract developers to do simulation driven development via an EVM emulator
Documentation
use std::fs::{self, File};

use ethers::{
    prelude::Middleware,
    types::{I256 as eI256, U256 as eU256},
};
use tracing_subscriber::{fmt, EnvFilter};
include!("common.rs");

#[tokio::test]
async fn arbiter_math() {
    let (_env, client) = startup();
    let arbiter_math = deploy_arbiter_math(client).await;

    // Test the cdf function
    let cdf_output = arbiter_math.cdf(eI256::from(1)).call().await.unwrap();
    println!("cdf(1) = {}", cdf_output);
    assert_eq!(cdf_output, eI256::from(500000000000000000u64));

    // Test the pdf function
    let pdf_output = arbiter_math.pdf(eI256::from(1)).call().await.unwrap();
    println!("pdf(1) = {}", pdf_output);
    assert_eq!(pdf_output, eI256::from(398942280401432678u64));

    // Test the ppf function.
    let ppf_output = arbiter_math.ppf(eI256::from(1)).call().await.unwrap();
    println!("ppf(1) = {}", ppf_output);
    assert_eq!(ppf_output, eI256::from(-8710427241990476442_i128));

    // Test the mulWadDown function.
    let mulwaddown_output = arbiter_math
        .mul_wad_down(eU256::from(1_000_000_000_000_000_000_u128), eU256::from(2))
        .call()
        .await
        .unwrap();
    println!("mulWadDown(1, 2) = {}", mulwaddown_output);
    assert_eq!(mulwaddown_output, eU256::from(2));

    // Test the mulWadUp function.
    let mulwadup_output = arbiter_math
        .mul_wad_up(eU256::from(1_000_000_000_000_000_000_u128), eU256::from(2))
        .call()
        .await
        .unwrap();
    println!("mulWadUp(1, 2) = {}", mulwadup_output);
    assert_eq!(mulwadup_output, eU256::from(2));

    // Test the divWadDown function.
    let divwaddown_output = arbiter_math
        .div_wad_down(eU256::from(1_000_000_000_000_000_000_u128), eU256::from(2))
        .call()
        .await
        .unwrap();
    println!("divWadDown(1, 2) = {}", divwaddown_output);
    assert_eq!(
        divwaddown_output,
        eU256::from(500000000000000000000000000000000000_u128)
    );

    // Test the divWadUp function.
    let divwadup_output = arbiter_math
        .div_wad_up(eU256::from(1_000_000_000_000_000_000_u128), eU256::from(2))
        .call()
        .await
        .unwrap();
    println!("divWadUp(1, 2) = {}", divwadup_output);
    assert_eq!(
        divwadup_output,
        eU256::from(500000000000000000000000000000000000_u128)
    );

    // Test the lnWad function.
    let lnwad_output = arbiter_math
        .log(eI256::from(1_000_000_000_000_000_000_u128))
        .call()
        .await
        .unwrap();
    println!("ln(1) = {}", lnwad_output);
    assert_eq!(lnwad_output, eI256::from(0));

    // Test the sqrt function
    let sqrt_output = arbiter_math
        .sqrt(eU256::from(1_000_000_000_000_000_000_u128))
        .call()
        .await
        .unwrap();
    println!("sqrt(1) = {}", sqrt_output);
    assert_eq!(sqrt_output, eU256::from(1_000_000_000));
}

// TODO: It would be good to change this to `token_functions` and test all
// relevant ERC20 functions (e.g., transfer, approve, etc.).
#[tokio::test]
async fn token_mint_and_balance() {
    let (_env, client) = startup();
    let arbx = deploy_arbx(client.clone()).await;

    // Mint some tokens to the client.
    arbx.mint(
        client.default_sender().unwrap(),
        eU256::from(TEST_MINT_AMOUNT),
    )
    .send()
    .await
    .unwrap()
    .await
    .unwrap();

    // Fetch the balance of the client.
    let balance = arbx
        .balance_of(client.default_sender().unwrap())
        .call()
        .await
        .unwrap();

    // Check that the balance is correct.
    assert_eq!(balance, eU256::from(TEST_MINT_AMOUNT));
}

#[tokio::test]
async fn liquid_exchange_swap() {
    let (_env, client) = startup();
    let (arbx, arby, liquid_exchange) = deploy_liquid_exchange(client.clone()).await;

    // Mint tokens to the client then check balances.
    arbx.mint(
        client.default_sender().unwrap(),
        eU256::from(TEST_MINT_AMOUNT),
    )
    .send()
    .await
    .unwrap()
    .await
    .unwrap();
    arby.mint(
        client.default_sender().unwrap(),
        eU256::from(TEST_MINT_AMOUNT),
    )
    .send()
    .await
    .unwrap()
    .await
    .unwrap();
    let arbx_balance = arbx
        .balance_of(client.default_sender().unwrap())
        .call()
        .await
        .unwrap();
    let arby_balance = arby
        .balance_of(client.default_sender().unwrap())
        .call()
        .await
        .unwrap();
    println!("arbx_balance prior to swap = {}", arbx_balance);
    println!("arby_balance prior to swap = {}", arby_balance);
    assert_eq!(arbx_balance, eU256::from(TEST_MINT_AMOUNT));
    assert_eq!(arby_balance, eU256::from(TEST_MINT_AMOUNT));

    // Get the price at the liquid exchange
    let price = liquid_exchange.price().call().await.unwrap();
    println!("price in 18 decimal WAD: {}", price);

    // Mint tokens to the liquid exchange.
    let exchange_mint_amount = eU256::MAX / 2;
    arbx.mint(liquid_exchange.address(), exchange_mint_amount)
        .send()
        .await
        .unwrap()
        .await
        .unwrap();
    arby.mint(liquid_exchange.address(), exchange_mint_amount)
        .send()
        .await
        .unwrap()
        .await
        .unwrap();

    // Approve the liquid exchange to spend the client's tokens.
    arbx.approve(liquid_exchange.address(), eU256::MAX)
        .send()
        .await
        .unwrap()
        .await
        .unwrap();
    arby.approve(liquid_exchange.address(), eU256::MAX)
        .send()
        .await
        .unwrap()
        .await
        .unwrap();

    // Swap some X for Y on the liquid exchange.
    let swap_amount_x = eU256::from(TEST_MINT_AMOUNT) / 2;
    liquid_exchange
        .swap(arbx.address(), swap_amount_x)
        .send()
        .await
        .unwrap()
        .await
        .unwrap()
        .unwrap();

    // Check the client's balances are correct.
    let arbx_balance_after_swap_x = arbx
        .balance_of(client.default_sender().unwrap())
        .call()
        .await
        .unwrap();
    let arby_balance_after_swap_x = arby
        .balance_of(client.default_sender().unwrap())
        .call()
        .await
        .unwrap();
    println!("arbx_balance after swap = {}", arbx_balance_after_swap_x);
    println!("arby_balance after swap = {}", arby_balance_after_swap_x);
    assert_eq!(
        arbx_balance_after_swap_x,
        eU256::from(TEST_MINT_AMOUNT) - swap_amount_x
    );
    let additional_y = swap_amount_x * price / eU256::from(10_u64.pow(18));
    assert_eq!(
        arby_balance_after_swap_x,
        eU256::from(TEST_MINT_AMOUNT) + additional_y
    );

    // Swap some Y for X on the liquid exchange.
    let swap_amount_y = additional_y;
    liquid_exchange
        .swap(arby.address(), swap_amount_y)
        .send()
        .await
        .unwrap()
        .await
        .unwrap();

    // Check the client's balances are correct.
    let arbx_balance_after_swap_y = arbx
        .balance_of(client.default_sender().unwrap())
        .call()
        .await
        .unwrap();
    let arby_balance_after_swap_y = arby
        .balance_of(client.default_sender().unwrap())
        .call()
        .await
        .unwrap();
    println!("arbx_balance after swap = {}", arbx_balance_after_swap_y);
    println!("arby_balance after swap = {}", arby_balance_after_swap_y);

    // The balance here is off by one due to rounding and the extremely small
    // balances we are using.
    assert_eq!(arbx_balance_after_swap_y, eU256::from(TEST_MINT_AMOUNT) - 1);
    assert_eq!(arby_balance_after_swap_y, eU256::from(TEST_MINT_AMOUNT));
}

#[tokio::test]
async fn price_simulation_oracle() {
    let (_env, client) = startup();
    let (.., liquid_exchange) = deploy_liquid_exchange(client.clone()).await;

    let price_path = vec![
        1000.0, 2000.0, 3000.0, 4000.0, 5000.0, 6000.0, 7000.0, 8000.0,
    ];

    // Get the initial price of the liquid exchange.
    let initial_price = liquid_exchange.price().call().await.unwrap();
    assert_eq!(initial_price, parse_ether(LIQUID_EXCHANGE_PRICE).unwrap());

    for price in price_path {
        let wad_price = parse_ether(price).unwrap();
        liquid_exchange
            .set_price(wad_price)
            .send()
            .await
            .unwrap()
            .await
            .unwrap();
        let new_price = liquid_exchange.price().call().await.unwrap();
        assert_eq!(new_price, wad_price);
    }
}

#[tokio::test]
async fn can_log() {
    std::env::set_var("RUST_LOG", "trace");
    let file = File::create("test_logs.log").expect("Unable to create log file");
    let subscriber = fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .with_writer(file)
        .finish();
    tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");

    let env = Environment::builder().with_console_logs().build();
    let client = ArbiterMiddleware::new(&env, None).unwrap();
    let counter = arbiter_bindings::bindings::counter::Counter::deploy(client, ())
        .unwrap()
        .send()
        .await
        .unwrap();

    // Call the `setNumber` function to emit a console log.
    counter
        .set_number(eU256::from(42))
        .send()
        .await
        .unwrap()
        .await
        .unwrap();

    let parsed_file = fs::read_to_string("test_logs.log").expect("Unable to read log file");
    assert!(parsed_file.contains("You set the number to: , 42"));
    fs::remove_file("test_logs.log").expect("Unable to remove log file");
}