Skip to content

Commit befa5e1

Browse files
committed
test: Improve V3 billing opt-out test coverage
1 parent c2fafea commit befa5e1

1 file changed

Lines changed: 108 additions & 34 deletions

File tree

  • substrate-node/pallets/pallet-smart-contract/src

substrate-node/pallets/pallet-smart-contract/src/tests.rs

Lines changed: 108 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5371,25 +5371,10 @@ fn test_create_node_contract_on_opted_out_node_admin_succeeds() {
53715371
}
53725372

53735373
#[test]
5374-
fn test_create_node_contract_on_normal_node_unaffected() {
5375-
new_test_ext().execute_with(|| {
5376-
run_to_block(1, None);
5377-
prepare_farm_and_node();
5378-
let node_id = 1;
5379-
5380-
assert_ok!(SmartContractModule::create_node_contract(
5381-
RuntimeOrigin::signed(bob()),
5382-
node_id,
5383-
generate_deployment_hash(),
5384-
get_deployment_data(),
5385-
0,
5386-
None,
5387-
));
5388-
});
5389-
}
5390-
5391-
#[test]
5392-
fn test_create_node_contract_opted_out_empty_admin_list_fails() {
5374+
fn test_create_node_contract_opted_out_farmer_cannot_self_deploy() {
5375+
// The farmer who opted out is NOT automatically an admin — they must be explicitly added.
5376+
// This is a distinct case from test_create_node_contract_on_opted_out_node_non_admin_fails
5377+
// which uses an unrelated account (bob). Here the farmer (alice) tries to deploy on their own node.
53935378
new_test_ext().execute_with(|| {
53945379
run_to_block(1, None);
53955380
prepare_farm_and_node();
@@ -5493,10 +5478,8 @@ fn test_create_rent_contract_on_opted_out_node_admin_succeeds() {
54935478

54945479
#[test]
54955480
fn test_billing_suppressed_for_opted_out_node_contract() {
5496-
// Note: should_bill_contract returns false for a node contract with no resources/IPs/NU/overdraft,
5497-
// so the OCW never submits bill_contract_for_block. We verify billing suppression by directly
5498-
// calling bill_contract and checking that balance is unchanged and state stays Created.
5499-
new_test_ext().execute_with(|| {
5481+
let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0);
5482+
ext.execute_with(|| {
55005483
run_to_block(1, None);
55015484
prepare_farm_and_node();
55025485
let node_id = 1;
@@ -5518,17 +5501,19 @@ fn test_billing_suppressed_for_opted_out_node_contract() {
55185501
));
55195502
let contract_id = 1;
55205503

5504+
push_contract_resources_used(contract_id);
5505+
55215506
let balance_before = Balances::free_balance(&bob());
55225507

5523-
// Directly invoke bill_contract (the on-chain extrinsic path)
5524-
assert_ok!(SmartContractModule::bill_contract_for_block(
5525-
RuntimeOrigin::signed(alice()),
5526-
contract_id,
5527-
));
5508+
// OCW fires at block 11 (billing cycle), but cost is zeroed by should_waive_migration_billing
5509+
pool_state
5510+
.write()
5511+
.should_call_bill_contract(contract_id, Ok(Pays::Yes.into()), 11);
5512+
run_to_block(11, Some(&mut pool_state));
55285513

55295514
let balance_after = Balances::free_balance(&bob());
55305515

5531-
// No charge — billing suppressed for opted-out node
5516+
// No charge despite real resource usage — billing suppressed for opted-out node
55325517
assert_eq!(balance_before, balance_after);
55335518

55345519
// Contract remains in Created state (not pushed to GracePeriod)
@@ -5539,8 +5524,7 @@ fn test_billing_suppressed_for_opted_out_node_contract() {
55395524

55405525
#[test]
55415526
fn test_cancel_contract_on_opted_out_node_zero_final_bill() {
5542-
let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0);
5543-
ext.execute_with(|| {
5527+
new_test_ext().execute_with(|| {
55445528
run_to_block(1, None);
55455529
prepare_farm_and_node();
55465530
let node_id = 1;
@@ -5562,22 +5546,112 @@ fn test_cancel_contract_on_opted_out_node_zero_final_bill() {
55625546
));
55635547
let contract_id = 1;
55645548

5549+
push_contract_resources_used(contract_id);
5550+
55655551
let balance_before = Balances::free_balance(&bob());
55665552

5567-
// Cancel the contract — triggers a final bill_contract call
5553+
// Cancel triggers bill_contract inline (no OCW) with state=Deleted
55685554
assert_ok!(SmartContractModule::cancel_contract(
55695555
RuntimeOrigin::signed(bob()),
55705556
contract_id,
55715557
));
55725558

55735559
let balance_after = Balances::free_balance(&bob());
55745560

5575-
// No charge at cancellation either
5561+
// No charge despite real resource usage — migration billing suppressed at cancellation too
55765562
assert_eq!(balance_before, balance_after);
55775563

55785564
// Contract is cleaned up
55795565
assert!(SmartContractModule::contracts(contract_id).is_none());
5580-
let _ = pool_state;
5566+
});
5567+
}
5568+
5569+
#[test]
5570+
fn test_grace_period_contract_restored_and_billing_free_after_opt_out() {
5571+
// Scenario:
5572+
// 1. charlie deploys on a normal node, runs out of funds → GracePeriod
5573+
// 2. Farmer opts out of v3 billing while contract is in GracePeriod
5574+
// 3. charlie tops up → next billing cycle restores contract to Created
5575+
// 4. Subsequent billing cycle: resources are present but cost is zero (migration window)
5576+
let (mut ext, mut pool_state) = new_test_ext_with_pool_state(0);
5577+
ext.execute_with(|| {
5578+
run_to_block(1, None);
5579+
prepare_farm_and_node();
5580+
let node_id = 1;
5581+
5582+
TFTPriceModule::set_prices(RuntimeOrigin::signed(alice()), 50, 101).unwrap();
5583+
5584+
// charlie (low balance) deploys — node is NOT yet opted out
5585+
assert_ok!(TfgridModule::add_twin_admin(RawOrigin::Root.into(), charlie()));
5586+
assert_ok!(SmartContractModule::create_node_contract(
5587+
RuntimeOrigin::signed(charlie()),
5588+
node_id,
5589+
generate_deployment_hash(),
5590+
get_deployment_data(),
5591+
0,
5592+
None,
5593+
));
5594+
let contract_id = 1;
5595+
5596+
push_contract_resources_used(contract_id);
5597+
5598+
// Cycle 1: charlie can pay
5599+
pool_state
5600+
.write()
5601+
.should_call_bill_contract(contract_id, Ok(Pays::Yes.into()), 11);
5602+
run_to_block(11, Some(&mut pool_state));
5603+
5604+
// Cycle 2: charlie runs out of funds → GracePeriod
5605+
pool_state
5606+
.write()
5607+
.should_call_bill_contract(contract_id, Ok(Pays::Yes.into()), 21);
5608+
run_to_block(21, Some(&mut pool_state));
5609+
5610+
let contract = SmartContractModule::contracts(contract_id).unwrap();
5611+
assert_eq!(contract.state, types::ContractState::GracePeriod(21));
5612+
5613+
// Farmer opts out while contract is in GracePeriod
5614+
assert_ok!(TfgridModule::opt_out_of_v3_billing(
5615+
RuntimeOrigin::signed(alice()),
5616+
node_id,
5617+
));
5618+
5619+
// Cycle 3: charlie still can't pay, stays in GracePeriod
5620+
pool_state
5621+
.write()
5622+
.should_call_bill_contract(contract_id, Ok(Pays::Yes.into()), 31);
5623+
run_to_block(31, Some(&mut pool_state));
5624+
5625+
let contract = SmartContractModule::contracts(contract_id).unwrap();
5626+
assert_eq!(contract.state, types::ContractState::GracePeriod(21));
5627+
5628+
// charlie tops up to cover the pre-opt-out overdraft
5629+
Balances::transfer(RuntimeOrigin::signed(bob()), charlie(), 100000000).unwrap();
5630+
5631+
// Cycle 4: sufficient funds → contract restored to Created
5632+
pool_state
5633+
.write()
5634+
.should_call_bill_contract(contract_id, Ok(Pays::Yes.into()), 41);
5635+
run_to_block(41, Some(&mut pool_state));
5636+
5637+
let contract = SmartContractModule::contracts(contract_id).unwrap();
5638+
assert_eq!(contract.state, types::ContractState::Created);
5639+
5640+
// Cycle 5: contract is Created on opted-out node with resources — billing must be free
5641+
let balance_before = Balances::free_balance(&charlie());
5642+
pool_state
5643+
.write()
5644+
.should_call_bill_contract(contract_id, Ok(Pays::Yes.into()), 51);
5645+
run_to_block(51, Some(&mut pool_state));
5646+
5647+
let balance_after = Balances::free_balance(&charlie());
5648+
5649+
// No charge — migration window active
5650+
assert_eq!(balance_before, balance_after);
5651+
5652+
// Contract stays Created (not pushed back to GracePeriod despite no payment)
5653+
let contract = SmartContractModule::contracts(contract_id).unwrap();
5654+
assert_eq!(contract.state, types::ContractState::Created);
55815655
});
55825656
}
55835657

0 commit comments

Comments
 (0)