From 8eab50a6d7d15c6992ddc506de711f50964121b6 Mon Sep 17 00:00:00 2001 From: Alex <445306+anorth@users.noreply.github.com> Date: Mon, 3 Oct 2022 15:51:12 +1100 Subject: [PATCH] Verified registry burns datacap tokens received for successful claim extensions (#725) --- actors/verifreg/src/lib.rs | 21 +++++--- actors/verifreg/tests/harness/mod.rs | 15 +++++- actors/verifreg/tests/verifreg_actor_test.rs | 52 ++++++++++++-------- 3 files changed, 60 insertions(+), 28 deletions(-) diff --git a/actors/verifreg/src/lib.rs b/actors/verifreg/src/lib.rs index 0d047f6ba..6f14e6f89 100644 --- a/actors/verifreg/src/lib.rs +++ b/actors/verifreg/src/lib.rs @@ -829,13 +829,12 @@ impl Actor { // Validate receiver hook payload. let tokens_received = validate_tokens_received(¶ms, my_id)?; - let token_datacap = tokens_to_datacap(&tokens_received.amount); let client = tokens_received.from; // Extract and validate allocation request from the operator data. let reqs: AllocationRequests = deserialize(&tokens_received.operator_data, "allocation requests")?; - let mut allocation_total = DataCap::zero(); + let mut datacap_total = DataCap::zero(); // Construct new allocation records. let mut new_allocs = Vec::with_capacity(reqs.allocations.len()); @@ -853,12 +852,13 @@ impl Actor { term_max: req.term_max, expiration: req.expiration, }); - allocation_total += DataCap::from(req.size.0); + datacap_total += DataCap::from(req.size.0); } let st: State = rt.state()?; let mut claims = st.load_claims(rt.store())?; let mut updated_claims = Vec::<(ClaimID, Claim)>::new(); + let mut extension_total = DataCap::zero(); for req in &reqs.extensions { // Note: we don't check the client address here, by design. // Any client can spend datacap to extend an existing claim. @@ -877,18 +877,25 @@ impl Actor { // The claim's client is not changed to be the address of the token sender. // It remains the original allocation client. updated_claims.push((req.claim, Claim { term_max: req.term_max, ..*claim })); - allocation_total += DataCap::from(claim.size.0); + datacap_total += DataCap::from(claim.size.0); + extension_total += DataCap::from(claim.size.0); } // Allocation size must match the tokens received exactly (we don't return change). - if allocation_total != token_datacap { + let tokens_as_datacap = tokens_to_datacap(&tokens_received.amount); + if datacap_total != tokens_as_datacap { return Err(actor_error!( illegal_argument, "total allocation size {} must match data cap amount received {}", - allocation_total, - token_datacap + datacap_total, + tokens_as_datacap )); } + + // Burn the received datacap tokens spent on extending existing claims. + // The tokens spent on new allocations will be burnt when claimed later, or refunded. + burn(rt, &extension_total)?; + // Partial success isn't supported yet, but these results make space for it in the future. let allocation_results = BatchReturn::ok(new_allocs.len() as u32); let extension_results = BatchReturn::ok(updated_claims.len() as u32); diff --git a/actors/verifreg/tests/harness/mod.rs b/actors/verifreg/tests/harness/mod.rs index 2c43d4422..9bc64ffe6 100644 --- a/actors/verifreg/tests/harness/mod.rs +++ b/actors/verifreg/tests/harness/mod.rs @@ -405,6 +405,7 @@ impl Harness { expected_alloc_results: BatchReturn, expected_extension_results: BatchReturn, expected_alloc_ids: Vec, + expected_burn: u64, ) -> Result<(), ActorError> { rt.set_caller(*DATACAP_TOKEN_ACTOR_CODE_ID, *DATACAP_TOKEN_ACTOR_ADDR); let params = UniversalReceiverParams { @@ -412,7 +413,19 @@ impl Harness { payload: serialize(&payload, "payload").unwrap(), }; - rt.expect_validate_caller_addr(vec![*DATACAP_TOKEN_ACTOR_ADDR]); + if !expected_burn.is_zero() { + rt.expect_send( + DATACAP_TOKEN_ACTOR_ADDR, + ext::datacap::Method::Burn as MethodNum, + RawBytes::serialize(&BurnParams { amount: TokenAmount::from_whole(expected_burn) }) + .unwrap(), + TokenAmount::zero(), + RawBytes::serialize(&BurnReturn { balance: TokenAmount::zero() }).unwrap(), + ExitCode::OK, + ); + } + + rt.expect_validate_caller_addr(vec![DATACAP_TOKEN_ACTOR_ADDR]); let ret = rt.call::( Method::UniversalReceiverHook as MethodNum, &serialize(¶ms, "hook params").unwrap(), diff --git a/actors/verifreg/tests/verifreg_actor_test.rs b/actors/verifreg/tests/verifreg_actor_test.rs index ad841e075..51df2ed09 100644 --- a/actors/verifreg/tests/verifreg_actor_test.rs +++ b/actors/verifreg/tests/verifreg_actor_test.rs @@ -919,7 +919,7 @@ mod datacap { make_alloc_req(&rt, PROVIDER2, SIZE * 2), ]; let payload = make_receiver_hook_token_payload(CLIENT1, reqs.clone(), vec![], SIZE * 3); - h.receive_tokens(&mut rt, payload, BatchReturn::ok(2), BATCH_EMPTY, vec![1, 2]) + h.receive_tokens(&mut rt, payload, BatchReturn::ok(2), BATCH_EMPTY, vec![1, 2], 0) .unwrap(); // Verify allocations in state. @@ -932,7 +932,8 @@ mod datacap { // Make another allocation from a different client let reqs = vec![make_alloc_req(&rt, PROVIDER1, SIZE)]; let payload = make_receiver_hook_token_payload(CLIENT2, reqs.clone(), vec![], SIZE); - h.receive_tokens(&mut rt, payload, BatchReturn::ok(1), BATCH_EMPTY, vec![3]).unwrap(); + h.receive_tokens(&mut rt, payload, BatchReturn::ok(1), BATCH_EMPTY, vec![3], 0) + .unwrap(); // Verify allocations in state. assert_allocation(&rt, CLIENT2, 3, &alloc_from_req(CLIENT2, &reqs[0])); @@ -964,7 +965,8 @@ mod datacap { ]; // Client1 extends both claims let payload = make_receiver_hook_token_payload(CLIENT1, vec![], reqs, SIZE * 3); - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BatchReturn::ok(2), vec![]).unwrap(); + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BatchReturn::ok(2), vec![], SIZE * 3) + .unwrap(); // Verify claims in state. assert_claim(&rt, PROVIDER1, cid1, &Claim { term_max: term_max + 1000, ..claim1 }); @@ -999,8 +1001,15 @@ mod datacap { // CLIENT1 makes two new allocations and extends two existing claims. let payload = make_receiver_hook_token_payload(CLIENT1, alloc_reqs.clone(), ext_reqs, SIZE * 6); - h.receive_tokens(&mut rt, payload, BatchReturn::ok(2), BatchReturn::ok(2), vec![3, 4]) - .unwrap(); + h.receive_tokens( + &mut rt, + payload, + BatchReturn::ok(2), + BatchReturn::ok(2), + vec![3, 4], + claim1.size.0 + claim2.size.0, + ) + .unwrap(); // Verify state. assert_allocation(&rt, CLIENT1, 3, &alloc_from_req(CLIENT1, &alloc_reqs[0])); @@ -1136,7 +1145,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, format!("allocation provider {} must be a miner actor", provider1).as_str(), - h.receive_tokens(&mut rt, payload, BatchReturn::ok(1), BATCH_EMPTY, vec![1]), + h.receive_tokens(&mut rt, payload, BatchReturn::ok(1), BATCH_EMPTY, vec![1], 0), ); h.check_state(&rt); } @@ -1154,7 +1163,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "allocation size 1048575 below minimum 1048576", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); } // Min term too short @@ -1165,7 +1174,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "allocation term min 518399 below limit 518400", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); } // Max term too long @@ -1176,7 +1185,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "allocation term max 5259486 above limit 5259485", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); } // Term minimum greater than maximum @@ -1188,7 +1197,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "allocation term min 2103795 exceeds term max 2103794", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); } // Allocation expires too late @@ -1199,7 +1208,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "allocation expiration 172801 exceeds maximum 172800", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); } // Tokens received doesn't match sum of allocation sizes @@ -1210,7 +1219,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "total allocation size 2097152 must match data cap amount received 2097153", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); } // One bad request fails the lot @@ -1224,7 +1233,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "allocation size 1048575 below minimum 1048576", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); } h.check_state(&rt); @@ -1255,12 +1264,13 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "term_max 5260486 for claim 1 exceeds maximum 5260485 at current epoch 1100", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); // But just on the limit is allowed let reqs = vec![make_extension_req(PROVIDER1, cid1, max_allowed_term)]; let payload = make_receiver_hook_token_payload(CLIENT1, vec![], reqs, SIZE); - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BatchReturn::ok(1), vec![]).unwrap(); + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BatchReturn::ok(1), vec![], SIZE) + .unwrap(); } { // Claim already expired @@ -1273,7 +1283,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_FORBIDDEN, "claim 1 expired at 518600, current epoch 518601", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); // But just at expiration is allowed let epoch = term_start + term_max; @@ -1281,7 +1291,8 @@ mod datacap { rt.set_epoch(epoch); let reqs = vec![make_extension_req(PROVIDER1, cid1, new_term)]; let payload = make_receiver_hook_token_payload(CLIENT1, vec![], reqs, SIZE); - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BatchReturn::ok(1), vec![]).unwrap(); + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BatchReturn::ok(1), vec![], SIZE) + .unwrap(); } { // Extension is zero @@ -1292,7 +1303,7 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "term_max 518500 for claim 1 is not larger than existing term max 518500", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); // Extension is negative let reqs = vec![make_extension_req(PROVIDER1, cid1, term_max - 1)]; @@ -1300,12 +1311,13 @@ mod datacap { expect_abort_contains_message( ExitCode::USR_ILLEGAL_ARGUMENT, "term_max 518499 for claim 1 is not larger than existing term max 518500", - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![]), + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BATCH_EMPTY, vec![], 0), ); // But extension by just 1 epoch is allowed let reqs = vec![make_extension_req(PROVIDER1, cid1, term_max + 1)]; let payload = make_receiver_hook_token_payload(CLIENT1, vec![], reqs, SIZE); - h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BatchReturn::ok(1), vec![]).unwrap(); + h.receive_tokens(&mut rt, payload, BATCH_EMPTY, BatchReturn::ok(1), vec![], SIZE) + .unwrap(); } } }