Skip to content

Commit

Permalink
Verified registry burns datacap tokens received for successful claim …
Browse files Browse the repository at this point in the history
…extensions (filecoin-project#725)
  • Loading branch information
anorth authored and shamb0 committed Jan 31, 2023
1 parent ac0b1ff commit 8eab50a
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 28 deletions.
21 changes: 14 additions & 7 deletions actors/verifreg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -829,13 +829,12 @@ impl Actor {

// Validate receiver hook payload.
let tokens_received = validate_tokens_received(&params, 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());
Expand All @@ -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.
Expand All @@ -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);
Expand Down
15 changes: 14 additions & 1 deletion actors/verifreg/tests/harness/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,14 +405,27 @@ impl Harness {
expected_alloc_results: BatchReturn,
expected_extension_results: BatchReturn,
expected_alloc_ids: Vec<AllocationID>,
expected_burn: u64,
) -> Result<(), ActorError> {
rt.set_caller(*DATACAP_TOKEN_ACTOR_CODE_ID, *DATACAP_TOKEN_ACTOR_ADDR);
let params = UniversalReceiverParams {
type_: FRC46_TOKEN_TYPE,
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::<VerifregActor>(
Method::UniversalReceiverHook as MethodNum,
&serialize(&params, "hook params").unwrap(),
Expand Down
52 changes: 32 additions & 20 deletions actors/verifreg/tests/verifreg_actor_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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]));
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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]));
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -1273,15 +1283,16 @@ 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;
let new_term = epoch - term_start + MAXIMUM_VERIFIED_ALLOCATION_TERM; // Can get full max term now
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
Expand All @@ -1292,20 +1303,21 @@ 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)];
let payload = make_receiver_hook_token_payload(CLIENT1, vec![], reqs, SIZE);
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();
}
}
}

0 comments on commit 8eab50a

Please sign in to comment.