use super::*;
use crate::{
order_book_deploy::Side,
prop::PropOrderBookCleanup,
};
async fn resting_cleanup(
fixture: &PropFixture,
side: Side,
price: u64,
) -> PropOrderBookCleanup {
let orders = fixture
.order_book
.order_book
.methods()
.get_orders_at_price(side, price)
.simulate(Execution::state_read_only())
.await
.unwrap()
.value;
let order_ids = orders
.into_iter()
.filter(|order| order.trader_id == Identity::ContractId(fixture.child_id))
.map(|order| order.order_id)
.collect::<Vec<_>>();
assert!(!order_ids.is_empty(), "the account has no resting order");
PropOrderBookCleanup {
order_book: fixture.order_book.contract_id,
order_ids,
}
}
#[tokio::test]
async fn permissioned_closure_drains_every_order_and_settles_all_session_legs() {
assert_permissioned_closure_drains_every_leg(false).await;
}
#[tokio::test]
async fn signed_closure_drains_every_order_and_settles_all_session_legs() {
assert_permissioned_closure_drains_every_leg(true).await;
}
async fn permissioned_close(
fixture: &PropFixture,
authorization: &Option<(u64, Signature)>,
cleanups: Vec<PropOrderBookCleanup>,
) -> Result<CallResponse<()>> {
match authorization {
Some((session, signature)) => {
fixture
.force_close_session_signed(
&fixture.user,
*session,
cleanups,
signature.clone(),
)
.await
}
None => {
fixture
.force_close_session(&fixture.deployer, cleanups)
.await
}
}
}
async fn assert_permissioned_closure_drains_every_leg(signed: bool) {
let mut fixture = PropFixture::new().await;
let authorization = if signed {
fixture
.deployment
.pool
.methods()
.set_expiry_cosigner(fixture.deployer.address())
.call()
.await
.unwrap();
let session = fixture.session().await.session_id;
Some((
session,
fixture
.force_close_cosignature(session, &fixture.deployer)
.await,
))
} else {
None
};
let borrowed = 100_000_000;
let gift = 100_000_000;
let drawn = 1_000_000;
let liquidator = Identity::Address(fixture.outsider.address());
fixture
.deployment
.pool
.methods()
.set_liquidator(liquidator)
.call()
.await
.unwrap();
fixture
.deployer
.force_transfer_to_contract(
fixture.child_id,
gift,
fixture.base_asset,
TxPolicies::default(),
)
.await
.unwrap();
let borrow = fixture.pool_call(
fn_selector!(borrow(AssetId, u64)),
0,
AssetId::default(),
Some(call_data!(fixture.base_asset, borrowed)),
);
let draw = fixture.draw_call(drawn);
let buy =
fixture.book_call(1_000_000, 1_000_000_000, fixture.collateral_asset, drawn);
let sell = fixture.book_call(
3_000_000,
borrowed + gift,
fixture.base_asset,
borrowed + gift,
);
fixture
.call_account(vec![borrow, draw, buy, sell], 2)
.await
.unwrap();
let buy_cleanup = resting_cleanup(&fixture, Side::Buy, 1_000_000).await;
let sell_cleanup = resting_cleanup(&fixture, Side::Sell, 3_000_000).await;
assert_eq!(fixture.account_balance(fixture.collateral_asset).await, 0);
assert_eq!(fixture.account_balance(fixture.base_asset).await, 0);
let session_before = fixture.session().await;
let pool_base_before = fixture.pool_balance(fixture.base_asset).await;
let pool_cash_before = fixture.pool_balance(fixture.collateral_asset).await;
let error = permissioned_close(&fixture, &authorization, vec![buy_cleanup.clone()])
.await
.unwrap_err();
assert!(error.to_string().contains("BookNotDrained"), "{error:#}");
assert_eq!(
resting_cleanup(&fixture, Side::Buy, 1_000_000)
.await
.order_ids,
buy_cleanup.order_ids
);
assert_eq!(fixture.account_balance(fixture.collateral_asset).await, 0);
assert_eq!(fixture.session().await.drawn_quote, drawn);
assert_eq!(fixture.debt(fixture.base_asset).await, borrowed);
assert_eq!(fixture.session().await, session_before);
assert_eq!(
fixture.pool_balance(fixture.base_asset).await,
pool_base_before
);
assert_eq!(
fixture.pool_balance(fixture.collateral_asset).await,
pool_cash_before
);
assert_eq!(
resting_cleanup(&fixture, Side::Sell, 3_000_000)
.await
.order_ids,
sell_cleanup.order_ids
);
let liquidator_before = fixture
.outsider
.get_asset_balance(&fixture.base_asset)
.await
.unwrap();
let pool_cash_before = fixture.pool_balance(fixture.collateral_asset).await;
let mut recipient_balances = Vec::new();
for wallet in [&fixture.user, &fixture.deployer] {
recipient_balances.push((
wallet.get_asset_balance(&fixture.base_asset).await.unwrap(),
wallet
.get_asset_balance(&fixture.collateral_asset)
.await
.unwrap(),
));
}
let response =
permissioned_close(&fixture, &authorization, vec![buy_cleanup, sell_cleanup])
.await
.unwrap();
let closed = fixture.closed_event(&response);
assert_eq!(closed.reason, SettlementReason::PermissionedClosure);
assert_eq!(closed.v, LINE + 200_000);
assert_eq!(closed.v_liq, LINE + 198_000);
assert_eq!(closed.profit_abs, 198_000);
assert!(!closed.profit_is_negative);
assert_eq!(closed.platform_total, 19_800);
assert_eq!(closed.user_net, COLLATERAL + 198_000 - 19_800);
assert_eq!(closed.bad_debt, 0);
assert!(closed.cancelled_debt.is_empty());
let seized = response.decode_logs_with_type::<SessionSeized>().unwrap();
assert_eq!(seized.len(), 1);
assert_eq!(seized[0].reason, SettlementReason::PermissionedClosure);
assert_eq!(seized[0].liquidator, liquidator);
assert_eq!(seized[0].debt, vec![(fixture.base_asset, borrowed)]);
assert_eq!(seized[0].transferred, vec![(fixture.base_asset, borrowed)]);
assert_eq!(
fixture
.outsider
.get_asset_balance(&fixture.base_asset)
.await
.unwrap()
- liquidator_before,
borrowed as u128
);
for ((wallet, payouts), (base_before, cash_before)) in [
(&fixture.user, &closed.payout_parent),
(&fixture.deployer, &closed.payout_platform),
]
.into_iter()
.zip(recipient_balances)
{
for (asset, before) in [
(fixture.base_asset, base_before),
(fixture.collateral_asset, cash_before),
] {
let payout: u64 = payouts
.iter()
.filter(|(id, _)| *id == asset)
.map(|(_, quantity)| *quantity)
.sum();
assert!(payout > 0);
assert_eq!(
wallet.get_asset_balance(&asset).await.unwrap() - before,
payout as u128
);
}
}
assert_eq!(
fixture.pool_balance(fixture.collateral_asset).await,
pool_cash_before + drawn - COLLATERAL
);
assert_eq!(fixture.account_balance(fixture.base_asset).await, 0);
assert_eq!(fixture.account_balance(fixture.collateral_asset).await, 0);
assert_eq!(
fixture
.order_book
.order_book
.methods()
.get_total_balance_of(Identity::ContractId(fixture.child_id))
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
(0, 0)
);
assert_eq!(fixture.debt(fixture.base_asset).await, 0);
assert!(fixture.session_opt().await.is_none());
}
#[tokio::test]
async fn permissioned_closure_and_liquidation_settle_the_same_uncovered_short() {
for permissioned in [false, true] {
let mut fixture = PropFixture::new().await;
let quantity = 1_000_000_000;
let sale = 2_000_000;
let drawn = 3_000_000;
fixture
.deployment
.pool
.methods()
.set_liquidator(Identity::Address(fixture.outsider.address()))
.call()
.await
.unwrap();
fixture
.rest_order(sale, quantity, fixture.collateral_asset, sale)
.await;
let borrow = fixture.pool_call(
fn_selector!(borrow(AssetId, u64)),
0,
AssetId::default(),
Some(call_data!(fixture.base_asset, quantity)),
);
let draw = fixture.draw_call(drawn);
let sell = fixture.book_call(sale, quantity, fixture.base_asset, quantity);
fixture
.call_account(vec![borrow, draw, sell, fixture.settle_book_call()], 2)
.await
.unwrap();
fixture.publish_base_price(20_000_000_000_000_000_000).await;
assert!(fixture.is_liquidatable().await);
let liquidator_before = fixture
.outsider
.get_asset_balance(&fixture.collateral_asset)
.await
.unwrap();
let pool_cash_before = fixture.pool_balance(fixture.collateral_asset).await;
let response = if permissioned {
fixture
.force_close_session(&fixture.deployer, vec![])
.await
.unwrap()
} else {
fixture.liquidate().await
};
let reason = if permissioned {
SettlementReason::PermissionedClosure
} else {
SettlementReason::Liquidation
};
let closed = fixture.closed_event(&response);
assert_eq!(closed.reason, reason);
assert_eq!(closed.cancelled_debt, vec![(fixture.base_asset, quantity)]);
assert_eq!(closed.profit_abs, 18_202_021);
assert!(closed.profit_is_negative);
assert_eq!(closed.bad_debt, 16_202_021);
assert_eq!(closed.user_net, 0);
assert_eq!(closed.platform_total, 0);
assert_eq!(fixture.bad_debt().await, closed.bad_debt);
let seized = response.decode_logs_with_type::<SessionSeized>().unwrap();
assert_eq!(seized.len(), 1);
assert_eq!(seized[0].reason, reason);
assert_eq!(seized[0].debt, vec![(fixture.base_asset, quantity)]);
assert_eq!(
seized[0].holdings,
vec![(fixture.collateral_asset, drawn + sale)]
);
assert_eq!(
seized[0].transferred,
vec![(fixture.collateral_asset, COLLATERAL + sale)]
);
assert_eq!(
fixture
.outsider
.get_asset_balance(&fixture.collateral_asset)
.await
.unwrap()
- liquidator_before,
(COLLATERAL + sale) as u128
);
assert_eq!(
fixture.pool_balance(fixture.collateral_asset).await,
pool_cash_before + drawn - COLLATERAL
);
assert_eq!(fixture.account_balance(fixture.collateral_asset).await, 0);
assert_eq!(fixture.debt(fixture.base_asset).await, 0);
assert!(fixture.session_opt().await.is_none());
}
}
#[tokio::test]
async fn a_close_is_refused_while_an_order_rests_and_the_cleanup_list_frees_it() {
let mut fixture = PropFixture::new().await;
let draw = fixture.draw_call(1_000_000);
let rest = fixture.book_call(
1_000_000,
1_000_000_000,
fixture.collateral_asset,
1_000_000,
);
fixture.call_account(vec![draw, rest], 1).await.unwrap();
assert_eq!(fixture.account_balance(fixture.collateral_asset).await, 0);
let net = fixture.repay_from_collateral_call(1_000_000);
fixture.call_account(vec![net], 0).await.unwrap();
assert_eq!(fixture.session().await.drawn_quote, 0);
let error = fixture
.account
.methods()
.close_session(Vec::<PropOrderBookCleanup>::new())
.with_contracts(&[
&fixture.deployment.pool,
&fixture.deployment.oracle,
&fixture.order_book.order_book,
&fixture.deployment.price_feed,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap_err();
assert!(
error.to_string().contains("BookNotDrained"),
"value locked in a book cannot be settled at any price: {error:#}"
);
let cleanup = resting_cleanup(&fixture, Side::Buy, 1_000_000).await;
let parent_before = fixture
.user
.get_asset_balance(&fixture.collateral_asset)
.await
.unwrap();
let response = fixture
.account
.methods()
.close_session(vec![cleanup])
.with_contracts(&[
&fixture.deployment.pool,
&fixture.deployment.oracle,
&fixture.order_book.order_book,
&fixture.deployment.price_feed,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap();
let closed = fixture.closed_event(&response);
assert_eq!(closed.reason, SettlementReason::UserClose);
assert_eq!(closed.bad_debt, 0);
assert_eq!(
closed.platform_total, 0,
"the netting is principal coming home, not profit"
);
assert_eq!(closed.user_net, COLLATERAL);
assert_eq!(
fixture
.user
.get_asset_balance(&fixture.collateral_asset)
.await
.unwrap()
- parent_before,
COLLATERAL as u128,
"the whole entry came back to the parent"
);
assert_eq!(
fixture
.order_book
.order_book
.methods()
.get_total_balance_of(Identity::ContractId(fixture.child_id))
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
(0, 0)
);
}
#[tokio::test]
async fn a_keeper_must_cancel_before_it_can_judge_and_the_cancel_answers_the_judgement() {
let mut fixture = PropFixture::new().await;
let draw = fixture.draw_call(1_800_000);
let rest = fixture.book_call(
1_000_000,
1_800_000_000,
fixture.collateral_asset,
1_800_000,
);
fixture.call_account(vec![draw, rest], 1).await.unwrap();
assert_eq!(fixture.account_balance(fixture.collateral_asset).await, 0);
let keeper = fixture
.deployment
.pool
.clone()
.with_account(fixture.outsider.clone());
let error = keeper
.methods()
.liquidate(fixture.child_id, vec![])
.with_contracts(&[
&fixture.account,
&fixture.deployment.oracle,
&fixture.order_book.order_book,
&fixture.deployment.price_feed,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap_err();
assert!(
error.to_string().contains("BookNotDrained"),
"the gate is never read against a half-visible portfolio: {error:#}"
);
let cleanup = resting_cleanup(&fixture, Side::Buy, 1_000_000).await;
let error = keeper
.methods()
.liquidate(fixture.child_id, vec![cleanup])
.with_contracts(&[
&fixture.account,
&fixture.deployment.oracle,
&fixture.order_book.order_book,
&fixture.deployment.price_feed,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap_err();
assert!(
error.to_string().contains("NotLiquidatable"),
"a session is judged on the cash the cancel brought home: {error:#}"
);
assert_eq!(fixture.account_balance(fixture.collateral_asset).await, 0);
assert_eq!(fixture.session().await.drawn_quote, 1_800_000);
assert_ne!(
fixture
.order_book
.order_book
.methods()
.get_total_balance_of(Identity::ContractId(fixture.child_id))
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
(0, 0),
"a refused seizure leaves the order resting"
);
}
#[tokio::test]
async fn a_two_market_session_must_free_both_books_before_it_closes() {
let mut fixture = PropFixture::new().await;
let second_base = AssetId::new([0x91; 32]);
let second = OrderBookDeploy::deploy(
&fixture.deployer,
second_base,
fixture.collateral_asset,
&OrderBookDeployConfig::default(),
)
.await
.unwrap();
fixture
.deployment
.price_feed
.methods()
.set_asset_decimals(second_base, 9)
.call()
.await
.unwrap();
fixture
.deployment
.price_feed
.methods()
.publish_prices(vec![PriceInput {
asset: second_base,
bid: 2_000_000_000_000_000_000u64.into(),
ask: 2_000_000_000_000_000_000u64.into(),
timestamp: fixture.next_publish_time(),
}])
.call()
.await
.unwrap();
fixture
.deployment
.pool
.methods()
.add_books(TIER_ID, vec![second.contract_id])
.with_contract_ids(&[second.contract_id, fixture.deployment.price_feed_id])
.call()
.await
.unwrap();
let draw = fixture.draw_call(2_000_000);
let rest_first = fixture.book_call(
1_000_000,
1_000_000_000,
fixture.collateral_asset,
1_000_000,
);
let rest_second = CallContractArg {
contract_id: second.contract_id,
function_selector: Bytes(fn_selector!(create_order(OrderArgs))),
call_params: CallParams {
coins: 1_000_000,
asset_id: fixture.collateral_asset,
gas: 10_000_000,
},
call_data: Some(Bytes(call_data!(OrderArgs {
price: 1_000_000,
quantity: 1_000_000_000,
order_type: OrderType::Spot,
}))),
};
let (user_signature, cosigner_signature, calls) = fixture
.signed_calls(vec![draw, rest_first, rest_second])
.await;
fixture
.account
.methods()
.call_contracts(user_signature, cosigner_signature, calls)
.with_contracts(&[
&fixture.deployment.oracle,
&fixture.deployment.pool,
&fixture.deployment.price_feed,
&fixture.deployment.registry,
&fixture.order_book.order_book,
&second.order_book,
])
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await
.unwrap();
let net = fixture.repay_from_collateral_call(2_000_000);
fixture.call_account(vec![net], 0).await.unwrap();
let first_cleanup = resting_cleanup(&fixture, Side::Buy, 1_000_000).await;
let second_orders = second
.order_book
.methods()
.get_orders_at_price(Side::Buy, 1_000_000)
.simulate(Execution::state_read_only())
.await
.unwrap()
.value;
let second_cleanup = PropOrderBookCleanup {
order_book: second.contract_id,
order_ids: second_orders
.into_iter()
.filter(|order| order.trader_id == Identity::ContractId(fixture.child_id))
.map(|order| order.order_id)
.collect(),
};
assert_eq!(second_cleanup.order_ids.len(), 1);
let error = fixture
.account
.methods()
.close_session(vec![first_cleanup.clone()])
.with_contracts(&[
&fixture.deployment.pool,
&fixture.deployment.oracle,
&fixture.order_book.order_book,
&second.order_book,
&fixture.deployment.price_feed,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap_err();
assert!(
error.to_string().contains("BookNotDrained"),
"the drain check walks every book the tier admitted: {error:#}"
);
let parent_before = fixture
.user
.get_asset_balance(&fixture.collateral_asset)
.await
.unwrap();
let response = fixture
.account
.methods()
.close_session(vec![first_cleanup, second_cleanup])
.with_contracts(&[
&fixture.deployment.pool,
&fixture.deployment.oracle,
&fixture.order_book.order_book,
&second.order_book,
&fixture.deployment.price_feed,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap();
let closed = fixture.closed_event(&response);
assert_eq!(closed.reason, SettlementReason::UserClose);
assert_eq!(closed.bad_debt, 0);
assert_eq!(closed.user_net, COLLATERAL);
assert_eq!(
fixture
.user
.get_asset_balance(&fixture.collateral_asset)
.await
.unwrap()
- parent_before,
COLLATERAL as u128
);
}
#[tokio::test]
async fn a_second_session_on_the_same_account_starts_from_a_clean_ledger() {
let mut fixture = PropFixture::new().await;
let draw = fixture.draw_call(1_000_000);
let borrow = fixture.pool_call(
fn_selector!(borrow(AssetId, u64)),
0,
AssetId::default(),
Some(call_data!(fixture.base_asset, 500_000_000u64)),
);
fixture.call_account(vec![draw, borrow], 2).await.unwrap();
fixture
.call_account(vec![fixture.prolong_call(1)], 1)
.await
.unwrap();
let first = fixture.session().await;
assert_eq!(first.session_id, 1);
assert_eq!(first.fees_accrued, 6_000);
let repay =
fixture.pool_call(fn_selector!(repay()), 500_000_000, fixture.base_asset, None);
let give_back = fixture.pool_call(
fn_selector!(return_quote()),
1_000_000,
fixture.collateral_asset,
None,
);
fixture
.call_account(vec![repay, give_back], 0)
.await
.unwrap();
fixture.close_session().await;
let second_entry = COLLATERAL;
let response = fixture
.account
.methods()
.start_session(TIER_ID, second_entry, ProlongPeriod::SixHours)
.call_params(CallParameters::new(
second_entry,
fixture.collateral_asset,
u64::MAX,
))
.unwrap()
.with_contract_ids(&[
fixture.deployment.oracle_id,
fixture.deployment.pool_id,
fixture.deployment.registry_id,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap();
let opened = fixture
.deployment
.pool
.log_decoder()
.decode_logs_with_type::<SessionOpened>(&response.tx_status.receipts)
.unwrap();
assert_eq!(opened.len(), 1);
assert_eq!(
opened[0].session_id, 2,
"IDs are monotonic, so the old keys can never be addressed again"
);
let second = fixture.session().await;
assert_eq!(second.session_id, 2);
assert_eq!(second.collateral, second_entry);
assert_eq!(second.drawn_quote, 0, "the old draw did not follow it");
assert_eq!(second.fees_accrued, 0, "nor the old fee");
assert_eq!(second.capitalised, 0);
assert_eq!(second.decapitalised, 0);
assert_eq!(second.credit_line, 10_000_000);
assert_eq!(fixture.debt(fixture.base_asset).await, 0);
assert!(
!fixture
.deployment
.pool
.methods()
.has_debts(fixture.child_id)
.simulate(Execution::state_read_only())
.await
.unwrap()
.value
);
fixture.close_session().await;
}
#[tokio::test]
async fn an_off_tier_holding_is_untouched_by_the_close_and_leaves_with_the_parent() {
let fixture = PropFixture::new().await;
let gift = AssetId::default();
fixture
.deployer
.force_transfer_to_contract(fixture.child_id, 4_242, gift, TxPolicies::default())
.await
.unwrap();
let held = fixture.account_balance(gift).await;
assert!(held != 0);
fixture.close_session().await;
assert_eq!(
fixture.account_balance(gift).await,
held,
"an asset outside the tier is not part of the settlement basket"
);
let parent_before = fixture.user.get_asset_balance(&gift).await.unwrap();
fixture
.account
.methods()
.withdraw(gift, held)
.with_contracts(&[&fixture.deployment.pool, &fixture.deployment.oracle])
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await
.unwrap();
assert!(
fixture.user.get_asset_balance(&gift).await.unwrap() > parent_before,
"and it leaves with the parent"
);
assert_eq!(fixture.account_balance(gift).await, 0);
}
#[tokio::test]
async fn an_unregistered_account_is_inert_until_its_parent_registers_it() {
let fixture = PropFixture::new().await;
let parent = Identity::Address(fixture.user.address());
let configurables = PropAccountProxyContractConfigurables::default()
.with_ORACLE_CONTRACT_ID(fixture.deployment.oracle_id)
.unwrap()
.with_PARENT(parent)
.unwrap()
.with_INDEX(99)
.unwrap();
let contract = regular_contract(
PROP_ACCOUNT_PROXY_BYTECODE,
PROP_ACCOUNT_PROXY_STORAGE,
fixture.deployment.account_salt,
)
.unwrap()
.with_configurables(configurables);
let (unregistered_id, _) = deploy_regular(&fixture.user, contract).await.unwrap();
let registry = &fixture.deployment.registry;
assert!(
!registry
.methods()
.prop_is_valid(unregistered_id)
.simulate(Execution::state_read_only())
.await
.unwrap()
.value
);
let unregistered = PropAccountContract::new(unregistered_id, fixture.user.clone());
let error = unregistered
.methods()
.start_session(TIER_ID, COLLATERAL, ProlongPeriod::SixHours)
.call_params(CallParameters::new(
COLLATERAL,
fixture.collateral_asset,
u64::MAX,
))
.unwrap()
.with_contract_ids(&[
fixture.deployment.oracle_id,
fixture.deployment.pool_id,
fixture.deployment.registry_id,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap_err();
assert!(
error.to_string().contains("InvalidParent"),
"an unregistered proxy has no parent to authorise: {error:#}"
);
let error = PropAccountProxyContract::new(unregistered_id, fixture.user.clone())
.methods()
.initialize()
.with_contract_ids(&[fixture.deployment.oracle_id, fixture.deployment.pool_id])
.call()
.await
.unwrap_err();
assert!(
error.to_string().contains("InvalidRegistry"),
"only the registry initialises an account: {error:#}"
);
let error = TradeAccountRegistry::new(
fixture.deployment.registry_id,
fixture.outsider.clone(),
)
.methods()
.prop_register_contract(unregistered_id, parent, 99)
.with_contract_ids(&[
fixture.deployment.oracle_id,
unregistered_id,
fixture.deployment.pool_id,
])
.call()
.await
.unwrap_err();
assert!(
error.to_string().contains("NotParent"),
"registration is claimed by the parent itself: {error:#}"
);
TradeAccountRegistry::new(fixture.deployment.registry_id, fixture.user.clone())
.methods()
.prop_register_contract(unregistered_id, parent, 99)
.with_contract_ids(&[
fixture.deployment.oracle_id,
unregistered_id,
fixture.deployment.pool_id,
])
.call()
.await
.unwrap();
assert!(
registry
.methods()
.prop_is_valid(unregistered_id)
.simulate(Execution::state_read_only())
.await
.unwrap()
.value
);
assert_eq!(
registry
.methods()
.prop_parent(unregistered_id)
.simulate(Execution::state_read_only())
.await
.unwrap()
.value,
Some(parent)
);
let error =
TradeAccountRegistry::new(fixture.deployment.registry_id, fixture.user.clone())
.methods()
.prop_register_contract(unregistered_id, parent, 99)
.with_contract_ids(&[
fixture.deployment.oracle_id,
unregistered_id,
fixture.deployment.pool_id,
])
.call()
.await
.unwrap_err();
assert!(
error.to_string().contains("AlreadyRegistered"),
"an account has one parent for life: {error:#}"
);
unregistered
.methods()
.start_session(TIER_ID, COLLATERAL, ProlongPeriod::SixHours)
.call_params(CallParameters::new(
COLLATERAL,
fixture.collateral_asset,
u64::MAX,
))
.unwrap()
.with_contract_ids(&[
fixture.deployment.oracle_id,
fixture.deployment.pool_id,
fixture.deployment.registry_id,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap();
assert!(
fixture
.deployment
.pool
.methods()
.get_session(unregistered_id)
.simulate(Execution::state_read_only())
.await
.unwrap()
.value
.is_some()
);
}
#[tokio::test]
async fn the_pool_refuses_to_open_on_an_account_that_is_still_holding() {
let mut fixture = PropFixture::new().await;
let parent = Identity::Address(fixture.user.address());
let sibling = fixture
.deployment
.deploy_account(&fixture.user, parent, 7)
.await
.unwrap();
let sibling_id = sibling.contract_id();
let sibling_account = PropAccountContract::new(sibling_id, fixture.user.clone());
let expiry = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
+ 86_400;
sibling_account
.methods()
.set_session(ParallelSessionArgs {
nonce: U256::zero(),
session_id: Identity::Address(fixture.user.address()),
expiry: Time { unix: expiry },
contract_ids: vec![],
})
.with_contract_ids(&[fixture.deployment.oracle_id])
.call()
.await
.unwrap();
for (asset, amount) in [
(fixture.collateral_asset, COLLATERAL),
(fixture.base_asset, 5_000),
] {
fixture
.deployer
.force_transfer_to_contract(sibling_id, amount, asset, TxPolicies::default())
.await
.unwrap();
}
let open = CallContractArg {
contract_id: fixture.deployment.pool_id,
function_selector: Bytes(fn_selector!(open_session(u64, ProlongPeriod))),
call_params: CallParams {
coins: COLLATERAL,
asset_id: fixture.collateral_asset,
gas: 30_000_000,
},
call_data: Some(Bytes(call_data!(TIER_ID, ProlongPeriod::SixHours))),
};
let (user_signature, cosigner_signature, calls) =
fixture.signed_calls_for(sibling_id, vec![open]).await;
let error = sibling_account
.methods()
.call_contracts(user_signature, cosigner_signature, calls)
.with_contracts(&[
&fixture.deployment.oracle,
&fixture.deployment.pool,
&fixture.deployment.price_feed,
&fixture.deployment.registry,
&fixture.order_book.order_book,
])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap_err();
assert!(
error.to_string().contains("AccountNotEmpty"),
"the pool counts what it was sent, not what the account happens to hold: {error:#}"
);
sibling_account
.methods()
.start_session(TIER_ID, COLLATERAL, ProlongPeriod::SixHours)
.with_contract_ids(&[
fixture.deployment.oracle_id,
fixture.deployment.pool_id,
fixture.deployment.registry_id,
])
.call_params(CallParameters::new(
COLLATERAL,
fixture.collateral_asset,
u64::MAX,
))
.unwrap()
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await
.unwrap();
assert_eq!(
fixture
.deployer
.try_provider()
.unwrap()
.get_contract_asset_balance(&sibling_id, &fixture.base_asset)
.await
.unwrap(),
0
);
}