Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/precompiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
})
Expand Down
57 changes: 51 additions & 6 deletions crates/precompiles/src/storage/thread_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
};

scoped_thread_local!(static STORAGE: RefCell<&mut dyn PrecompileStorageProvider>);
scoped_thread_local!(static MSG_SENDER: Address);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm not sure i understand the idea here. all precompiles propagate msg_sender manually now so adding this introduces a bit of an inconsistency to how it's handled

also i don't think we need to keep it as a thread local variable, it could just be a part of PrecompileStorageProvider context i believe

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah we want to remove manually propogating msg_sender to system transfer from, so we can have better guarantees about TIP 1035.

We want system_transfer_from to receive a trusted msg_sender, instead of trusting whatever the precompile passes to it.


/// Thread-local storage accessor that implements `PrecompileStorageProvider` without the trait bound.
///
Expand All @@ -40,6 +41,18 @@ scoped_thread_local!(static STORAGE: RefCell<&mut dyn PrecompileStorageProvider>
pub struct StorageCtx;

impl StorageCtx {
fn enter_storage<S, R>(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
Expand All @@ -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<S, R>(
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<R>(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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
});
}
}
27 changes: 14 additions & 13 deletions crates/precompiles/src/tip20/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
///
Expand All @@ -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<bool> {
/// - `InsufficientBalance` — `msg.sender` balance lower than transfer amount
pub fn system_transfer_from(&mut self, to: Address, amount: U256) -> Result<bool> {
let from = self.storage.msg_sender();
let to = Recipient::resolve(to)?;
self.validate_transfer(from, &to)?;
self.check_and_update_spending_limit(from, amount)?;
Expand Down Expand Up @@ -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(())
Expand Down
19 changes: 8 additions & 11 deletions crates/precompiles/src/tip_fee_manager/amm.rs
Original file line number Diff line number Diff line change
@@ -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},
};
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
Loading