@@ -24,6 +24,16 @@ use crate::kernel::{Block, ClassifyResult, Context as _, ExecutionError, Kernel}
2424use crate :: machine:: { BURNT_FUNDS_ACTOR_ID , Machine , REWARD_ACTOR_ID } ;
2525use crate :: trace:: ExecutionTrace ;
2626
27+ /// The list of options for executing an fvm message
28+ pub struct ExecutionOptions < F > {
29+ /// Indicates if the execution should always revert regardless the transaction outcome.
30+ /// This is useful for read only executions.
31+ pub always_revert : bool ,
32+ /// The hook to handle the transaction gas fee at the end.
33+ /// If this is not provided, a default fvm implementation will be used.
34+ pub txn_gas_hook : F ,
35+ }
36+
2737/// The default [`Executor`].
2838///
2939/// # Warning
@@ -112,11 +122,25 @@ where
112122 pub fn execute_message_with_revert (
113123 & mut self ,
114124 msg : Message ,
115- mut apply_kind : ApplyKind ,
125+ apply_kind : ApplyKind ,
116126 raw_length : usize ,
117127 always_revert : bool ,
118128 ) -> anyhow:: Result < ApplyRet > {
119- if always_revert {
129+ let options = ExecutionOptions {
130+ always_revert,
131+ txn_gas_hook : default_gas_hook,
132+ } ;
133+ self . execute_message_with_options ( msg, apply_kind, raw_length, options)
134+ }
135+
136+ pub fn execute_message_with_options < F : TxnGasHook > (
137+ & mut self ,
138+ msg : Message ,
139+ mut apply_kind : ApplyKind ,
140+ raw_length : usize ,
141+ options : ExecutionOptions < F > ,
142+ ) -> anyhow:: Result < ApplyRet > {
143+ if options. always_revert {
120144 // The apply kind is always hard coded to implicit if the call is expected to revert.
121145 // This will bypass some checks and gas deduction in `preflight_messages`.
122146 apply_kind = ApplyKind :: Implicit ;
@@ -213,7 +237,7 @@ where
213237 false ,
214238 )
215239 } ,
216- always_revert,
240+ options . always_revert ,
217241 ) ;
218242
219243 let ( res, machine) = match cm. finish ( ) {
@@ -320,14 +344,15 @@ where
320344 } ;
321345
322346 match apply_kind {
323- ApplyKind :: Explicit => self . finish_message (
347+ ApplyKind :: Explicit => self . finish_message_with_hook (
324348 sender_id,
325349 msg,
326350 receipt,
327351 failure_info,
328352 gas_cost,
329353 exec_trace,
330354 events,
355+ options. txn_gas_hook ,
331356 ) ,
332357 ApplyKind :: Implicit => Ok ( ApplyRet {
333358 msg_receipt : receipt,
@@ -484,7 +509,7 @@ where
484509 }
485510
486511 #[ allow( clippy:: too_many_arguments) ]
487- fn finish_message (
512+ fn finish_message_with_hook < F > (
488513 & mut self ,
489514 sender_id : ActorID ,
490515 msg : Message ,
@@ -493,25 +518,20 @@ where
493518 gas_cost : TokenAmount ,
494519 exec_trace : ExecutionTrace ,
495520 events : Vec < StampedEvent > ,
496- ) -> anyhow:: Result < ApplyRet > {
497- // NOTE: we don't support old network versions in the FVM, so we always burn.
498- let GasOutputs {
499- base_fee_burn,
500- over_estimation_burn,
501- miner_penalty,
502- miner_tip,
503- refund,
504- gas_refund,
505- gas_burned,
506- } = GasOutputs :: compute (
521+ gas_hook : F ,
522+ ) -> anyhow:: Result < ApplyRet >
523+ where
524+ F : TxnGasHook ,
525+ {
526+ let gas_outputs = GasOutputs :: compute (
507527 receipt. gas_used ,
508528 msg. gas_limit ,
509529 & self . context ( ) . base_fee ,
510530 & msg. gas_fee_cap ,
511531 & msg. gas_premium ,
512532 ) ;
513533
514- let mut transfer_to_actor = |addr : ActorID , amt : & TokenAmount | -> anyhow :: Result < ( ) > {
534+ let mut transfer_to_actor = |addr : ActorID , amt : & TokenAmount | {
515535 if amt. is_negative ( ) {
516536 return Err ( anyhow ! ( "attempted to transfer negative value into actor" ) ) ;
517537 }
@@ -525,19 +545,34 @@ where
525545 Ok ( ( ) )
526546 } ;
527547
528- transfer_to_actor ( BURNT_FUNDS_ACTOR_ID , & base_fee_burn) ?;
548+ let gas_distributions = gas_hook. call ( sender_id, & receipt, & gas_outputs) ;
549+ let gas_sum: TokenAmount = gas_distributions. iter ( ) . map ( |( _, amount) | amount) . sum ( ) ;
529550
530- transfer_to_actor ( REWARD_ACTOR_ID , & miner_tip) ?;
551+ // NOTE: we don't support old network versions in the FVM, so we always burn.
552+ let GasOutputs {
553+ base_fee_burn,
554+ over_estimation_burn,
555+ miner_penalty,
556+ miner_tip,
557+ refund,
558+ gas_refund,
559+ gas_burned,
560+ } = gas_outputs;
531561
532- transfer_to_actor ( BURNT_FUNDS_ACTOR_ID , & over_estimation_burn) ?;
562+ if ( & base_fee_burn + & over_estimation_burn + & refund + & miner_tip) != gas_sum {
563+ // Sanity check. This could be a fatal error.
564+ return Err ( anyhow ! ( "gas processing hook gives out extra gas" ) ) ;
565+ }
533566
534- // refund unused gas
535- transfer_to_actor ( sender_id, & refund) ?;
567+ for ( id, amount) in gas_distributions {
568+ transfer_to_actor ( id, & amount) ?;
569+ }
536570
537- if ( & base_fee_burn + & over_estimation_burn + & refund + & miner_tip ) != gas_cost {
571+ if gas_sum != gas_cost {
538572 // Sanity check. This could be a fatal error.
539- return Err ( anyhow ! ( "Gas handling math is wrong" ) ) ;
573+ return Err ( anyhow ! ( "Txn gas fee hook invalid, expecting total {} but found {}" , gas_cost , gas_sum ) ) ;
540574 }
575+
541576 Ok ( ApplyRet {
542577 msg_receipt : receipt,
543578 penalty : miner_penalty,
@@ -569,3 +604,48 @@ where
569604 )
570605 }
571606}
607+
608+ pub fn default_gas_hook (
609+ sender_id : ActorID ,
610+ _: & Receipt ,
611+ gas_output : & GasOutputs ,
612+ ) -> Vec < ( ActorID , TokenAmount ) > {
613+ vec ! [
614+ ( BURNT_FUNDS_ACTOR_ID , gas_output. base_fee_burn. clone( ) ) ,
615+ ( REWARD_ACTOR_ID , gas_output. miner_tip. clone( ) ) ,
616+ (
617+ BURNT_FUNDS_ACTOR_ID ,
618+ gas_output. over_estimation_burn. clone( ) ,
619+ ) ,
620+ ( sender_id, gas_output. refund. clone( ) ) ,
621+ ]
622+ }
623+
624+ /// The gas hook to handle how to transfer the final txn gas.
625+ /// Returns a vec of the amount of gas that should be deposited into each actor id
626+ pub trait TxnGasHook {
627+ fn call (
628+ self ,
629+ // the sender of the txn
630+ actor_id : ActorID ,
631+ // the transaction outcome
632+ receipt : & Receipt ,
633+ // the gas usage and distribution according to fvm rules
634+ gas_outputs : & GasOutputs ,
635+ ) -> Vec < ( ActorID , TokenAmount ) > ;
636+ }
637+
638+ // Implement the trait for any function/closure that matches the signature
639+ impl < F > TxnGasHook for F
640+ where
641+ F : FnOnce ( ActorID , & Receipt , & GasOutputs ) -> Vec < ( ActorID , TokenAmount ) > ,
642+ {
643+ fn call (
644+ self ,
645+ actor_id : ActorID ,
646+ receipt : & Receipt ,
647+ gas_outputs : & GasOutputs ,
648+ ) -> Vec < ( ActorID , TokenAmount ) > {
649+ self ( actor_id, receipt, gas_outputs)
650+ }
651+ }
0 commit comments