Skip to content
Open
45 changes: 45 additions & 0 deletions contracts/libraries/MsgSender.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
Copy link
Preview

Copilot AI Sep 12, 2025

Choose a reason for hiding this comment

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

This pragma version should be ^0.8.24 to match the other transient storage files, since this contract uses TransientArray which requires the TSTORE/TLOAD opcodes.

Suggested change
pragma solidity ^0.8.0;
pragma solidity ^0.8.24;

Copilot uses AI. Check for mistakes.


import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { Context } from "@openzeppelin/contracts/utils/Context.sol";
import { TransientArray } from "./TransientArray.sol";

// Stack of senders for each function selector
contract MsgSender is Context {
using Math for uint256;
using TransientArray for TransientArray.Address;

error MsgSenderPopExpectedMismatch(address expected, address popped);

mapping(address caller =>
mapping(bytes4 => TransientArray.Address)) private _senders;

function _msgSender(address caller, bytes4 selector, uint256 index) internal view virtual returns(address) {
return _senders[caller][selector].at(index);
}

function _msgSenderLength(address caller, bytes4 selector) internal view returns (uint256) {
return _senders[caller][selector].length();
}

function _msgSenderPush(address caller, bytes4 selector, address newSender) internal {
_senders[caller][selector].push(newSender);
}

function _msgSenderPop(address caller, bytes4 selector, address expected) internal {
TransientArray.Address storage stack = _senders[caller][selector];
address popped = stack.pop();
if (expected != popped) revert MsgSenderPopExpectedMismatch(expected, popped);
}

function _msgSender() internal view virtual override returns(address) {
TransientArray.Address storage stack = _senders[msg.sender][msg.sig];
(bool success, uint256 lastIndex) = stack.length().trySub(1);
if (!success) {
return super._msgSender(); // Fallback to Context's _msgSender if stack is empty
}
return stack.unsafeAt(lastIndex); // Use the last pushed sender
}
}
54 changes: 54 additions & 0 deletions contracts/libraries/ReentrancyGuard.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24

import { TransientLock, TransientLockLib } from "./TransientLock.sol";

/// @dev Base contract with reentrancy guard functionality using transient storage locks.
///
/// Use private _lock defined in this contract:
/// ```solidity
/// function swap(...) external nonReentrant {
/// function doMagic(...) external onlyNonReentrantCall {
/// ```
///
/// Or use your own locks for more granular control:
/// ```solidity
/// TransientLock private _myLock;
/// function swap(...) external nonReentrantLock(_myLock) {
/// function doMagic(...) external onlyNonReentrantCallLock(_myLock) {
/// ```
///
abstract contract ReentrancyGuard {
using TransientLockLib for TransientLock;

error MissingNonReentrantModifier(bytes4 selector);

TransientLock private _lock;

modifier nonReentrant {
_lock.lock();
_;
_lock.unlock();
}

modifier onlyNonReentrantCall {
if (!_inNonReentrantCall()) revert MissingNonReentrantModifier(msg.sig);
_;
}

modifier nonReentrantLock(TransientLock storage lock) {
lock.lock();
_;
lock.unlock();
}

modifier onlyNonReentrantCallLock(TransientLock storage lock) {
if (!lock.isLocked()) revert MissingNonReentrantModifier(msg.sig);
_;
}

function _inNonReentrantCall() internal view returns (bool) {
return _lock.isLocked();
}
}
130 changes: 130 additions & 0 deletions contracts/libraries/Transient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24

struct tuint256 { // solhint-disable-line contract-name-camelcase
uint256 _raw;
}

struct taddress { // solhint-disable-line contract-name-camelcase
address _raw;
}

struct tbytes32 { // solhint-disable-line contract-name-camelcase
bytes32 _raw;
}

/// @dev Library for drop-in replacement of uint256, address, and bytes32 with transient storage.
/// ```solidity
/// contract MagicProtocol {
/// using TransientLib for tuint256;
///
/// error ReentrantCallDetected();
///
/// struct ReentrancyLock {
/// tuint256 counter;
/// }
///
/// ReentrancyLock private _lock;
///
/// modifier nonReentrable {
/// require(_lock.counter.inc() == 1, ReentrantCallDetected());
/// _;
/// _lock.counter.dec();
/// }
///
/// function someMagicFunction(...) external nonReentrable {
/// ...
/// target.callSomeSuspiciousFunction(...);
/// ...
/// }
/// }
/// ```
library TransientLib {
error MathOverflow();
error MathUnderflow();

// Functions for tuint256

function tload(tuint256 storage self) internal view returns(uint256 ret) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
ret := tload(self.slot)
}
}

function tstore(tuint256 storage self, uint256 value) internal {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
tstore(self.slot, value)
}
}

function inc(tuint256 storage self) internal returns (uint256 incremented) {
return inc(self, TransientLib.MathOverflow.selector);
}

function inc(tuint256 storage self, bytes4 exception) internal returns (uint256 incremented) {
incremented = unsafeInc(self);
if (incremented == 0) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
mstore(0, exception)
revert(0, 4)
}
}
}

function unsafeInc(tuint256 storage self) internal returns (uint256 incremented) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
incremented := add(tload(self.slot), 1)
tstore(self.slot, incremented)
}
}

function dec(tuint256 storage self) internal returns (uint256 decremented) {
return dec(self, TransientLib.MathUnderflow.selector);
}

function dec(tuint256 storage self, bytes4 exception) internal returns (uint256 decremented) {
decremented = unsafeDec(self);
if (decremented == type(uint256).max) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
mstore(0, exception)
revert(0, 4)
}
}
}

function unsafeDec(tuint256 storage self) internal returns (uint256 decremented) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
decremented := sub(tload(self.slot), 1)
tstore(self.slot, decremented)
}
}

// Functions for taddress

function tload(taddress storage self) internal view returns(address ret) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
ret := tload(self.slot)
}
}

function tstore(taddress storage self, address value) internal {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
tstore(self.slot, value)
}
}

// Functions for tbytes32

function tload(tbytes32 storage self) internal view returns(bytes32 ret) {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
ret := tload(self.slot)
}
}

function tstore(tbytes32 storage self, bytes32 value) internal {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
tstore(self.slot, value)
}
}
}
Loading
Loading