From 1f135abdb3dc17a68076f46a2e7ea34188170bdf Mon Sep 17 00:00:00 2001 From: Tanishk Goyal Date: Fri, 1 May 2026 02:54:24 +0530 Subject: [PATCH] fix(precompiles): bind system_transfer_from to msg.sender Amp-Thread-ID: https://ampcode.com/threads/T-019de03d-f3b6-7292-9c49-cc2b5c740ec8 --- crates/precompiles/src/lib.rs | 2 +- .../precompiles/src/storage/thread_local.rs | 57 +++++++++++++++++-- crates/precompiles/src/tip20/mod.rs | 27 ++++----- crates/precompiles/src/tip_fee_manager/amm.rs | 19 +++---- 4 files changed, 74 insertions(+), 31 deletions(-) diff --git a/crates/precompiles/src/lib.rs b/crates/precompiles/src/lib.rs index 5a3302e2fe..971ca15f17 100644 --- a/crates/precompiles/src/lib.rs +++ b/crates/precompiles/src/lib.rs @@ -169,7 +169,7 @@ macro_rules! tempo_precompile { $input.is_static, gas_params.clone(), ); - crate::storage::StorageCtx::enter(&mut storage, || { + crate::storage::StorageCtx::enter_with_msg_sender(&mut storage, $input.caller, || { $impl.call($input.data, $input.caller) }) }) diff --git a/crates/precompiles/src/storage/thread_local.rs b/crates/precompiles/src/storage/thread_local.rs index 2e51068ea8..8cfd8bddae 100644 --- a/crates/precompiles/src/storage/thread_local.rs +++ b/crates/precompiles/src/storage/thread_local.rs @@ -21,6 +21,7 @@ use crate::{ }; scoped_thread_local!(static STORAGE: RefCell<&mut dyn PrecompileStorageProvider>); +scoped_thread_local!(static MSG_SENDER: Address); /// Thread-local storage accessor that implements `PrecompileStorageProvider` without the trait bound. /// @@ -40,6 +41,18 @@ scoped_thread_local!(static STORAGE: RefCell<&mut dyn PrecompileStorageProvider> pub struct StorageCtx; impl StorageCtx { + fn enter_storage(storage: &mut S, f: impl FnOnce() -> R) -> R + where + S: PrecompileStorageProvider, + { + // SAFETY: `scoped_tls` ensures the pointer is only accessible within the closure scope. + let storage: &mut dyn PrecompileStorageProvider = storage; + let storage_static: &mut (dyn PrecompileStorageProvider + 'static) = + unsafe { std::mem::transmute(storage) }; + let cell = RefCell::new(storage_static); + STORAGE.set(&cell, f) + } + /// Enter storage context. All storage operations must happen within the closure. /// /// # IMPORTANT @@ -52,12 +65,24 @@ impl StorageCtx { where S: PrecompileStorageProvider, { - // SAFETY: `scoped_tls` ensures the pointer is only accessible within the closure scope. - let storage: &mut dyn PrecompileStorageProvider = storage; - let storage_static: &mut (dyn PrecompileStorageProvider + 'static) = - unsafe { std::mem::transmute(storage) }; - let cell = RefCell::new(storage_static); - STORAGE.set(&cell, f) + Self::enter_storage(storage, f) + } + + /// Enter storage context with the external caller available as `msg.sender`. + pub fn enter_with_msg_sender( + storage: &mut S, + msg_sender: Address, + f: impl FnOnce() -> R, + ) -> R + where + S: PrecompileStorageProvider, + { + Self::enter_storage(storage, || Self::with_msg_sender(msg_sender, f)) + } + + /// Execute a closure with `msg.sender` bound for nested internal precompile calls. + pub fn with_msg_sender(msg_sender: Address, f: impl FnOnce() -> R) -> R { + MSG_SENDER.set(&msg_sender, f) } /// Execute an infallible function with access to the current thread-local storage provider. @@ -98,6 +123,15 @@ impl StorageCtx { }) } + /// Returns the current call's external sender. + pub fn msg_sender(&self) -> Address { + assert!( + MSG_SENDER.is_set(), + "No msg sender context. 'StorageCtx::enter_with_msg_sender' or 'StorageCtx::with_msg_sender' must be called first" + ); + MSG_SENDER.with(|msg_sender| *msg_sender) + } + // `PrecompileStorageProvider` methods (with modified mutability for read-only methods) /// Executes a closure with access to the account info, returning the closure's result. @@ -602,4 +636,15 @@ mod tests { assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(99)); }); } + + #[test] + fn test_msg_sender_context() { + let mut storage = HashMapStorageProvider::new(1); + let sender = Address::random(); + + StorageCtx::enter_with_msg_sender(&mut storage, sender, || { + let ctx = StorageCtx; + assert_eq!(ctx.msg_sender(), sender); + }); + } } diff --git a/crates/precompiles/src/tip20/mod.rs b/crates/precompiles/src/tip20/mod.rs index 960721a27b..bea3c6e196 100644 --- a/crates/precompiles/src/tip20/mod.rs +++ b/crates/precompiles/src/tip20/mod.rs @@ -749,7 +749,7 @@ impl TIP20Token { Ok(true) } - /// Transfers `amount` from `from` to `to` without approval, for use + /// Transfers `amount` from the current call's `msg.sender` to `to` without approval, for use /// by other precompiles only (not exposed via ABI). Enforces /// compliance via the [`TIP403Registry`] and [`AccountKeychain`]. /// @@ -758,13 +758,9 @@ impl TIP20Token { /// - `InvalidRecipient` — recipient address is zero /// - `PolicyForbids` — TIP-403 policy rejects sender or recipient /// - `SpendingLimitExceeded` — access key spending limit exceeded - /// - `InsufficientBalance` — `from` balance lower than transfer amount - pub fn system_transfer_from( - &mut self, - from: Address, - to: Address, - amount: U256, - ) -> Result { + /// - `InsufficientBalance` — `msg.sender` balance lower than transfer amount + pub fn system_transfer_from(&mut self, to: Address, amount: U256) -> Result { + let from = self.storage.msg_sender(); let to = Recipient::resolve(to)?; self.validate_transfer(from, &to)?; self.check_and_update_spending_limit(from, amount)?; @@ -1817,20 +1813,25 @@ pub(crate) mod tests { fn test_system_transfer_from() -> eyre::Result<()> { let mut storage = HashMapStorageProvider::new(1); let admin = Address::random(); - let from = Address::random(); + let sender = Address::random(); let to = Address::random(); let amount = U256::random() % U256::from(u128::MAX); - StorageCtx::enter(&mut storage, || { + StorageCtx::enter_with_msg_sender(&mut storage, sender, || { let mut token = TIP20Setup::create("Test", "TST", admin) .with_issuer(admin) - .with_mint(from, amount) + .with_mint(sender, amount) .apply()?; - assert!(token.system_transfer_from(from, to, amount).is_ok()); + assert!(token.system_transfer_from(to, amount).is_ok()); assert_eq!( token.emitted_events().last().unwrap(), - &TIP20Event::Transfer(ITIP20::Transfer { from, to, amount }).into_log_data() + &TIP20Event::Transfer(ITIP20::Transfer { + from: sender, + to, + amount, + }) + .into_log_data() ); Ok(()) diff --git a/crates/precompiles/src/tip_fee_manager/amm.rs b/crates/precompiles/src/tip_fee_manager/amm.rs index 9ac0dcff99..f2314fa073 100644 --- a/crates/precompiles/src/tip_fee_manager/amm.rs +++ b/crates/precompiles/src/tip_fee_manager/amm.rs @@ -1,6 +1,6 @@ use crate::{ error::{Result, TempoPrecompileError}, - storage::Handler, + storage::{Handler, StorageCtx}, tip_fee_manager::{ITIPFeeAMM, TIPFeeAMMError, TIPFeeAMMEvent, TipFeeManager}, tip20::{ITIP20, TIP20Token, validate_usd_currency}, }; @@ -183,11 +183,9 @@ impl TipFeeManager { let amount_in = U256::from(amount_in); let amount_out = U256::from(amount_out); - TIP20Token::from_address(validator_token)?.system_transfer_from( - msg_sender, - self.address, - amount_in, - )?; + StorageCtx::with_msg_sender(msg_sender, || { + TIP20Token::from_address(validator_token)?.system_transfer_from(self.address, amount_in) + })?; TIP20Token::from_address(user_token)?.transfer( self.address, @@ -294,11 +292,10 @@ impl TipFeeManager { } // Transfer validator tokens from user - let _ = TIP20Token::from_address(validator_token)?.system_transfer_from( - msg_sender, - self.address, - amount_validator_token, - )?; + let _ = StorageCtx::with_msg_sender(msg_sender, || { + TIP20Token::from_address(validator_token)? + .system_transfer_from(self.address, amount_validator_token) + })?; // Update reserves let validator_amount: u128 = amount_validator_token