From 231a8688c3ccb67a6e8edf1cf8653396672a4822 Mon Sep 17 00:00:00 2001 From: David Himmelstrup Date: Tue, 3 May 2022 15:50:36 +0200 Subject: [PATCH] Split market actor unit tests into separate modules. (#308) * Split tests into three separate modules. * Move tests into another submodule. --- actors/market/tests/activate_deal_failures.rs | 115 +++ actors/market/tests/harness.rs | 68 +- actors/market/tests/market_actor_test.rs | 738 +----------------- .../tests/on_miner_sectors_terminate.rs | 414 ++++++++++ .../tests/publish_storage_deals_failures.rs | 206 +++++ 5 files changed, 804 insertions(+), 737 deletions(-) create mode 100644 actors/market/tests/activate_deal_failures.rs create mode 100644 actors/market/tests/on_miner_sectors_terminate.rs create mode 100644 actors/market/tests/publish_storage_deals_failures.rs diff --git a/actors/market/tests/activate_deal_failures.rs b/actors/market/tests/activate_deal_failures.rs new file mode 100644 index 000000000..0a8792ace --- /dev/null +++ b/actors/market/tests/activate_deal_failures.rs @@ -0,0 +1,115 @@ +// Copyright 2019-2022 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use fil_actor_market::{ActivateDealsParams, Actor as MarketActor, Method}; +use fil_actors_runtime::network::EPOCHS_IN_DAY; +use fil_actors_runtime::test_utils::*; +use fvm_ipld_encoding::RawBytes; +use fvm_shared::address::Address; +use fvm_shared::deal::DealID; +use fvm_shared::error::ExitCode; + +mod harness; +use harness::*; + +#[cfg(test)] +mod test_activate_deal_failures { + use super::*; + + #[test] + fn fail_when_caller_is_not_the_provider_of_the_deal() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + + let mut rt = setup(); + let provider2_addr = Address::new_id(201); + let addrs = MinerAddresses { provider: provider2_addr, ..MinerAddresses::default() }; + let deal_id = + generate_and_publish_deal(&mut rt, CLIENT_ADDR, &addrs, start_epoch, end_epoch); + + let params = ActivateDealsParams { deal_ids: vec![deal_id], sector_expiry }; + + rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); + rt.set_caller(*MINER_ACTOR_CODE_ID, PROVIDER_ADDR); + expect_abort( + ExitCode::USR_FORBIDDEN, + rt.call::( + Method::ActivateDeals as u64, + &RawBytes::serialize(params).unwrap(), + ), + ); + + rt.verify(); + check_state(&rt); + } + + #[test] + fn fail_when_caller_is_not_a_storage_miner_actor() { + let mut rt = setup(); + rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, PROVIDER_ADDR); + + let params = ActivateDealsParams { deal_ids: vec![], sector_expiry: 0 }; + expect_abort( + ExitCode::USR_FORBIDDEN, + rt.call::( + Method::ActivateDeals as u64, + &RawBytes::serialize(params).unwrap(), + ), + ); + + rt.verify(); + check_state(&rt); + } + + #[test] + fn fail_when_deal_has_not_been_published_before() { + let mut rt = setup(); + let params = ActivateDealsParams { deal_ids: vec![DealID::from(42u32)], sector_expiry: 0 }; + + rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); + rt.set_caller(*MINER_ACTOR_CODE_ID, PROVIDER_ADDR); + expect_abort( + ExitCode::USR_NOT_FOUND, + rt.call::( + Method::ActivateDeals as u64, + &RawBytes::serialize(params).unwrap(), + ), + ); + + rt.verify(); + check_state(&rt); + } + + #[test] + fn fail_when_deal_has_already_been_activated() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + + let mut rt = setup(); + let deal_id = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ); + activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, 0, &[deal_id]); + + rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); + rt.set_caller(*MINER_ACTOR_CODE_ID, PROVIDER_ADDR); + let params = ActivateDealsParams { deal_ids: vec![deal_id], sector_expiry }; + expect_abort( + ExitCode::USR_ILLEGAL_ARGUMENT, + rt.call::( + Method::ActivateDeals as u64, + &RawBytes::serialize(params).unwrap(), + ), + ); + + rt.verify(); + check_state(&rt); + } +} diff --git a/actors/market/tests/harness.rs b/actors/market/tests/harness.rs index be9c7bf44..07db2a7d8 100644 --- a/actors/market/tests/harness.rs +++ b/actors/market/tests/harness.rs @@ -1,3 +1,4 @@ +#![allow(dead_code)] use cid::Cid; use num_traits::{FromPrimitive, Zero}; use std::collections::HashMap; @@ -12,10 +13,12 @@ use fil_actor_market::{ use fil_actor_power::{CurrentTotalPowerReturn, Method as PowerMethod}; use fil_actor_reward::Method as RewardMethod; use fil_actor_verifreg::UseBytesParams; -use fil_actors_runtime::runtime::{Policy, Runtime}; use fil_actors_runtime::{ - test_utils::*, ActorError, SetMultimap, BURNT_FUNDS_ACTOR_ADDR, CRON_ACTOR_ADDR, - REWARD_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, SYSTEM_ACTOR_ADDR, + network::EPOCHS_IN_DAY, + runtime::{Policy, Runtime}, + test_utils::*, + ActorError, SetMultimap, BURNT_FUNDS_ACTOR_ADDR, CRON_ACTOR_ADDR, REWARD_ACTOR_ADDR, + STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, SYSTEM_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR, }; use fvm_ipld_encoding::{to_vec, RawBytes}; @@ -578,6 +581,65 @@ pub fn assert_deal_deleted(rt: &mut MockRuntime, deal_id: DealID, p: DealProposa assert!(!pending_deals.contains_key(&BytesKey(p_cid.to_bytes())).unwrap()); } +pub fn assert_deal_failure( + add_funds: bool, + post_setup: F, + exit_code: ExitCode, + sig_result: Result<(), anyhow::Error>, +) where + F: FnOnce(&mut MockRuntime, &mut DealProposal), +{ + let current_epoch = ChainEpoch::from(5); + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + + let mut rt = setup(); + let mut deal_proposal = if add_funds { + generate_deal_and_add_funds( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ) + } else { + generate_deal_proposal(CLIENT_ADDR, PROVIDER_ADDR, start_epoch, end_epoch) + }; + deal_proposal.verified_deal = false; + rt.set_epoch(current_epoch); + post_setup(&mut rt, &mut deal_proposal); + + rt.expect_validate_caller_type(vec![*ACCOUNT_ACTOR_CODE_ID, *MULTISIG_ACTOR_CODE_ID]); + expect_provider_control_address(&mut rt, PROVIDER_ADDR, OWNER_ADDR, WORKER_ADDR); + expect_query_network_info(&mut rt); + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, WORKER_ADDR); + + let buf = RawBytes::serialize(deal_proposal.clone()).expect("failed to marshal deal proposal"); + let sig = Signature::new_bls("does not matter".as_bytes().to_vec()); + rt.expect_verify_signature(ExpectedVerifySig { + sig: sig.clone(), + signer: deal_proposal.client, + plaintext: buf.to_vec(), + result: sig_result, + }); + + let params: PublishStorageDealsParams = PublishStorageDealsParams { + deals: vec![ClientDealProposal { proposal: deal_proposal, client_signature: sig }], + }; + + assert_eq!( + exit_code, + rt.call::( + Method::PublishStorageDeals as u64, + &RawBytes::serialize(params).unwrap(), + ) + .unwrap_err() + .exit_code() + ); + rt.verify(); + // TODO: actor.checkState(rt) +} + pub fn process_epoch(start_epoch: ChainEpoch, deal_id: DealID) -> ChainEpoch { let policy = Policy::default(); gen_rand_next_epoch(&policy, start_epoch, deal_id) diff --git a/actors/market/tests/market_actor_test.rs b/actors/market/tests/market_actor_test.rs index 92551931f..0f9ca036f 100644 --- a/actors/market/tests/market_actor_test.rs +++ b/actors/market/tests/market_actor_test.rs @@ -1,15 +1,11 @@ // Copyright 2019-2022 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use std::convert::TryInto; - use fil_actor_market::balance_table::BALANCE_TABLE_BITWIDTH; -use fil_actor_market::policy::deal_provider_collateral_bounds; use fil_actor_market::{ - ext, ActivateDealsParams, Actor as MarketActor, ClientDealProposal, DealMetaArray, - DealProposal, Label, Method, OnMinerSectorsTerminateParams, PublishStorageDealsParams, - PublishStorageDealsReturn, State, WithdrawBalanceParams, PROPOSALS_AMT_BITWIDTH, - STATES_AMT_BITWIDTH, + ext, ActivateDealsParams, Actor as MarketActor, ClientDealProposal, DealMetaArray, Label, + Method, PublishStorageDealsParams, PublishStorageDealsReturn, State, WithdrawBalanceParams, + PROPOSALS_AMT_BITWIDTH, STATES_AMT_BITWIDTH, }; use fil_actor_verifreg::UseBytesParams; use fil_actors_runtime::cbor::deserialize; @@ -32,10 +28,8 @@ use fvm_shared::econ::TokenAmount; use fvm_shared::error::ExitCode; use fvm_shared::piece::PaddedPieceSize; use fvm_shared::sector::StoragePower; -use fvm_shared::{HAMT_BIT_WIDTH, METHOD_CONSTRUCTOR, METHOD_SEND, TOTAL_FILECOIN}; +use fvm_shared::{HAMT_BIT_WIDTH, METHOD_CONSTRUCTOR, METHOD_SEND}; -use anyhow::anyhow; -use cid::Cid; use num_traits::FromPrimitive; mod harness; @@ -828,386 +822,6 @@ fn publish_a_deal_after_activating_a_previous_deal_which_has_a_start_epoch_far_i check_state(&rt); } -// Converted from https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1274 -#[test] -fn terminate_multiple_deals_from_multiple_providers() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - let current_epoch = 5; - - let provider2 = Address::new_id(501); - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - let [deal1, deal2, deal3]: [DealID; 3] = (end_epoch..end_epoch + 3) - .map(|epoch| { - generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - epoch, - ) - }) - .collect::>() - .try_into() - .unwrap(); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1, deal2, deal3]); - - let addrs = MinerAddresses { provider: provider2, ..MinerAddresses::default() }; - let deal4 = generate_and_publish_deal(&mut rt, CLIENT_ADDR, &addrs, start_epoch, end_epoch); - let deal5 = generate_and_publish_deal(&mut rt, CLIENT_ADDR, &addrs, start_epoch, end_epoch + 1); - activate_deals(&mut rt, sector_expiry, provider2, current_epoch, &[deal4, deal5]); - - terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); - assert_deals_terminated(&mut rt, current_epoch, &[deal1]); - assert_deals_not_terminated(&mut rt, &[deal2, deal3, deal4, deal5]); - - terminate_deals(&mut rt, provider2, &[deal5]); - assert_deals_terminated(&mut rt, current_epoch, &[deal5]); - assert_deals_not_terminated(&mut rt, &[deal2, deal3, deal4]); - - terminate_deals(&mut rt, PROVIDER_ADDR, &[deal2, deal3]); - assert_deals_terminated(&mut rt, current_epoch, &[deal2, deal3]); - assert_deals_not_terminated(&mut rt, &[deal4]); - - terminate_deals(&mut rt, provider2, &[deal4]); - assert_deals_terminated(&mut rt, current_epoch, &[deal4]); -} - -// Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1312 -#[test] -fn ignore_deal_proposal_that_does_not_exist() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - let current_epoch = 5; - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - let deal1 = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1]); - - terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1, 42]); - - let s = get_deal_state(&mut rt, deal1); - assert_eq!(s.slash_epoch, current_epoch); -} - -// Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1326 -#[test] -fn terminate_valid_deals_along_with_just_expired_deal() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - let current_epoch = 5; - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - let deal1 = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ); - let deal2 = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch + 1, - ); - let deal3 = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch - 1, - ); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1, deal2, deal3]); - - let new_epoch = end_epoch - 1; - rt.set_epoch(new_epoch); - - terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1, deal2, deal3]); - assert_deals_terminated(&mut rt, new_epoch, &[deal1, deal2]); - assert_deals_not_terminated(&mut rt, &[deal3]); -} -// Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1346 -#[test] -fn terminate_valid_deals_along_with_expired_and_cleaned_up_deal() { - let deal_updates_interval = Policy::default().deal_updates_interval; - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - let current_epoch = 5; - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - let deal1 = generate_deal_and_add_funds( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ); - let deal2 = generate_deal_and_add_funds( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch - deal_updates_interval, - ); - - rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, WORKER_ADDR); - let deal_ids = publish_deals(&mut rt, &MinerAddresses::default(), &[deal1, deal2.clone()]); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &deal_ids); - - let new_epoch = end_epoch - 1; - rt.set_epoch(new_epoch); - cron_tick(&mut rt); - - terminate_deals(&mut rt, PROVIDER_ADDR, &deal_ids); - assert_deals_terminated(&mut rt, new_epoch, &deal_ids[0..0]); - assert_deal_deleted(&mut rt, deal_ids[1], deal2); -} - -// Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1369 -#[test] -fn terminating_a_deal_the_second_time_does_not_change_its_slash_epoch() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - let current_epoch = 5; - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - let deal1 = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1]); - - // terminating the deal so slash epoch is the current epoch - terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); - - // set a new epoch and terminate again -> however slash epoch will still be the old epoch. - rt.set_epoch(current_epoch + 1); - terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); - let s = get_deal_state(&mut rt, deal1); - assert_eq!(s.slash_epoch, current_epoch); -} - -// Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1387 -#[test] -fn terminating_new_deals_and_an_already_terminated_deal_only_terminates_the_new_deals() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - let current_epoch = 5; - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - // provider1 publishes deal1 and 2 and deal3 -> deal3 has the lowest endepoch - let deals: Vec = [end_epoch, end_epoch + 1, end_epoch - 1] - .iter() - .map(|&epoch| { - generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - epoch, - ) - }) - .collect(); - let [deal1, deal2, deal3]: [DealID; 3] = deals.as_slice().try_into().unwrap(); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &deals); - - // terminating the deal so slash epoch is the current epoch - terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); - - // set a new epoch and terminate again -> however slash epoch will still be the old epoch. - let new_epoch = current_epoch + 1; - rt.set_epoch(new_epoch); - terminate_deals(&mut rt, PROVIDER_ADDR, &deals); - - let s1 = get_deal_state(&mut rt, deal1); - assert_eq!(s1.slash_epoch, current_epoch); - - let s2 = get_deal_state(&mut rt, deal2); - assert_eq!(s2.slash_epoch, new_epoch); - - let s3 = get_deal_state(&mut rt, deal3); - assert_eq!(s3.slash_epoch, new_epoch); -} - -// Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1415 -#[test] -fn do_not_terminate_deal_if_end_epoch_is_equal_to_or_less_than_current_epoch() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - let current_epoch = 5; - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - // deal1 has endepoch equal to current epoch when terminate is called - let deal1 = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1]); - rt.set_epoch(end_epoch); - terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); - assert_deals_not_terminated(&mut rt, &[deal1]); - - // deal2 has end epoch less than current epoch when terminate is called - rt.set_epoch(current_epoch); - let deal2 = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch + 1, - end_epoch, - ); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal2]); - rt.set_epoch(end_epoch + 1); - terminate_deals(&mut rt, PROVIDER_ADDR, &[deal2]); - assert_deals_not_terminated(&mut rt, &[deal2]); -} - -// Converted from: https://github.com/filecoin-project/specs-actors/blob/master/actors/builtin/market/market_test.go#L1436 -#[test] -fn fail_when_caller_is_not_a_storage_miner_actor() { - let mut rt = setup(); - rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); - rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, PROVIDER_ADDR); - let params = OnMinerSectorsTerminateParams { epoch: rt.epoch, deal_ids: vec![] }; - - // XXX: Which exit code is correct: SYS_FORBIDDEN(8) or USR_FORBIDDEN(18)? - assert_eq!( - ExitCode::USR_FORBIDDEN, - rt.call::( - Method::OnMinerSectorsTerminate as u64, - &RawBytes::serialize(params).unwrap(), - ) - .unwrap_err() - .exit_code() - ); -} - -// Converted from: https://github.com/filecoin-project/specs-actors/blob/master/actors/builtin/market/market_test.go#L1448 -#[test] -fn fail_when_caller_is_not_the_provider_of_the_deal() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - let current_epoch = 5; - - let provider2 = Address::new_id(501); - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - let deal = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal]); - - // XXX: Difference between go messages: 't0501' has turned into 'f0501'. - let ret = terminate_deals_raw(&mut rt, provider2, &[deal]); - expect_abort_contains_message( - ExitCode::USR_ILLEGAL_STATE, - "caller f0501 is not the provider f0102 of deal 0", - ret, - ); -} - -// Converted from: https://github.com/filecoin-project/specs-actors/blob/master/actors/builtin/market/market_test.go#L1468 -#[test] -fn fail_when_deal_has_been_published_but_not_activated() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let current_epoch = 5; - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - let deal = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ); - - let ret = terminate_deals_raw(&mut rt, PROVIDER_ADDR, &[deal]); - expect_abort_contains_message(ExitCode::USR_ILLEGAL_ARGUMENT, "no state for deal", ret); - rt.verify(); -} - -// Converted from: https://github.com/filecoin-project/specs-actors/blob/master/actors/builtin/market/market_test.go#L1485 -#[test] -fn termination_of_all_deals_should_fail_when_one_deal_fails() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - let current_epoch = 5; - - let mut rt = setup(); - rt.set_epoch(current_epoch); - - // deal1 would terminate but deal2 will fail because deal2 has not been activated - let deal1 = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1]); - let deal2 = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch + 1, - ); - - let ret = terminate_deals_raw(&mut rt, PROVIDER_ADDR, &[deal1, deal2]); - expect_abort_contains_message(ExitCode::USR_ILLEGAL_ARGUMENT, "no state for deal", ret); - rt.verify(); - - // verify deal1 has not been terminated - assert_deals_not_terminated(&mut rt, &[deal1]); -} - #[test] fn publish_a_deal_with_enough_collateral_when_circulating_supply_is_superior_to_zero() { let policy = Policy::default(); @@ -1449,248 +1063,6 @@ fn active_deals_multiple_times_with_different_providers() { check_state(&rt); } -fn assert_deal_failure( - add_funds: bool, - post_setup: F, - exit_code: ExitCode, - sig_result: Result<(), anyhow::Error>, -) where - F: FnOnce(&mut MockRuntime, &mut DealProposal), -{ - let current_epoch = ChainEpoch::from(5); - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - - let mut rt = setup(); - let mut deal_proposal = if add_funds { - generate_deal_and_add_funds( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ) - } else { - generate_deal_proposal(CLIENT_ADDR, PROVIDER_ADDR, start_epoch, end_epoch) - }; - deal_proposal.verified_deal = false; - rt.set_epoch(current_epoch); - post_setup(&mut rt, &mut deal_proposal); - - rt.expect_validate_caller_type(vec![*ACCOUNT_ACTOR_CODE_ID, *MULTISIG_ACTOR_CODE_ID]); - expect_provider_control_address(&mut rt, PROVIDER_ADDR, OWNER_ADDR, WORKER_ADDR); - expect_query_network_info(&mut rt); - rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, WORKER_ADDR); - - let buf = RawBytes::serialize(deal_proposal.clone()).expect("failed to marshal deal proposal"); - let sig = Signature::new_bls("does not matter".as_bytes().to_vec()); - rt.expect_verify_signature(ExpectedVerifySig { - sig: sig.clone(), - signer: deal_proposal.client, - plaintext: buf.to_vec(), - result: sig_result, - }); - - let params: PublishStorageDealsParams = PublishStorageDealsParams { - deals: vec![ClientDealProposal { proposal: deal_proposal, client_signature: sig }], - }; - - assert_eq!( - exit_code, - rt.call::( - Method::PublishStorageDeals as u64, - &RawBytes::serialize(params).unwrap(), - ) - .unwrap_err() - .exit_code() - ); - rt.verify(); - // TODO: actor.checkState(rt) -} - -#[cfg(test)] -mod publish_storage_deals_failures { - use super::*; - - #[test] - fn deal_end_after_deal_start() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.start_epoch = 10; - d.end_epoch = 9; - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn current_epoch_greater_than_start_epoch() { - let f = |rt: &mut MockRuntime, d: &mut DealProposal| { - d.start_epoch = rt.epoch - 1; - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn deal_duration_greater_than_max_deal_duration() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.start_epoch = ChainEpoch::from(10); - d.end_epoch = d.start_epoch + (540 * EPOCHS_IN_DAY) + 1 - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn negative_price_per_epoch() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.storage_price_per_epoch = TokenAmount::from(-1); - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn price_per_epoch_greater_than_total_filecoin() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.storage_price_per_epoch = TOTAL_FILECOIN.clone() + 1; - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn negative_provider_collateral() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.provider_collateral = TokenAmount::from(-1); - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn provider_collateral_greater_than_max_collateral() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.provider_collateral = TOTAL_FILECOIN.clone() + 1; - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn provider_collateral_less_than_bound() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - let power = StoragePower::from_i128(1 << 50).unwrap(); - let (provider_min, _) = deal_provider_collateral_bounds( - &Policy::default(), - PaddedPieceSize(2048), - &BigInt::from(0u8), - &BigInt::from(0u8), - &power, - ); - d.provider_collateral = provider_min - 1; - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn negative_client_collateral() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.client_collateral = TokenAmount::from(-1); - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn client_collateral_greater_than_max_collateral() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.client_collateral = TOTAL_FILECOIN.clone() + 1; - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn client_does_not_have_enough_balance_for_collateral() { - let f = |rt: &mut MockRuntime, d: &mut DealProposal| { - add_participant_funds(rt, CLIENT_ADDR, d.client_balance_requirement() - 1); - add_provider_funds(rt, d.provider_collateral.clone(), &MinerAddresses::default()); - }; - assert_deal_failure(false, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn provider_does_not_have_enough_balance_for_collateral() { - let f = |rt: &mut MockRuntime, d: &mut DealProposal| { - add_participant_funds(rt, CLIENT_ADDR, d.client_balance_requirement()); - add_provider_funds(rt, d.provider_collateral.clone() - 1, &MinerAddresses::default()); - }; - assert_deal_failure(false, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn client_address_does_not_exist() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.client = Address::new_id(1); - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn unable_to_resolve_client_address() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.client = new_bls_addr(1); - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn signature_is_invalid() { - let f = |_rt: &mut MockRuntime, _d: &mut DealProposal| {}; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Err(anyhow!("error"))); - } - - #[test] - fn no_entry_for_client_in_locked_balance_table() { - let f = |rt: &mut MockRuntime, d: &mut DealProposal| { - add_provider_funds(rt, d.provider_collateral.clone(), &MinerAddresses::default()); - }; - assert_deal_failure(false, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn no_entry_for_provider_in_locked_balance_table() { - let f = |rt: &mut MockRuntime, d: &mut DealProposal| { - add_participant_funds(rt, CLIENT_ADDR, d.client_balance_requirement()); - }; - assert_deal_failure(false, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn bad_piece_cid() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.piece_cid = Cid::default(); - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn zero_piece_size() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.piece_size = PaddedPieceSize(0u64); - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn piece_size_less_than_128_bytes() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.piece_size = PaddedPieceSize(64u64); - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } - - #[test] - fn piece_size_is_not_a_power_of_2() { - let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { - d.piece_size = PaddedPieceSize(254u64); - }; - assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); - } -} - // Converted from: https://github.com/filecoin-project/specs-actors/blob/master/actors/builtin/market/market_test.go#L1519 #[test] fn fail_when_deal_is_activated_but_proposal_is_not_found() { @@ -1754,108 +1126,6 @@ fn fail_when_deal_update_epoch_is_in_the_future() { check_state(&rt); } -#[cfg(test)] -mod test_activate_deal_failures { - use super::*; - - #[test] - fn fail_when_caller_is_not_the_provider_of_the_deal() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - - let mut rt = setup(); - let provider2_addr = Address::new_id(201); - let addrs = MinerAddresses { provider: provider2_addr, ..MinerAddresses::default() }; - let deal_id = - generate_and_publish_deal(&mut rt, CLIENT_ADDR, &addrs, start_epoch, end_epoch); - - let params = ActivateDealsParams { deal_ids: vec![deal_id], sector_expiry }; - - rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); - rt.set_caller(*MINER_ACTOR_CODE_ID, PROVIDER_ADDR); - expect_abort( - ExitCode::USR_FORBIDDEN, - rt.call::( - Method::ActivateDeals as u64, - &RawBytes::serialize(params).unwrap(), - ), - ); - - rt.verify(); - check_state(&rt); - } - - #[test] - fn fail_when_caller_is_not_a_storage_miner_actor() { - let mut rt = setup(); - rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); - rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, PROVIDER_ADDR); - - let params = ActivateDealsParams { deal_ids: vec![], sector_expiry: 0 }; - expect_abort( - ExitCode::USR_FORBIDDEN, - rt.call::( - Method::ActivateDeals as u64, - &RawBytes::serialize(params).unwrap(), - ), - ); - - rt.verify(); - check_state(&rt); - } - - #[test] - fn fail_when_deal_has_not_been_published_before() { - let mut rt = setup(); - let params = ActivateDealsParams { deal_ids: vec![DealID::from(42u32)], sector_expiry: 0 }; - - rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); - rt.set_caller(*MINER_ACTOR_CODE_ID, PROVIDER_ADDR); - expect_abort( - ExitCode::USR_NOT_FOUND, - rt.call::( - Method::ActivateDeals as u64, - &RawBytes::serialize(params).unwrap(), - ), - ); - - rt.verify(); - check_state(&rt); - } - - #[test] - fn fail_when_deal_has_already_been_activated() { - let start_epoch = 10; - let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; - let sector_expiry = end_epoch + 100; - - let mut rt = setup(); - let deal_id = generate_and_publish_deal( - &mut rt, - CLIENT_ADDR, - &MinerAddresses::default(), - start_epoch, - end_epoch, - ); - activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, 0, &[deal_id]); - - rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); - rt.set_caller(*MINER_ACTOR_CODE_ID, PROVIDER_ADDR); - let params = ActivateDealsParams { deal_ids: vec![deal_id], sector_expiry }; - expect_abort( - ExitCode::USR_ILLEGAL_ARGUMENT, - rt.call::( - Method::ActivateDeals as u64, - &RawBytes::serialize(params).unwrap(), - ), - ); - - rt.verify(); - check_state(&rt); - } -} - #[test] fn crontick_for_a_deal_at_its_start_epoch_results_in_zero_payment_and_no_slashing() { let start_epoch = ChainEpoch::from(50); diff --git a/actors/market/tests/on_miner_sectors_terminate.rs b/actors/market/tests/on_miner_sectors_terminate.rs new file mode 100644 index 000000000..7f66a3128 --- /dev/null +++ b/actors/market/tests/on_miner_sectors_terminate.rs @@ -0,0 +1,414 @@ +// Copyright 2019-2022 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use std::convert::TryInto; + +use fil_actor_market::{Actor as MarketActor, Method, OnMinerSectorsTerminateParams}; +use fil_actors_runtime::network::EPOCHS_IN_DAY; +use fil_actors_runtime::runtime::Policy; +use fil_actors_runtime::test_utils::*; +use fvm_ipld_encoding::RawBytes; +use fvm_shared::address::Address; +use fvm_shared::deal::DealID; +use fvm_shared::error::ExitCode; + +mod harness; +use harness::*; + +#[cfg(test)] +mod on_miner_sectors_terminate { + use super::*; + + // Converted from https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1274 + #[test] + fn terminate_multiple_deals_from_multiple_providers() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + let current_epoch = 5; + + let provider2 = Address::new_id(501); + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + let [deal1, deal2, deal3]: [DealID; 3] = (end_epoch..end_epoch + 3) + .map(|epoch| { + generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + epoch, + ) + }) + .collect::>() + .try_into() + .unwrap(); + activate_deals( + &mut rt, + sector_expiry, + PROVIDER_ADDR, + current_epoch, + &[deal1, deal2, deal3], + ); + + let addrs = MinerAddresses { provider: provider2, ..MinerAddresses::default() }; + let deal4 = generate_and_publish_deal(&mut rt, CLIENT_ADDR, &addrs, start_epoch, end_epoch); + let deal5 = + generate_and_publish_deal(&mut rt, CLIENT_ADDR, &addrs, start_epoch, end_epoch + 1); + activate_deals(&mut rt, sector_expiry, provider2, current_epoch, &[deal4, deal5]); + + terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); + assert_deals_terminated(&mut rt, current_epoch, &[deal1]); + assert_deals_not_terminated(&mut rt, &[deal2, deal3, deal4, deal5]); + + terminate_deals(&mut rt, provider2, &[deal5]); + assert_deals_terminated(&mut rt, current_epoch, &[deal5]); + assert_deals_not_terminated(&mut rt, &[deal2, deal3, deal4]); + + terminate_deals(&mut rt, PROVIDER_ADDR, &[deal2, deal3]); + assert_deals_terminated(&mut rt, current_epoch, &[deal2, deal3]); + assert_deals_not_terminated(&mut rt, &[deal4]); + + terminate_deals(&mut rt, provider2, &[deal4]); + assert_deals_terminated(&mut rt, current_epoch, &[deal4]); + } + + // Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1312 + #[test] + fn ignore_deal_proposal_that_does_not_exist() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + let current_epoch = 5; + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + let deal1 = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ); + activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1]); + + terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1, 42]); + + let s = get_deal_state(&mut rt, deal1); + assert_eq!(s.slash_epoch, current_epoch); + } + + // Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1326 + #[test] + fn terminate_valid_deals_along_with_just_expired_deal() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + let current_epoch = 5; + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + let deal1 = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ); + let deal2 = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch + 1, + ); + let deal3 = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch - 1, + ); + activate_deals( + &mut rt, + sector_expiry, + PROVIDER_ADDR, + current_epoch, + &[deal1, deal2, deal3], + ); + + let new_epoch = end_epoch - 1; + rt.set_epoch(new_epoch); + + terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1, deal2, deal3]); + assert_deals_terminated(&mut rt, new_epoch, &[deal1, deal2]); + assert_deals_not_terminated(&mut rt, &[deal3]); + } + // Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1346 + #[test] + fn terminate_valid_deals_along_with_expired_and_cleaned_up_deal() { + let deal_updates_interval = Policy::default().deal_updates_interval; + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + let current_epoch = 5; + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + let deal1 = generate_deal_and_add_funds( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ); + let deal2 = generate_deal_and_add_funds( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch - deal_updates_interval, + ); + + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, WORKER_ADDR); + let deal_ids = publish_deals(&mut rt, &MinerAddresses::default(), &[deal1, deal2.clone()]); + activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &deal_ids); + + let new_epoch = end_epoch - 1; + rt.set_epoch(new_epoch); + cron_tick(&mut rt); + + terminate_deals(&mut rt, PROVIDER_ADDR, &deal_ids); + assert_deals_terminated(&mut rt, new_epoch, &deal_ids[0..0]); + assert_deal_deleted(&mut rt, deal_ids[1], deal2); + } + + // Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1369 + #[test] + fn terminating_a_deal_the_second_time_does_not_change_its_slash_epoch() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + let current_epoch = 5; + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + let deal1 = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ); + activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1]); + + // terminating the deal so slash epoch is the current epoch + terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); + + // set a new epoch and terminate again -> however slash epoch will still be the old epoch. + rt.set_epoch(current_epoch + 1); + terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); + let s = get_deal_state(&mut rt, deal1); + assert_eq!(s.slash_epoch, current_epoch); + } + + // Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1387 + #[test] + fn terminating_new_deals_and_an_already_terminated_deal_only_terminates_the_new_deals() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + let current_epoch = 5; + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + // provider1 publishes deal1 and 2 and deal3 -> deal3 has the lowest endepoch + let deals: Vec = [end_epoch, end_epoch + 1, end_epoch - 1] + .iter() + .map(|&epoch| { + generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + epoch, + ) + }) + .collect(); + let [deal1, deal2, deal3]: [DealID; 3] = deals.as_slice().try_into().unwrap(); + activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &deals); + + // terminating the deal so slash epoch is the current epoch + terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); + + // set a new epoch and terminate again -> however slash epoch will still be the old epoch. + let new_epoch = current_epoch + 1; + rt.set_epoch(new_epoch); + terminate_deals(&mut rt, PROVIDER_ADDR, &deals); + + let s1 = get_deal_state(&mut rt, deal1); + assert_eq!(s1.slash_epoch, current_epoch); + + let s2 = get_deal_state(&mut rt, deal2); + assert_eq!(s2.slash_epoch, new_epoch); + + let s3 = get_deal_state(&mut rt, deal3); + assert_eq!(s3.slash_epoch, new_epoch); + } + + // Converted from: https://github.com/filecoin-project/specs-actors/blob/d56b240af24517443ce1f8abfbdab7cb22d331f1/actors/builtin/market/market_test.go#L1415 + #[test] + fn do_not_terminate_deal_if_end_epoch_is_equal_to_or_less_than_current_epoch() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + let current_epoch = 5; + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + // deal1 has endepoch equal to current epoch when terminate is called + let deal1 = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ); + activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1]); + rt.set_epoch(end_epoch); + terminate_deals(&mut rt, PROVIDER_ADDR, &[deal1]); + assert_deals_not_terminated(&mut rt, &[deal1]); + + // deal2 has end epoch less than current epoch when terminate is called + rt.set_epoch(current_epoch); + let deal2 = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch + 1, + end_epoch, + ); + activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal2]); + rt.set_epoch(end_epoch + 1); + terminate_deals(&mut rt, PROVIDER_ADDR, &[deal2]); + assert_deals_not_terminated(&mut rt, &[deal2]); + } + + // Converted from: https://github.com/filecoin-project/specs-actors/blob/master/actors/builtin/market/market_test.go#L1436 + #[test] + fn fail_when_caller_is_not_a_storage_miner_actor() { + let mut rt = setup(); + rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, PROVIDER_ADDR); + let params = OnMinerSectorsTerminateParams { epoch: rt.epoch, deal_ids: vec![] }; + + // XXX: Which exit code is correct: SYS_FORBIDDEN(8) or USR_FORBIDDEN(18)? + assert_eq!( + ExitCode::USR_FORBIDDEN, + rt.call::( + Method::OnMinerSectorsTerminate as u64, + &RawBytes::serialize(params).unwrap(), + ) + .unwrap_err() + .exit_code() + ); + } + + // Converted from: https://github.com/filecoin-project/specs-actors/blob/master/actors/builtin/market/market_test.go#L1448 + #[test] + fn fail_when_caller_is_not_the_provider_of_the_deal() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + let current_epoch = 5; + + let provider2 = Address::new_id(501); + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + let deal = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ); + activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal]); + + // XXX: Difference between go messages: 't0501' has turned into 'f0501'. + let ret = terminate_deals_raw(&mut rt, provider2, &[deal]); + expect_abort_contains_message( + ExitCode::USR_ILLEGAL_STATE, + "caller f0501 is not the provider f0102 of deal 0", + ret, + ); + } + + // Converted from: https://github.com/filecoin-project/specs-actors/blob/master/actors/builtin/market/market_test.go#L1468 + #[test] + fn fail_when_deal_has_been_published_but_not_activated() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let current_epoch = 5; + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + let deal = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ); + + let ret = terminate_deals_raw(&mut rt, PROVIDER_ADDR, &[deal]); + expect_abort_contains_message(ExitCode::USR_ILLEGAL_ARGUMENT, "no state for deal", ret); + rt.verify(); + } + + // Converted from: https://github.com/filecoin-project/specs-actors/blob/master/actors/builtin/market/market_test.go#L1485 + #[test] + fn termination_of_all_deals_should_fail_when_one_deal_fails() { + let start_epoch = 10; + let end_epoch = start_epoch + 200 * EPOCHS_IN_DAY; + let sector_expiry = end_epoch + 100; + let current_epoch = 5; + + let mut rt = setup(); + rt.set_epoch(current_epoch); + + // deal1 would terminate but deal2 will fail because deal2 has not been activated + let deal1 = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch, + ); + activate_deals(&mut rt, sector_expiry, PROVIDER_ADDR, current_epoch, &[deal1]); + let deal2 = generate_and_publish_deal( + &mut rt, + CLIENT_ADDR, + &MinerAddresses::default(), + start_epoch, + end_epoch + 1, + ); + + let ret = terminate_deals_raw(&mut rt, PROVIDER_ADDR, &[deal1, deal2]); + expect_abort_contains_message(ExitCode::USR_ILLEGAL_ARGUMENT, "no state for deal", ret); + rt.verify(); + + // verify deal1 has not been terminated + assert_deals_not_terminated(&mut rt, &[deal1]); + } +} diff --git a/actors/market/tests/publish_storage_deals_failures.rs b/actors/market/tests/publish_storage_deals_failures.rs new file mode 100644 index 000000000..b519b5198 --- /dev/null +++ b/actors/market/tests/publish_storage_deals_failures.rs @@ -0,0 +1,206 @@ +// Copyright 2019-2022 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use fil_actor_market::policy::deal_provider_collateral_bounds; +use fil_actor_market::DealProposal; +use fil_actors_runtime::network::EPOCHS_IN_DAY; +use fil_actors_runtime::runtime::Policy; +use fil_actors_runtime::test_utils::*; +use fvm_shared::address::Address; +use fvm_shared::bigint::BigInt; +use fvm_shared::clock::ChainEpoch; +use fvm_shared::econ::TokenAmount; +use fvm_shared::error::ExitCode; +use fvm_shared::piece::PaddedPieceSize; +use fvm_shared::sector::StoragePower; +use fvm_shared::TOTAL_FILECOIN; + +use anyhow::anyhow; +use cid::Cid; +use num_traits::FromPrimitive; + +mod harness; +use harness::*; + +#[cfg(test)] +mod publish_storage_deals_failures { + use super::*; + + #[test] + fn deal_end_after_deal_start() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.start_epoch = 10; + d.end_epoch = 9; + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn current_epoch_greater_than_start_epoch() { + let f = |rt: &mut MockRuntime, d: &mut DealProposal| { + d.start_epoch = rt.epoch - 1; + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn deal_duration_greater_than_max_deal_duration() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.start_epoch = ChainEpoch::from(10); + d.end_epoch = d.start_epoch + (540 * EPOCHS_IN_DAY) + 1 + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn negative_price_per_epoch() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.storage_price_per_epoch = TokenAmount::from(-1); + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn price_per_epoch_greater_than_total_filecoin() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.storage_price_per_epoch = TOTAL_FILECOIN.clone() + 1; + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn negative_provider_collateral() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.provider_collateral = TokenAmount::from(-1); + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn provider_collateral_greater_than_max_collateral() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.provider_collateral = TOTAL_FILECOIN.clone() + 1; + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn provider_collateral_less_than_bound() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + let power = StoragePower::from_i128(1 << 50).unwrap(); + let (provider_min, _) = deal_provider_collateral_bounds( + &Policy::default(), + PaddedPieceSize(2048), + &BigInt::from(0u8), + &BigInt::from(0u8), + &power, + ); + d.provider_collateral = provider_min - 1; + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn negative_client_collateral() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.client_collateral = TokenAmount::from(-1); + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn client_collateral_greater_than_max_collateral() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.client_collateral = TOTAL_FILECOIN.clone() + 1; + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn client_does_not_have_enough_balance_for_collateral() { + let f = |rt: &mut MockRuntime, d: &mut DealProposal| { + add_participant_funds(rt, CLIENT_ADDR, d.client_balance_requirement() - 1); + add_provider_funds(rt, d.provider_collateral.clone(), &MinerAddresses::default()); + }; + assert_deal_failure(false, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn provider_does_not_have_enough_balance_for_collateral() { + let f = |rt: &mut MockRuntime, d: &mut DealProposal| { + add_participant_funds(rt, CLIENT_ADDR, d.client_balance_requirement()); + add_provider_funds(rt, d.provider_collateral.clone() - 1, &MinerAddresses::default()); + }; + assert_deal_failure(false, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn client_address_does_not_exist() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.client = Address::new_id(1); + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn unable_to_resolve_client_address() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.client = new_bls_addr(1); + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn signature_is_invalid() { + let f = |_rt: &mut MockRuntime, _d: &mut DealProposal| {}; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Err(anyhow!("error"))); + } + + #[test] + fn no_entry_for_client_in_locked_balance_table() { + let f = |rt: &mut MockRuntime, d: &mut DealProposal| { + add_provider_funds(rt, d.provider_collateral.clone(), &MinerAddresses::default()); + }; + assert_deal_failure(false, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn no_entry_for_provider_in_locked_balance_table() { + let f = |rt: &mut MockRuntime, d: &mut DealProposal| { + add_participant_funds(rt, CLIENT_ADDR, d.client_balance_requirement()); + }; + assert_deal_failure(false, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn bad_piece_cid() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.piece_cid = Cid::default(); + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn zero_piece_size() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.piece_size = PaddedPieceSize(0u64); + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn piece_size_less_than_128_bytes() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.piece_size = PaddedPieceSize(64u64); + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } + + #[test] + fn piece_size_is_not_a_power_of_2() { + let f = |_rt: &mut MockRuntime, d: &mut DealProposal| { + d.piece_size = PaddedPieceSize(254u64); + }; + assert_deal_failure(true, f, ExitCode::USR_ILLEGAL_ARGUMENT, Ok(())); + } +}