@@ -33,6 +33,8 @@ use fvm_shared::econ::TokenAmount;
33
33
// The following errors are particular cases of illegal state.
34
34
// They're not expected to ever happen, but if they do, distinguished codes can help us
35
35
// diagnose the problem.
36
+
37
+ pub use beneficiary:: * ;
36
38
use fil_actors_runtime:: cbor:: { deserialize, serialize, serialize_vec} ;
37
39
use fil_actors_runtime:: runtime:: builtins:: Type ;
38
40
use fvm_shared:: error:: * ;
@@ -60,6 +62,7 @@ use crate::Code::Blake2b256;
60
62
#[ cfg( feature = "fil-actor" ) ]
61
63
fil_actors_runtime:: wasm_trampoline!( Actor ) ;
62
64
65
+ mod beneficiary;
63
66
mod bitfield_queue;
64
67
mod commd;
65
68
mod deadline_assignment;
@@ -118,6 +121,8 @@ pub enum Method {
118
121
ProveReplicaUpdates = 27 ,
119
122
PreCommitSectorBatch2 = 28 ,
120
123
ProveReplicaUpdates2 = 29 ,
124
+ ChangeBeneficiary = 30 ,
125
+ GetBeneficiary = 31 ,
121
126
}
122
127
123
128
pub const ERR_BALANCE_INVARIANTS_BROKEN : ExitCode = ExitCode :: new ( 1000 ) ;
@@ -314,6 +319,15 @@ impl Actor {
314
319
new_address
315
320
) ) ;
316
321
}
322
+
323
+ // Change beneficiary address to new owner if current beneficiary address equal to old owner address
324
+ if info. beneficiary == info. owner {
325
+ info. beneficiary = pending_address;
326
+ }
327
+ // Cancel pending beneficiary term change when the owner changes
328
+ info. pending_beneficiary_term = None ;
329
+
330
+ // Set the new owner address
317
331
info. owner = pending_address;
318
332
}
319
333
@@ -3235,13 +3249,13 @@ impl Actor {
3235
3249
) ) ;
3236
3250
}
3237
3251
3238
- let ( info, newly_vested , fee_to_burn , available_balance , state) =
3252
+ let ( info, amount_withdrawn , newly_vested , fee_to_burn , state) =
3239
3253
rt. transaction ( |state : & mut State , rt| {
3240
- let info = get_miner_info ( rt. store ( ) , state) ?;
3254
+ let mut info = get_miner_info ( rt. store ( ) , state) ?;
3241
3255
3242
3256
// Only the owner is allowed to withdraw the balance as it belongs to/is controlled by the owner
3243
3257
// and not the worker.
3244
- rt. validate_immediate_caller_is ( & [ info. owner ] ) ?;
3258
+ rt. validate_immediate_caller_is ( & [ info. owner , info . beneficiary ] ) ?;
3245
3259
3246
3260
// Ensure we don't have any pending terminations.
3247
3261
if !state. early_terminations . is_empty ( ) {
@@ -3273,36 +3287,197 @@ impl Actor {
3273
3287
// Verify unlocked funds cover both InitialPledgeRequirement and FeeDebt
3274
3288
// and repay fee debt now.
3275
3289
let fee_to_burn = repay_debts_or_abort ( rt, state) ?;
3276
-
3277
- Ok ( ( info, newly_vested, fee_to_burn, available_balance, state. clone ( ) ) )
3290
+ let mut amount_withdrawn =
3291
+ std:: cmp:: min ( & available_balance, & params. amount_requested ) ;
3292
+ if amount_withdrawn. is_negative ( ) {
3293
+ return Err ( actor_error ! (
3294
+ illegal_state,
3295
+ "negative amount to withdraw: {}" ,
3296
+ amount_withdrawn
3297
+ ) ) ;
3298
+ }
3299
+ if info. beneficiary != info. owner {
3300
+ // remaining_quota always zero and positive
3301
+ let remaining_quota = info. beneficiary_term . available ( rt. curr_epoch ( ) ) ;
3302
+ amount_withdrawn = std:: cmp:: min ( amount_withdrawn, & remaining_quota) ;
3303
+ if amount_withdrawn. is_positive ( ) {
3304
+ info. beneficiary_term . used_quota += amount_withdrawn;
3305
+ state. save_info ( rt. store ( ) , & info) . map_err ( |e| {
3306
+ e. downcast_default (
3307
+ ExitCode :: USR_ILLEGAL_STATE ,
3308
+ "failed to save miner info" ,
3309
+ )
3310
+ } ) ?;
3311
+ }
3312
+ Ok ( ( info, amount_withdrawn. clone ( ) , newly_vested, fee_to_burn, state. clone ( ) ) )
3313
+ } else {
3314
+ Ok ( ( info, amount_withdrawn. clone ( ) , newly_vested, fee_to_burn, state. clone ( ) ) )
3315
+ }
3278
3316
} ) ?;
3279
3317
3280
- let amount_withdrawn = std:: cmp:: min ( & available_balance, & params. amount_requested ) ;
3281
- if amount_withdrawn. is_negative ( ) {
3282
- return Err ( actor_error ! (
3283
- illegal_state,
3284
- "negative amount to withdraw: {}" ,
3285
- amount_withdrawn
3286
- ) ) ;
3287
- }
3288
- if amount_withdrawn > & available_balance {
3289
- return Err ( actor_error ! (
3290
- illegal_state,
3291
- "amount to withdraw {} < available {}" ,
3292
- amount_withdrawn,
3293
- available_balance
3294
- ) ) ;
3295
- }
3296
-
3297
3318
if amount_withdrawn. is_positive ( ) {
3298
- rt. send ( info. owner , METHOD_SEND , RawBytes :: default ( ) , amount_withdrawn. clone ( ) ) ?;
3319
+ rt. send ( info. beneficiary , METHOD_SEND , RawBytes :: default ( ) , amount_withdrawn. clone ( ) ) ?;
3299
3320
}
3300
3321
3301
3322
burn_funds ( rt, fee_to_burn) ?;
3302
3323
notify_pledge_changed ( rt, & newly_vested. neg ( ) ) ?;
3303
3324
3304
3325
state. check_balance_invariants ( & rt. current_balance ( ) ) . map_err ( balance_invariants_broken) ?;
3305
- Ok ( WithdrawBalanceReturn { amount_withdrawn : amount_withdrawn. clone ( ) } )
3326
+ Ok ( WithdrawBalanceReturn { amount_withdrawn } )
3327
+ }
3328
+
3329
+ /// Proposes or confirms a change of beneficiary address.
3330
+ /// A proposal must be submitted by the owner, and takes effect after approval of both the proposed beneficiary and current beneficiary,
3331
+ /// if applicable, any current beneficiary that has time and quota remaining.
3332
+ //// See FIP-0029, https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0029.md
3333
+ fn change_beneficiary < BS , RT > (
3334
+ rt : & mut RT ,
3335
+ params : ChangeBeneficiaryParams ,
3336
+ ) -> Result < ( ) , ActorError >
3337
+ where
3338
+ BS : Blockstore ,
3339
+ RT : Runtime < BS > ,
3340
+ {
3341
+ let caller = rt. message ( ) . caller ( ) ;
3342
+ let new_beneficiary =
3343
+ Address :: new_id ( rt. resolve_address ( & params. new_beneficiary ) . ok_or_else ( || {
3344
+ actor_error ! (
3345
+ illegal_argument,
3346
+ "unable to resolve address: {}" ,
3347
+ params. new_beneficiary
3348
+ )
3349
+ } ) ?) ;
3350
+
3351
+ rt. transaction ( |state : & mut State , rt| {
3352
+ let mut info = get_miner_info ( rt. store ( ) , state) ?;
3353
+ if caller == info. owner {
3354
+ // This is a ChangeBeneficiary proposal when the caller is Owner
3355
+ if new_beneficiary != info. owner {
3356
+ // When beneficiary is not owner, just check quota in params,
3357
+ // Expiration maybe an expiration value, but wouldn't cause problem, just the new beneficiary never get any benefit
3358
+ if !params. new_quota . is_positive ( ) {
3359
+ return Err ( actor_error ! (
3360
+ illegal_argument,
3361
+ "beneficial quota {} must bigger than zero" ,
3362
+ params. new_quota
3363
+ ) ) ;
3364
+ }
3365
+ } else {
3366
+ // Expiration/quota must set to 0 while change beneficiary to owner
3367
+ if !params. new_quota . is_zero ( ) {
3368
+ return Err ( actor_error ! (
3369
+ illegal_argument,
3370
+ "owner beneficial quota {} must be zero" ,
3371
+ params. new_quota
3372
+ ) ) ;
3373
+ }
3374
+
3375
+ if params. new_expiration != 0 {
3376
+ return Err ( actor_error ! (
3377
+ illegal_argument,
3378
+ "owner beneficial expiration {} must be zero" ,
3379
+ params. new_expiration
3380
+ ) ) ;
3381
+ }
3382
+ }
3383
+
3384
+ let mut pending_beneficiary_term = PendingBeneficiaryChange :: new (
3385
+ new_beneficiary,
3386
+ params. new_quota ,
3387
+ params. new_expiration ,
3388
+ ) ;
3389
+ if info. beneficiary_term . available ( rt. curr_epoch ( ) ) . is_zero ( ) {
3390
+ // Set current beneficiary to approved when current beneficiary is not effective
3391
+ pending_beneficiary_term. approved_by_beneficiary = true ;
3392
+ }
3393
+ info. pending_beneficiary_term = Some ( pending_beneficiary_term) ;
3394
+ } else if let Some ( pending_term) = & info. pending_beneficiary_term {
3395
+ if caller != info. beneficiary && caller != pending_term. new_beneficiary {
3396
+ return Err ( actor_error ! (
3397
+ forbidden,
3398
+ "message caller {} is neither proposal beneficiary{} nor current beneficiary{}" ,
3399
+ caller,
3400
+ params. new_beneficiary,
3401
+ info. beneficiary
3402
+ ) ) ;
3403
+ }
3404
+
3405
+ if pending_term. new_beneficiary != new_beneficiary {
3406
+ return Err ( actor_error ! (
3407
+ illegal_argument,
3408
+ "new beneficiary address must be equal expect {}, but got {}" ,
3409
+ pending_term. new_beneficiary,
3410
+ params. new_beneficiary
3411
+ ) ) ;
3412
+ }
3413
+ if pending_term. new_quota != params. new_quota {
3414
+ return Err ( actor_error ! (
3415
+ illegal_argument,
3416
+ "new beneficiary quota must be equal expect {}, but got {}" ,
3417
+ pending_term. new_quota,
3418
+ params. new_quota
3419
+ ) ) ;
3420
+ }
3421
+ if pending_term. new_expiration != params. new_expiration {
3422
+ return Err ( actor_error ! (
3423
+ illegal_argument,
3424
+ "new beneficiary expire date must be equal expect {}, but got {}" ,
3425
+ pending_term. new_expiration,
3426
+ params. new_expiration
3427
+ ) ) ;
3428
+ }
3429
+ } else {
3430
+ return Err ( actor_error ! ( forbidden, "No changeBeneficiary proposal exists" ) ) ;
3431
+ }
3432
+
3433
+ if let Some ( pending_term) = info. pending_beneficiary_term . as_mut ( ) {
3434
+ if caller == info. beneficiary {
3435
+ pending_term. approved_by_beneficiary = true
3436
+ }
3437
+
3438
+ if caller == new_beneficiary {
3439
+ pending_term. approved_by_nominee = true
3440
+ }
3441
+
3442
+ if pending_term. approved_by_beneficiary && pending_term. approved_by_nominee {
3443
+ //approved by both beneficiary and nominee
3444
+ if new_beneficiary != info. beneficiary {
3445
+ //if beneficiary changes, reset used_quota to zero
3446
+ info. beneficiary_term . used_quota = TokenAmount :: zero ( ) ;
3447
+ }
3448
+ info. beneficiary = new_beneficiary;
3449
+ info. beneficiary_term . quota = pending_term. new_quota . clone ( ) ;
3450
+ info. beneficiary_term . expiration = pending_term. new_expiration ;
3451
+ // clear the pending proposal
3452
+ info. pending_beneficiary_term = None ;
3453
+ }
3454
+ }
3455
+
3456
+ state. save_info ( rt. store ( ) , & info) . map_err ( |e| {
3457
+ e. downcast_default ( ExitCode :: USR_ILLEGAL_STATE , "failed to save miner info" )
3458
+ } ) ?;
3459
+ Ok ( ( ) )
3460
+ } )
3461
+ }
3462
+
3463
+ // GetBeneficiary retrieves the currently active and proposed beneficiary information.
3464
+ // This method is for use by other actors (such as those acting as beneficiaries),
3465
+ // and to abstract the state representation for clients.
3466
+ fn get_beneficiary < BS , RT > ( rt : & mut RT ) -> Result < GetBeneficiaryReturn , ActorError >
3467
+ where
3468
+ BS : Blockstore ,
3469
+ RT : Runtime < BS > ,
3470
+ {
3471
+ rt. validate_immediate_caller_accept_any ( ) ?;
3472
+ let info = rt. transaction ( |state : & mut State , rt| get_miner_info ( rt. store ( ) , state) ) ?;
3473
+
3474
+ Ok ( GetBeneficiaryReturn {
3475
+ active : ActiveBeneficiary {
3476
+ beneficiary : info. beneficiary ,
3477
+ term : info. beneficiary_term ,
3478
+ } ,
3479
+ proposed : info. pending_beneficiary_term ,
3480
+ } )
3306
3481
}
3307
3482
3308
3483
fn repay_debt < BS , RT > ( rt : & mut RT ) -> Result < ( ) , ActorError >
@@ -4689,6 +4864,14 @@ impl ActorCode for Actor {
4689
4864
let res = Self :: prove_replica_updates2 ( rt, cbor:: deserialize_params ( params) ?) ?;
4690
4865
Ok ( RawBytes :: serialize ( res) ?)
4691
4866
}
4867
+ Some ( Method :: ChangeBeneficiary ) => {
4868
+ Self :: change_beneficiary ( rt, cbor:: deserialize_params ( params) ?) ?;
4869
+ Ok ( RawBytes :: default ( ) )
4870
+ }
4871
+ Some ( Method :: GetBeneficiary ) => {
4872
+ let res = Self :: get_beneficiary ( rt) ?;
4873
+ Ok ( RawBytes :: serialize ( res) ?)
4874
+ }
4692
4875
None => Err ( actor_error ! ( unhandled_message, "Invalid method" ) ) ,
4693
4876
}
4694
4877
}
0 commit comments