Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: minimum_balance for placing orders set to 1 atom for FOK #2558

Merged
merged 6 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/e2e/tests/e2e/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ mod smart_contract_orders;
mod solver_competition;
mod submission;
mod tracking_insufficient_funds;
mod uncovered_order;
mod univ2;
mod vault_balances;
80 changes: 80 additions & 0 deletions crates/e2e/tests/e2e/uncovered_order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use {
e2e::{setup::*, tx, tx_value},
ethcontract::U256,
model::{
order::{OrderCreation, OrderKind},
signature::EcdsaSigningScheme,
},
secp256k1::SecretKey,
shared::ethrpc::Web3,
web3::signing::SecretKeyRef,
};

#[tokio::test]
#[ignore]
async fn local_node_test() {
run_test(test).await;
}

/// Tests that a user can already create an order if they only have
/// 1 wei of the sell token and later fund their account to get the
/// order executed.
async fn test(web3: Web3) {
tracing::info!("Setting up chain state.");
let mut onchain = OnchainComponents::deploy(web3).await;

let [solver] = onchain.make_solvers(to_wei(10)).await;
let [trader] = onchain.make_accounts(to_wei(10)).await;
let [token] = onchain
.deploy_tokens_with_weth_uni_v2_pools(to_wei(1_000), to_wei(1_000))
.await;
let weth = &onchain.contracts().weth;

tx!(
trader.account(),
weth.approve(onchain.contracts().allowance, to_wei(3))
);

tracing::info!("Starting services.");
let services = Services::new(onchain.contracts()).await;
services.start_protocol(solver).await;

tracing::info!("Placing order with 0 sell tokens");
let order = OrderCreation {
sell_token: weth.address(),
sell_amount: to_wei(2),
fee_amount: 0.into(),
buy_token: token.address(),
buy_amount: to_wei(1),
valid_to: model::time::now_in_epoch_seconds() + 300,
kind: OrderKind::Sell,
partially_fillable: false,
..Default::default()
}
.sign(
EcdsaSigningScheme::Eip712,
&onchain.contracts().domain_separator,
SecretKeyRef::from(&SecretKey::from_slice(trader.private_key()).unwrap()),
);
services.create_order(&order).await.unwrap_err();

tracing::info!("Placing order with 1 wei of sell_tokens");
tx_value!(trader.account(), 1.into(), weth.deposit());
services.create_order(&order).await.unwrap();

tracing::info!("Deposit ETH to make order executable");
tx_value!(trader.account(), to_wei(2), weth.deposit());

tracing::info!("Waiting for order to show up in auction");
wait_for_condition(TIMEOUT, || async { services.solvable_orders().await == 1 })
.await
.unwrap();

// Drive solution
tracing::info!("Waiting for trade.");
wait_for_condition(TIMEOUT, || async { services.solvable_orders().await == 0 })
.await
.unwrap();
let balance_after = weth.balance_of(trader.address()).call().await.unwrap();
assert_eq!(U256::one(), balance_after);
}
90 changes: 6 additions & 84 deletions crates/shared/src/order_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -591,8 +591,6 @@ impl OrderValidating for OrderValidator {
verification,
};

let min_balance = minimum_balance(&data).ok_or(ValidationError::SellAmountOverflow)?;

// Fast path to check if transfer is possible with a single node query.
// If not, run extra queries for additional information.
match self
Expand All @@ -604,7 +602,7 @@ impl OrderValidating for OrderValidator {
source: data.sell_token_balance,
interactions: app_data.interactions.pre.clone(),
},
min_balance,
MINIMUM_BALANCE,
)
.await
{
Expand Down Expand Up @@ -826,17 +824,11 @@ fn has_same_buy_and_sell_token(order: &PreOrderData, native_token: &WETH9) -> bo
}

/// Min balance user must have in sell token for order to be accepted.
///
/// None when addition overflows.
fn minimum_balance(order: &OrderData) -> Option<U256> {
// TODO: We might even want to allow 0 balance for partially fillable but we
// require balance for fok limit orders too so this make some sense and protects
// against accidentally creating order for token without balance.
if order.partially_fillable {
return Some(1.into());
}
order.sell_amount.checked_add(order.fee_amount)
}
// All orders can be placed without having the full sell balance.
// A minimum, of 1 atom is still required as a spam protection measure.
// TODO: ideally, we should keep the full balance enforcement for SWAPs,
// but given all orders are LIMIT now, this is harder to do.
const MINIMUM_BALANCE: U256 = U256::one(); // 1 atom of a token

/// Retrieves the quote for an order that is being created and verify that its
/// fee is sufficient.
Expand Down Expand Up @@ -988,22 +980,6 @@ mod tests {
std::str::FromStr,
};

#[test]
fn minimum_balance_() {
let order = OrderData {
sell_amount: U256::MAX,
fee_amount: U256::from(1),
..Default::default()
};
assert_eq!(minimum_balance(&order), None);
let order = OrderData {
sell_amount: U256::from(1),
fee_amount: U256::from(1),
..Default::default()
};
assert_eq!(minimum_balance(&order), Some(U256::from(2)));
}

#[test]
fn detects_orders_with_same_buy_and_sell_token() {
let native_token = dummy_contract!(WETH9, [0xef; 20]);
Expand Down Expand Up @@ -1895,60 +1871,6 @@ mod tests {
));
}

#[tokio::test]
async fn post_validate_err_sell_amount_overflow() {
let mut order_quoter = MockOrderQuoting::new();
let mut bad_token_detector = MockBadTokenDetecting::new();
let mut balance_fetcher = MockBalanceFetching::new();
order_quoter
.expect_find_quote()
.returning(|_, _| Ok(Default::default()));
order_quoter.expect_store_quote().returning(Ok);
bad_token_detector
.expect_detect()
.returning(|_| Ok(TokenQuality::Good));
balance_fetcher
.expect_can_transfer()
.returning(|_, _| Ok(()));
let mut limit_order_counter = MockLimitOrderCounting::new();
limit_order_counter.expect_count().returning(|_| Ok(0u64));
let validator = OrderValidator::new(
dummy_contract!(WETH9, [0xef; 20]),
Arc::new(order_validation::banned::Users::none()),
OrderValidPeriodConfiguration::any(),
false,
Arc::new(bad_token_detector),
dummy_contract!(HooksTrampoline, [0xcf; 20]),
Arc::new(order_quoter),
Arc::new(balance_fetcher),
Arc::new(MockSignatureValidating::new()),
Arc::new(limit_order_counter),
0,
Arc::new(MockCodeFetching::new()),
Default::default(),
None,
u64::MAX,
);
let order = OrderCreation {
valid_to: time::now_in_epoch_seconds() + 2,
sell_token: H160::from_low_u64_be(1),
buy_token: H160::from_low_u64_be(2),
buy_amount: U256::from(1),
sell_amount: U256::MAX,
fee_amount: U256::from(1),
signature: Signature::Eip712(EcdsaSignature::non_zero()),
app_data: OrderCreationAppData::Full {
full: "{}".to_string(),
},
..Default::default()
};
let result = validator
.validate_and_construct_order(order, &Default::default(), Default::default(), None)
.await;
dbg!(&result);
assert!(matches!(result, Err(ValidationError::SellAmountOverflow)));
}

#[tokio::test]
async fn post_validate_err_insufficient_balance() {
let mut order_quoter = MockOrderQuoting::new();
Expand Down
Loading