Skip to content

Commit

Permalink
Additional tests for synchronous termination and SettleDealPayments (#…
Browse files Browse the repository at this point in the history
…1423)

* rename some tests and comments to no longer reference slashed_epoch which will never be observable

* port cron tick tests for deal termination

* move deal termination tests

* modify generate and publish deal to return proposal inline

* fix bug when settling payments between between publish and activation

* deal termination edge cases

* pr review
  • Loading branch information
alexytsu authored Oct 9, 2023
1 parent 19365bc commit 8cd44ce
Show file tree
Hide file tree
Showing 11 changed files with 412 additions and 160 deletions.
1 change: 1 addition & 0 deletions actors/market/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,7 @@ impl Actor {
completed: false,
payment: TokenAmount::zero(),
});
batch_gen.add_success();
continue;
}
LoadDealState::ProposalExpired(penalty) => {
Expand Down
8 changes: 3 additions & 5 deletions actors/market/tests/activate_deal_failures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ fn fail_when_caller_is_not_the_provider_of_the_deal() {
let rt = setup();
let provider2_addr = Address::new_id(201);
let addrs = MinerAddresses { provider: provider2_addr, ..MinerAddresses::default() };
let deal_id = generate_and_publish_deal(&rt, CLIENT_ADDR, &addrs, start_epoch, end_epoch);
let (deal_id, _) = generate_and_publish_deal(&rt, CLIENT_ADDR, &addrs, start_epoch, end_epoch);

let res = batch_activate_deals_raw(
&rt,
Expand Down Expand Up @@ -105,7 +105,7 @@ fn fail_when_deal_has_already_been_activated() {
let sector_expiry = end_epoch + 100;

let rt = setup();
let deal_id = generate_and_publish_deal(
let (deal_id, _) = generate_and_publish_deal(
&rt,
CLIENT_ADDR,
&MinerAddresses::default(),
Expand Down Expand Up @@ -141,16 +141,14 @@ fn fail_when_deal_has_already_been_expired() {
let sector_expiry = end_epoch + 100;

let rt = setup();
let deal_id = generate_and_publish_deal(
let (deal_id, deal_proposal) = generate_and_publish_deal(
&rt,
CLIENT_ADDR,
&MinerAddresses::default(),
start_epoch,
end_epoch,
);

let deal_proposal = get_deal_proposal(&rt, deal_id);

let current = end_epoch + 25;
rt.set_epoch(current);
rt.expect_send_simple(
Expand Down
6 changes: 2 additions & 4 deletions actors/market/tests/cron_tick_timedout_deals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,13 @@ const END_EPOCH: ChainEpoch = START_EPOCH + 200 * EPOCHS_IN_DAY;
#[test]
fn timed_out_deal_is_slashed_and_deleted() {
let rt = setup();
let deal_id = generate_and_publish_deal(
let (deal_id, deal_proposal) = generate_and_publish_deal(
&rt,
CLIENT_ADDR,
&MinerAddresses::default(),
START_EPOCH,
END_EPOCH,
);
let deal_proposal = get_deal_proposal(&rt, deal_id);

let c_escrow = get_balance(&rt, &CLIENT_ADDR).balance;

Expand Down Expand Up @@ -64,14 +63,13 @@ fn timed_out_deal_is_slashed_and_deleted() {
fn publishing_timed_out_deal_again_should_work_after_cron_tick_as_it_should_no_longer_be_pending() {
const START_EPOCH: ChainEpoch = 0;
let rt = setup();
let deal_id = generate_and_publish_deal(
let (deal_id, deal_proposal) = generate_and_publish_deal(
&rt,
CLIENT_ADDR,
&MinerAddresses::default(),
START_EPOCH,
END_EPOCH,
);
let deal_proposal = get_deal_proposal(&rt, deal_id);

// publishing will fail as it will be in pending
let deal_proposal2 = generate_deal_and_add_funds(
Expand Down
254 changes: 254 additions & 0 deletions actors/market/tests/deal_termination.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
use fil_actor_market::{DealSettlementSummary, EX_DEAL_EXPIRED};
use fil_actors_runtime::EPOCHS_IN_DAY;
use fvm_shared::{clock::ChainEpoch, econ::TokenAmount, error::ExitCode};

mod harness;
use harness::*;
use num_traits::Zero;

#[test]
fn deal_is_terminated() {
struct Case {
name: &'static str,
deal_start: ChainEpoch,
deal_end: ChainEpoch,
activation_epoch: ChainEpoch,
termination_epoch: ChainEpoch,
termination_payment: TokenAmount,
}

let cases = [
Case {
name: "deal is terminated after the startepoch and then settle payments before the endepoch",
deal_start: 10,
deal_end: 10 + 200 * EPOCHS_IN_DAY,
activation_epoch: 5,
termination_epoch: 15,
termination_payment: TokenAmount::from_atto(50), // (15 - 10) * 10 as deal storage fee is 10 per epoch
},
Case {
name: "deal is terminated after the startepoch and then settle payments after the endepoch",
deal_start: 10,
deal_end: 10 + 200 * EPOCHS_IN_DAY,
activation_epoch: 5,
termination_epoch: 15,
termination_payment: TokenAmount::from_atto(50), // (15 - 10) * 10 as deal storage fee is 10 per epoch
},
Case {
name: "deal is terminated at the startepoch and then settle payments before the endepoch",
deal_start: 10,
deal_end: 10 + 200 * EPOCHS_IN_DAY,
activation_epoch: 5,
termination_epoch: 10,
termination_payment: TokenAmount::zero(), // (10 - 10) * 10
},
Case {
name: "deal is terminated at the startepoch and then settle payments after the endepoch",
deal_start: 10,
deal_end: 10 + 200 * EPOCHS_IN_DAY,
activation_epoch: 5,
termination_epoch: 10,
termination_payment: TokenAmount::zero(), // (10 - 10) * 10
},
Case {
name: "deal is terminated at the activationepoch and then settle payments before the startepoch",
deal_start: 10,
deal_end: 10 + 200 * EPOCHS_IN_DAY,
activation_epoch: 5,
termination_epoch: 5,
termination_payment: TokenAmount::zero(), // (10 - 10) * 10
},
Case {
name: "deal is terminated at the activationepoch and then settle payments after the startepoch",
deal_start: 10,
deal_end: 10 + 200 * EPOCHS_IN_DAY,
activation_epoch: 5,
termination_epoch: 5,
termination_payment: TokenAmount::zero(), // (10 - 10) * 10
},
Case {
name: "deal is terminated at the activationepoch and then settle payments after the endepoch",
deal_start: 10,
deal_end: 10 + 200 * EPOCHS_IN_DAY,
activation_epoch: 5,
termination_epoch: 5,
termination_payment: TokenAmount::zero(), // (10 - 10) * 10
},
];

for tc in cases {
eprintln!("running test case: {}", tc.name);
let rt = setup();

// publish and activate
rt.set_epoch(tc.activation_epoch);
let (deal_id, deal_proposal) = publish_and_activate_deal(
&rt,
CLIENT_ADDR,
&MinerAddresses::default(),
tc.deal_start,
tc.deal_end,
tc.activation_epoch,
tc.deal_end,
);

// terminate
rt.set_epoch(tc.termination_epoch);
let (pay, slashed) =
terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[deal_id]);

assert_eq!(tc.termination_payment, pay);
assert_eq!(deal_proposal.provider_collateral, slashed);

assert_deal_deleted(&rt, deal_id, &deal_proposal);

// assert that trying to settle is always a no-op after termination

// immediately after termination
settle_deal_payments_no_change(&rt, PROVIDER_ADDR, CLIENT_ADDR, PROVIDER_ADDR, &[deal_id]);
let mut epoch = tc.termination_epoch + 1;
rt.set_epoch(epoch);

// at deal start (if deal was terminated before start)
if epoch < tc.deal_start {
epoch = tc.deal_start;
rt.set_epoch(epoch);
settle_deal_payments_no_change(
&rt,
PROVIDER_ADDR,
CLIENT_ADDR,
PROVIDER_ADDR,
&[deal_id],
);
}

// during deal (if deal was terminated before end)
if epoch < tc.deal_end {
epoch = tc.deal_end;
rt.set_epoch(epoch);
settle_deal_payments_no_change(
&rt,
PROVIDER_ADDR,
CLIENT_ADDR,
PROVIDER_ADDR,
&[deal_id],
);
}

if epoch < tc.deal_end + 1 {
epoch = tc.deal_end + 1;
rt.set_epoch(epoch);
settle_deal_payments_no_change(
&rt,
PROVIDER_ADDR,
CLIENT_ADDR,
PROVIDER_ADDR,
&[deal_id],
);
}

check_state(&rt);
}
}

#[test]
fn settle_payments_then_terminate_deal_in_the_same_epoch() {
let start_epoch = ChainEpoch::from(50);
let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY;
let termination_epoch = start_epoch + 100;
let sector_expiry = end_epoch + 100;
let deal_duration = termination_epoch - start_epoch;

let rt = setup();

let (deal_id, proposal) = publish_and_activate_deal(
&rt,
CLIENT_ADDR,
&MinerAddresses::default(),
start_epoch,
end_epoch,
0,
sector_expiry,
);

let client_before = get_balance(&rt, &CLIENT_ADDR);
let provider_before = get_balance(&rt, &PROVIDER_ADDR);

// settle payments then terminate
rt.set_epoch(termination_epoch);
let expected_payment = deal_duration * &proposal.storage_price_per_epoch;
let ret = settle_deal_payments(&rt, PROVIDER_ADDR, &[deal_id]);
assert_eq!(
ret.settlements.get(0).unwrap(),
&DealSettlementSummary { completed: false, payment: expected_payment.clone() }
);
terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[deal_id]);
assert_deal_deleted(&rt, deal_id, &proposal);

// end state should be equivalent to only calling termination
let client_after = get_balance(&rt, &CLIENT_ADDR);
let provider_after = get_balance(&rt, &PROVIDER_ADDR);
let expected_slash = proposal.provider_collateral;
assert_eq!(&client_after.balance, &(client_before.balance - &expected_payment));
assert!(&client_after.locked.is_zero());
assert_eq!(
&provider_after.balance,
&(provider_before.balance + &expected_payment - expected_slash)
);
assert!(&provider_after.locked.is_zero());

check_state(&rt);
}

#[test]
fn terminate_a_deal_then_settle_it_in_the_same_epoch() {
let start_epoch = ChainEpoch::from(50);
let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY;
let termination_epoch = start_epoch + 100;
let sector_expiry = end_epoch + 100;

let rt = setup();

let (deal_id, proposal) = publish_and_activate_deal(
&rt,
CLIENT_ADDR,
&MinerAddresses::default(),
start_epoch,
end_epoch,
0,
sector_expiry,
);

// terminate then attempt to settle payment
rt.set_epoch(termination_epoch);
terminate_deals_and_assert_balances(&rt, CLIENT_ADDR, PROVIDER_ADDR, &[deal_id]);
let ret = settle_deal_payments(&rt, PROVIDER_ADDR, &[deal_id]);
assert_eq!(ret.results.codes(), vec![EX_DEAL_EXPIRED]);
assert_deal_deleted(&rt, deal_id, &proposal);

check_state(&rt);
}

#[test]
fn terminating_a_deal_before_activation_fails() {
let rt = setup();
let addrs = MinerAddresses::default();

let start_epoch = ChainEpoch::from(50);
let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY;
let publish_epoch = start_epoch - 3;
let termination_epoch = start_epoch - 2;
let activation_epoch = start_epoch - 1;

// publish
rt.set_epoch(publish_epoch);
let (deal, _) = generate_and_publish_deal(&rt, CLIENT_ADDR, &addrs, start_epoch, end_epoch);

// terminate before activation
rt.set_epoch(termination_epoch);
terminate_deals_expect_abort(&rt, addrs.provider, &[deal], ExitCode::USR_ILLEGAL_ARGUMENT);

// can still successfully activate
rt.set_epoch(activation_epoch);
activate_deals(&rt, end_epoch, addrs.provider, activation_epoch, &[deal]);
}
Loading

0 comments on commit 8cd44ce

Please sign in to comment.