-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
350 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,322 @@ | ||
use crate::{ | ||
constants::{MAX_TICK, MIN_TICK}, | ||
error::ContractError, | ||
order::*, | ||
orderbook::*, | ||
state::*, | ||
types::OrderDirection, | ||
}; | ||
use cosmwasm_std::{coin, Addr, Uint128, Uint256}; | ||
use cosmwasm_std::{ | ||
testing::{mock_dependencies_with_balances, mock_env, mock_info}, | ||
Decimal256, | ||
}; | ||
|
||
#[allow(clippy::uninlined_format_args)] | ||
fn format_test_name(name: &str) -> String { | ||
format!("\n\nTest case failed: {}\n", name) | ||
} | ||
|
||
struct PlaceLimitTestCase { | ||
name: &'static str, | ||
book_id: u64, | ||
tick_id: i64, | ||
quantity: Uint128, | ||
sent: Uint128, | ||
order_direction: OrderDirection, | ||
expected_error: Option<ContractError>, | ||
} | ||
|
||
#[test] | ||
fn test_place_limit() { | ||
let valid_book_id = 0; | ||
let invalid_book_id = valid_book_id + 1; | ||
let test_cases = vec![ | ||
PlaceLimitTestCase { | ||
name: "valid order with positive tick id", | ||
book_id: valid_book_id, | ||
tick_id: 10, | ||
quantity: Uint128::new(100), | ||
sent: Uint128::new(100), | ||
order_direction: OrderDirection::Ask, | ||
expected_error: None, | ||
}, | ||
PlaceLimitTestCase { | ||
name: "valid order with zero tick id", | ||
book_id: valid_book_id, | ||
tick_id: 0, | ||
quantity: Uint128::new(34321), | ||
sent: Uint128::new(34321), | ||
order_direction: OrderDirection::Bid, | ||
expected_error: None, | ||
}, | ||
PlaceLimitTestCase { | ||
name: "valid order with negative tick id", | ||
book_id: valid_book_id, | ||
tick_id: -5, | ||
quantity: Uint128::new(100), | ||
sent: Uint128::new(100), | ||
order_direction: OrderDirection::Bid, | ||
expected_error: None, | ||
}, | ||
PlaceLimitTestCase { | ||
name: "valid order with large quantity", | ||
book_id: valid_book_id, | ||
tick_id: 3, | ||
quantity: Uint128::new(34321), | ||
sent: Uint128::new(34321), | ||
order_direction: OrderDirection::Ask, | ||
expected_error: None, | ||
}, | ||
PlaceLimitTestCase { | ||
name: "invalid book id", | ||
book_id: invalid_book_id, | ||
tick_id: 1, | ||
quantity: Uint128::new(100), | ||
sent: Uint128::new(100), | ||
order_direction: OrderDirection::Ask, | ||
expected_error: Some(ContractError::InvalidBookId { | ||
book_id: invalid_book_id, | ||
}), | ||
}, | ||
PlaceLimitTestCase { | ||
name: "invalid tick id (max)", | ||
book_id: valid_book_id, | ||
tick_id: MAX_TICK + 1, | ||
quantity: Uint128::new(100), | ||
sent: Uint128::new(100), | ||
order_direction: OrderDirection::Ask, | ||
expected_error: Some(ContractError::InvalidTickId { | ||
tick_id: MAX_TICK + 1, | ||
}), | ||
}, | ||
PlaceLimitTestCase { | ||
name: "invalid tick id (min)", | ||
book_id: valid_book_id, | ||
tick_id: MIN_TICK - 1, | ||
quantity: Uint128::new(100), | ||
sent: Uint128::new(100), | ||
order_direction: OrderDirection::Ask, | ||
expected_error: Some(ContractError::InvalidTickId { | ||
tick_id: MIN_TICK - 1, | ||
}), | ||
}, | ||
PlaceLimitTestCase { | ||
name: "invalid quantity", | ||
book_id: valid_book_id, | ||
tick_id: 1, | ||
quantity: Uint128::zero(), | ||
sent: Uint128::new(1000), | ||
order_direction: OrderDirection::Ask, | ||
expected_error: Some(ContractError::InvalidQuantity { | ||
quantity: Uint128::zero(), | ||
}), | ||
}, | ||
PlaceLimitTestCase { | ||
name: "insufficient funds", | ||
book_id: valid_book_id, | ||
tick_id: 1, | ||
quantity: Uint128::new(1000), | ||
sent: Uint128::new(500), | ||
order_direction: OrderDirection::Ask, | ||
expected_error: Some(ContractError::InsufficientFunds { | ||
sent: Uint128::new(500), | ||
required: Uint128::new(1000), | ||
}), | ||
}, | ||
PlaceLimitTestCase { | ||
name: "excessive funds", | ||
book_id: valid_book_id, | ||
tick_id: 1, | ||
quantity: Uint128::new(100), | ||
sent: Uint128::new(500), | ||
order_direction: OrderDirection::Ask, | ||
expected_error: Some(ContractError::InsufficientFunds { | ||
sent: Uint128::new(500), | ||
required: Uint128::new(100), | ||
}), | ||
}, | ||
]; | ||
|
||
for test in test_cases { | ||
// --- Setup --- | ||
|
||
// Create a mock environment and info | ||
let coin_vec = vec![coin( | ||
test.sent.u128(), | ||
if test.order_direction == OrderDirection::Ask { | ||
"base" | ||
} else { | ||
"quote" | ||
}, | ||
)]; | ||
let balances = [("creator", coin_vec.as_slice())]; | ||
let mut deps = mock_dependencies_with_balances(&balances); | ||
let env = mock_env(); | ||
let info = mock_info("creator", &coin_vec); | ||
|
||
// Create an orderbook to operate on | ||
let quote_denom = "quote".to_string(); | ||
let base_denom = "base".to_string(); | ||
let _create_response = create_orderbook( | ||
deps.as_mut(), | ||
env.clone(), | ||
info.clone(), | ||
quote_denom, | ||
base_denom, | ||
) | ||
.unwrap(); | ||
|
||
// --- System under test --- | ||
|
||
let response = place_limit( | ||
deps.as_mut(), | ||
env.clone(), | ||
info.clone(), | ||
test.book_id, | ||
test.tick_id, | ||
test.order_direction, | ||
test.quantity, | ||
); | ||
|
||
// --- Assertions --- | ||
|
||
// Error case assertions if applicable | ||
if let Some(expected_error) = &test.expected_error { | ||
assert_eq!( | ||
response.unwrap_err(), | ||
*expected_error, | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
|
||
// Verify that the order was not put in state | ||
let order_result = orders() | ||
.may_load(&deps.storage, &(test.book_id, test.tick_id, 0)) | ||
.unwrap(); | ||
assert!(order_result.is_none(), "{}", format_test_name(test.name)); | ||
|
||
// Verifiy liquidity was not updated | ||
let state = TICK_STATE | ||
.load(&deps.storage, &(test.book_id, test.tick_id)) | ||
.unwrap_or_default(); | ||
assert!( | ||
state.total_amount_of_liquidity.is_zero(), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
continue; | ||
} | ||
|
||
// Assert no error and retrieve response contents | ||
let response = response.unwrap(); | ||
|
||
// Assertions on the response for a valid order | ||
assert_eq!( | ||
response.attributes[0], | ||
("method", "placeLimit"), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
response.attributes[1], | ||
("owner", "creator"), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
response.attributes[2], | ||
("book_id", test.book_id.to_string()), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
response.attributes[3], | ||
("tick_id", test.tick_id.to_string()), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
response.attributes[6], | ||
("quantity", test.quantity.to_string()), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
response.attributes[7], | ||
("quantity_fulfilled", "0"), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
|
||
// Retrieve the order from storage to verify it was saved correctly | ||
let expected_order_id = 0; | ||
let order = orders() | ||
.load( | ||
&deps.storage, | ||
&(test.book_id, test.tick_id, expected_order_id), | ||
) | ||
.unwrap(); | ||
|
||
// Verify the order's fields | ||
assert_eq!( | ||
order.book_id, | ||
test.book_id, | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
order.tick_id, | ||
test.tick_id, | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
order.order_id, | ||
expected_order_id, | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
order.order_direction, | ||
test.order_direction, | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
order.owner, | ||
Addr::unchecked("creator"), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
order.quantity, | ||
test.quantity, | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
|
||
// Validate liquidity updated as intended | ||
let state = TICK_STATE | ||
.load(&deps.storage, &(test.book_id, test.tick_id)) | ||
.unwrap(); | ||
assert_eq!( | ||
state.total_amount_of_liquidity, | ||
Decimal256::from_ratio(test.quantity, Uint256::one()), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
state.effective_total_amount_swapped, | ||
Decimal256::zero(), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
assert_eq!( | ||
state.cumulative_total_limits, | ||
Decimal256::one(), | ||
"{}", | ||
format_test_name(test.name) | ||
); | ||
} | ||
} |