|
1 | 1 | // Copyright 2021-2023 Protocol Labs |
2 | 2 | // SPDX-License-Identifier: Apache-2.0, MIT |
3 | 3 | use std::rc::Rc; |
| 4 | +use std::sync::{Arc, Mutex}; |
4 | 5 |
|
5 | 6 | use anyhow::{Context, anyhow}; |
6 | 7 | use cid::Cid; |
@@ -33,6 +34,7 @@ use crate::syscalls::error::Abort; |
33 | 34 | use crate::syscalls::{charge_for_exec, update_gas_available}; |
34 | 35 | use crate::trace::{ExecutionEvent, ExecutionTrace}; |
35 | 36 | use crate::{syscall_error, system_actor}; |
| 37 | +use crate::executor::ReservationSession; |
36 | 38 |
|
37 | 39 | /// The default [`CallManager`] implementation. |
38 | 40 | #[repr(transparent)] |
@@ -77,6 +79,8 @@ pub struct InnerDefaultCallManager<M: Machine> { |
77 | 79 | events: EventsAccumulator, |
78 | 80 | /// The actor call stack (ActorID and entrypoint name tuple). |
79 | 81 | actor_call_stack: Vec<(ActorID, &'static str)>, |
| 82 | + /// Shared reservation session ledger for the current tipset, if any. |
| 83 | + reservation_session: Arc<Mutex<ReservationSession>>, |
80 | 84 | } |
81 | 85 |
|
82 | 86 | #[doc(hidden)] |
@@ -111,6 +115,7 @@ where |
111 | 115 | receiver_address: Address, |
112 | 116 | nonce: u64, |
113 | 117 | gas_premium: TokenAmount, |
| 118 | + reservation_session: Arc<Mutex<ReservationSession>>, |
114 | 119 | ) -> Self { |
115 | 120 | let limits = machine.new_limiter(); |
116 | 121 | let gas_tracker = |
@@ -162,6 +167,7 @@ where |
162 | 167 | events: Default::default(), |
163 | 168 | state_access_tracker, |
164 | 169 | actor_call_stack: vec![], |
| 170 | + reservation_session, |
165 | 171 | }))) |
166 | 172 | } |
167 | 173 |
|
@@ -489,8 +495,46 @@ where |
489 | 495 | .get_actor(from)? |
490 | 496 | .ok_or_else(||syscall_error!(InsufficientFunds; "insufficient funds to transfer {value}FIL from {from} to {to})"))?; |
491 | 497 |
|
492 | | - if &from_actor.balance < value { |
493 | | - return Err(syscall_error!(InsufficientFunds; "sender does not have funds to transfer (balance {}, transfer {})", &from_actor.balance, value).into()); |
| 498 | + // In reservation mode, ensure the sender cannot spend funds that are reserved for gas. |
| 499 | + // Free balance is defined as balance - reserved_remaining. To avoid negative intermediates, |
| 500 | + // we enforce the equivalent inequality: value + reserved_remaining <= balance. |
| 501 | + let (reservation_open, reserved_remaining) = { |
| 502 | + let session = self |
| 503 | + .reservation_session |
| 504 | + .lock() |
| 505 | + .expect("reservation session mutex poisoned"); |
| 506 | + if session.open { |
| 507 | + let reserved = session |
| 508 | + .reservations |
| 509 | + .get(&from) |
| 510 | + .cloned() |
| 511 | + .unwrap_or_else(TokenAmount::zero); |
| 512 | + (true, reserved) |
| 513 | + } else { |
| 514 | + (false, TokenAmount::zero()) |
| 515 | + } |
| 516 | + }; |
| 517 | + |
| 518 | + if reservation_open { |
| 519 | + let required = &reserved_remaining + value; |
| 520 | + if &from_actor.balance < &required { |
| 521 | + return Err(syscall_error!( |
| 522 | + InsufficientFunds; |
| 523 | + "sender does not have free funds to transfer (balance {}, transfer {}, reserved {})", |
| 524 | + &from_actor.balance, |
| 525 | + value, |
| 526 | + &reserved_remaining |
| 527 | + ) |
| 528 | + .into()); |
| 529 | + } |
| 530 | + } else if &from_actor.balance < value { |
| 531 | + return Err(syscall_error!( |
| 532 | + InsufficientFunds; |
| 533 | + "sender does not have funds to transfer (balance {}, transfer {})", |
| 534 | + &from_actor.balance, |
| 535 | + value |
| 536 | + ) |
| 537 | + .into()); |
494 | 538 | } |
495 | 539 |
|
496 | 540 | if from == to { |
|
0 commit comments