From e21ab85702ace0ac008c0ae52662a60355023039 Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Thu, 24 Jul 2025 00:38:07 +0200 Subject: [PATCH 01/11] Add Transient.sol and TransientArray.sol and use TransientArray.Addesss for BySig._msgSenders transient array --- contracts/libraries/Transient.sol | 134 ++++++++++++++++++++++ contracts/libraries/TransientArray.sol | 152 +++++++++++++++++++++++++ contracts/mixins/BySig.sol | 6 +- 3 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 contracts/libraries/Transient.sol create mode 100644 contracts/libraries/TransientArray.sol diff --git a/contracts/libraries/Transient.sol b/contracts/libraries/Transient.sol new file mode 100644 index 00000000..bef22400 --- /dev/null +++ b/contracts/libraries/Transient.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +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) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + incremented := add(tload(self.slot), 1) + if iszero(incremented) { + mstore(0, exception) + revert(0, 4) + } + tstore(self.slot, incremented) + } + } + + 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) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly + let original := tload(self.slot) + if iszero(original) { + mstore(0, exception) + revert(0, 4) + } + decremented := sub(original, 1) + tstore(self.slot, decremented) + } + } + + 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) + } + } +} + diff --git a/contracts/libraries/TransientArray.sol b/contracts/libraries/TransientArray.sol new file mode 100644 index 00000000..fe68b110 --- /dev/null +++ b/contracts/libraries/TransientArray.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { TransientLib, tuint256, taddress, tbytes32 } from "./Transient.sol"; + +/// @dev Library for managing transient dynamic arrays (TSTORE, TLOAD) of different types (uint256, address, bytes32). +/// ```solidity +/// contract MagicProtocol { +/// using TransientArray for TransientArray.Uint256; +/// +/// TransientArray.Uint256 private myArray; +/// +/// function cook(uint256 value) external { +/// myArray.push(value); +/// require(myArray._length() == 1, "Array should not be empty after push"); +/// require(myArray.at(0) == value, "Value at index 0 does not match pushed value"); +/// +/// uint256 poppedValue = myArray.pop(); +/// require(poppedValue == value, "Popped value does not match pushed value"); +/// require(myArray._length() == 0, "Array should be empty after pop"); +/// } +/// } +/// ``` +library TransientArray { + using TransientLib for tuint256; + using TransientLib for taddress; + using TransientLib for tbytes32; + + error TransientArray_IndexOutOfBounds(); + error TransientArray_EmptyArrayPop(); + + struct Uint256 { + tuint256 _length; + mapping(uint256 => tuint256) _items; + } + + struct Address { + tuint256 _length; + mapping(uint256 => taddress) _items; + } + + struct Bytes32 { + tuint256 _length; + mapping(uint256 => tbytes32) _items; + } + + // Functions for Uint256 + + function length(Uint256 storage self) internal view returns (uint256) { + return self._length.tload(); + } + + function at(Uint256 storage self, uint256 index) internal view returns (uint256) { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + return self._items[index].tload(); + } + + function unsafeAt(Uint256 storage self, uint256 index) internal view returns (uint256) { + return self._items[index].tload(); + } + + function push(Uint256 storage self, uint256 value) internal { + uint256 nextElementIndex = self._length.tload(); + self._items[nextElementIndex].tstore(value); + unchecked { + self._length.tstore(nextElementIndex + 1); + } + } + + function pop(Uint256 storage self) internal returns (uint256 ret) { + uint256 currentLength = self._length.tload(); + if (currentLength == 0) revert TransientArray_EmptyArrayPop(); + uint256 newLength; + unchecked { + newLength = currentLength - 1; + } + ret = self._items[newLength].tload(); + delete self._items[newLength]; + self._length.tstore(newLength); + } + + // Functions for Address + + function length(Address storage self) internal view returns (uint256) { + return self._length.tload(); + } + + function at(Address storage self, uint256 index) internal view returns (address) { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + return self._items[index].tload(); + } + + function unsafeAt(Address storage self, uint256 index) internal view returns (address) { + return self._items[index].tload(); + } + + function push(Address storage self, address value) internal { + uint256 nextElementIndex = self._length.tload(); + self._items[nextElementIndex].tstore(value); + unchecked { + self._length.tstore(nextElementIndex + 1); + } + } + + function pop(Address storage self) internal returns (address ret) { + uint256 currentLength = self._length.tload(); + if (currentLength == 0) revert TransientArray_EmptyArrayPop(); + uint256 newLength; + unchecked { + newLength = currentLength - 1; + } + ret = self._items[newLength].tload(); + delete self._items[newLength]; + self._length.tstore(newLength); + } + + // Functions for Bytes32 + + function length(Bytes32 storage self) internal view returns (uint256) { + return self._length.tload(); + } + + function at(Bytes32 storage self, uint256 index) internal view returns (bytes32) { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + return self._items[index].tload(); + } + + function unsafeAt(Bytes32 storage self, uint256 index) internal view returns (bytes32) { + return self._items[index].tload(); + } + + function push(Bytes32 storage self, bytes32 value) internal { + uint256 nextElementIndex = self._length.tload(); + self._items[nextElementIndex].tstore(value); + unchecked { + self._length.tstore(nextElementIndex + 1); + } + } + + function pop(Bytes32 storage self) internal returns (bytes32 ret) { + uint256 currentLength = self._length.tload(); + if (currentLength == 0) revert TransientArray_EmptyArrayPop(); + uint256 newLength; + unchecked { + newLength = currentLength - 1; + } + ret = self._items[newLength].tload(); + delete self._items[newLength]; + self._length.tstore(newLength); + } +} diff --git a/contracts/mixins/BySig.sol b/contracts/mixins/BySig.sol index 72ddf6d7..4c666347 100644 --- a/contracts/mixins/BySig.sol +++ b/contracts/mixins/BySig.sol @@ -7,7 +7,7 @@ import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import { ECDSA } from "../libraries/ECDSA.sol"; import { BySigTraits } from "../libraries/BySigTraits.sol"; -import { AddressArray } from "../libraries/AddressArray.sol"; +import { TransientArray } from "../libraries/TransientArray.sol"; /** * @title BySig @@ -17,7 +17,7 @@ import { AddressArray } from "../libraries/AddressArray.sol"; abstract contract BySig is Context, EIP712 { using Address for address; using BySigTraits for BySigTraits.Value; - using AddressArray for AddressArray.Data; + using TransientArray for TransientArray.Address; /// @notice Emitted when the nonce used for a call is incorrect. error WrongNonce(); @@ -47,7 +47,7 @@ abstract contract BySig is Context, EIP712 { bytes32 constant public SIGNED_CALL_TYPEHASH = keccak256("SignedCall(uint256 traits,bytes data)"); // Various nonces used for signature verification and replay protection. - AddressArray.Data /* transient */ private _msgSenders; + TransientArray.Address /* transient */ private _msgSenders; mapping(address => uint256) private _bySigAccountNonces; mapping(address => mapping(bytes4 => uint256)) private _bySigSelectorNonces; mapping(address => mapping(uint256 => uint256)) private _bySigUniqueNonces; From a37dc559194b459176b1e49a25dfdbc35750f9db Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Thu, 24 Jul 2025 01:21:46 +0200 Subject: [PATCH 02/11] Add transient ReentrancyGuard lib --- contracts/libraries/ReentrancyGuard.sol | 28 +++++++++++++++++ contracts/libraries/ReentrancyGuardBase.sol | 33 +++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 contracts/libraries/ReentrancyGuard.sol create mode 100644 contracts/libraries/ReentrancyGuardBase.sol diff --git a/contracts/libraries/ReentrancyGuard.sol b/contracts/libraries/ReentrancyGuard.sol new file mode 100644 index 00000000..2d07f6a2 --- /dev/null +++ b/contracts/libraries/ReentrancyGuard.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { TransientLib, tuint256 } from "./Transient.sol"; + +struct ReentrancyGuard { + tuint256 _raw; +} + +library ReentrancyGuardLib { + using TransientLib for tuint256; + + error ReentrantCallDetected(); + error EnterLeaveDisbalance(); + + function enter(ReentrancyGuard storage self) internal { + if (self._raw.inc() != 1) revert ReentrantCallDetected(); + } + + function enterNoIncrement(ReentrancyGuard storage self) internal view { + if (self._raw.tload() != 0) revert ReentrantCallDetected(); + } + + function leave(ReentrancyGuard storage self) internal { + self._raw.dec(EnterLeaveDisbalance.selector); + } +} diff --git a/contracts/libraries/ReentrancyGuardBase.sol b/contracts/libraries/ReentrancyGuardBase.sol new file mode 100644 index 00000000..885100ab --- /dev/null +++ b/contracts/libraries/ReentrancyGuardBase.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { ReentrancyGuard, ReentrancyGuardLib } from "./ReentrancyGuard.sol"; + +contract ReentrancyGuardBase { + using ReentrancyGuardLib for ReentrancyGuard; + + ReentrancyGuard private _lock; + + modifier nonReentrant { + _lock.enter(); + _; + _lock.leave(); + } + + modifier nonReentrantView { + _lock.enterNoIncrement(); + _; + } + + modifier nonReentrantGuard(ReentrancyGuard storage lock) { + lock.enter(); + _; + lock.leave(); + } + + modifier nonReentrantViewGuard(ReentrancyGuard storage lock) { + lock.enterNoIncrement(); + _; + } +} From bb7fbcd08e5b4bb5f83f7f160c4a9339b9485ddc Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Thu, 24 Jul 2025 01:29:12 +0200 Subject: [PATCH 03/11] Make ReentrancyGuardBase abstract --- contracts/libraries/ReentrancyGuardBase.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/libraries/ReentrancyGuardBase.sol b/contracts/libraries/ReentrancyGuardBase.sol index 885100ab..a65d1f33 100644 --- a/contracts/libraries/ReentrancyGuardBase.sol +++ b/contracts/libraries/ReentrancyGuardBase.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import { ReentrancyGuard, ReentrancyGuardLib } from "./ReentrancyGuard.sol"; -contract ReentrancyGuardBase { +abstract contract ReentrancyGuardBase { using ReentrancyGuardLib for ReentrancyGuard; ReentrancyGuard private _lock; From b54387faa2eedba357db626e587a1db4d5e33514 Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Sat, 26 Jul 2025 02:25:21 +0200 Subject: [PATCH 04/11] Rename ReentrancyGuard into TransientLock and ReentrancyGuradBase into ReentrancyGuard --- contracts/libraries/ReentrancyGuard.sol | 58 +++++++++++++++------ contracts/libraries/ReentrancyGuardBase.sol | 33 ------------ contracts/libraries/TransientLock.sol | 35 +++++++++++++ 3 files changed, 77 insertions(+), 49 deletions(-) delete mode 100644 contracts/libraries/ReentrancyGuardBase.sol create mode 100644 contracts/libraries/TransientLock.sol diff --git a/contracts/libraries/ReentrancyGuard.sol b/contracts/libraries/ReentrancyGuard.sol index 2d07f6a2..d79c37dc 100644 --- a/contracts/libraries/ReentrancyGuard.sol +++ b/contracts/libraries/ReentrancyGuard.sol @@ -2,27 +2,53 @@ pragma solidity ^0.8.0; -import { TransientLib, tuint256 } from "./Transient.sol"; - -struct ReentrancyGuard { - tuint256 _raw; -} - -library ReentrancyGuardLib { - using TransientLib for tuint256; +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(); + + TransientLock private _lock; + + modifier nonReentrant { + _lock.lock(); + _; + _lock.unlock(); + } - error ReentrantCallDetected(); - error EnterLeaveDisbalance(); + modifier onlyNonReentrantCall { + if (!_inNonReentrantCall()) revert MissingNonReentrantModifier(); + _; + } - function enter(ReentrancyGuard storage self) internal { - if (self._raw.inc() != 1) revert ReentrantCallDetected(); + modifier nonReentrantLock(TransientLock storage lock) { + lock.lock(); + _; + lock.unlock(); } - function enterNoIncrement(ReentrancyGuard storage self) internal view { - if (self._raw.tload() != 0) revert ReentrantCallDetected(); + modifier onlyNonReentrantCallLock(TransientLock storage lock) { + if (!lock.isLocked()) revert MissingNonReentrantModifier(); + _; } - function leave(ReentrancyGuard storage self) internal { - self._raw.dec(EnterLeaveDisbalance.selector); + function _inNonReentrantCall() internal view returns (bool) { + return _lock.isLocked(); } } diff --git a/contracts/libraries/ReentrancyGuardBase.sol b/contracts/libraries/ReentrancyGuardBase.sol deleted file mode 100644 index a65d1f33..00000000 --- a/contracts/libraries/ReentrancyGuardBase.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { ReentrancyGuard, ReentrancyGuardLib } from "./ReentrancyGuard.sol"; - -abstract contract ReentrancyGuardBase { - using ReentrancyGuardLib for ReentrancyGuard; - - ReentrancyGuard private _lock; - - modifier nonReentrant { - _lock.enter(); - _; - _lock.leave(); - } - - modifier nonReentrantView { - _lock.enterNoIncrement(); - _; - } - - modifier nonReentrantGuard(ReentrancyGuard storage lock) { - lock.enter(); - _; - lock.leave(); - } - - modifier nonReentrantViewGuard(ReentrancyGuard storage lock) { - lock.enterNoIncrement(); - _; - } -} diff --git a/contracts/libraries/TransientLock.sol b/contracts/libraries/TransientLock.sol new file mode 100644 index 00000000..63b27d4d --- /dev/null +++ b/contracts/libraries/TransientLock.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { TransientLib, tuint256 } from "./Transient.sol"; + +struct TransientLock { + tuint256 _raw; +} + +library TransientLockLib { + using TransientLib for tuint256; + + uint256 constant private _UNLOCKED = 0; + uint256 constant private _LOCKED = 1; + + error UnexpectedLock(); + error UnexpectedUnlock(); + + function lock(TransientLock storage self) internal { + if (self._raw.inc() != _LOCKED) revert UnexpectedLock(); + } + + function isLocked(TransientLock storage self) internal view returns (bool) { + return self._raw.tload() == _LOCKED; + } + + function unlock(TransientLock storage self) internal { + self._raw.dec(UnexpectedUnlock.selector); + } + + function unlock(TransientLock storage self, bytes4 exception) internal { + self._raw.dec(exception); + } +} From 790d70c806caee5587594c7ddd396410cd351c2d Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Wed, 10 Sep 2025 15:49:02 +0400 Subject: [PATCH 05/11] Add TransientSet and MsgSender and use it in BySig --- contracts/libraries/MsgSender.sol | 46 ++++++ contracts/libraries/ReentrancyGuard.sol | 8 +- contracts/libraries/Transient.sol | 2 +- contracts/libraries/TransientArray.sol | 85 +++++++--- contracts/libraries/TransientLock.sol | 2 +- contracts/libraries/TransientSet.sol | 199 +++++++++++++++++++++++ contracts/mixins/BySig.sol | 25 +-- contracts/tests/mocks/TokenWithBySig.sol | 5 +- 8 files changed, 322 insertions(+), 50 deletions(-) create mode 100644 contracts/libraries/MsgSender.sol create mode 100644 contracts/libraries/TransientSet.sol diff --git a/contracts/libraries/MsgSender.sol b/contracts/libraries/MsgSender.sol new file mode 100644 index 00000000..f994c926 --- /dev/null +++ b/contracts/libraries/MsgSender.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +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 + } +} diff --git a/contracts/libraries/ReentrancyGuard.sol b/contracts/libraries/ReentrancyGuard.sol index d79c37dc..37473f0e 100644 --- a/contracts/libraries/ReentrancyGuard.sol +++ b/contracts/libraries/ReentrancyGuard.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 import { TransientLock, TransientLockLib } from "./TransientLock.sol"; @@ -22,7 +22,7 @@ import { TransientLock, TransientLockLib } from "./TransientLock.sol"; abstract contract ReentrancyGuard { using TransientLockLib for TransientLock; - error MissingNonReentrantModifier(); + error MissingNonReentrantModifier(bytes4 selector); TransientLock private _lock; @@ -33,7 +33,7 @@ abstract contract ReentrancyGuard { } modifier onlyNonReentrantCall { - if (!_inNonReentrantCall()) revert MissingNonReentrantModifier(); + if (!_inNonReentrantCall()) revert MissingNonReentrantModifier(msg.sig); _; } @@ -44,7 +44,7 @@ abstract contract ReentrancyGuard { } modifier onlyNonReentrantCallLock(TransientLock storage lock) { - if (!lock.isLocked()) revert MissingNonReentrantModifier(); + if (!lock.isLocked()) revert MissingNonReentrantModifier(msg.sig); _; } diff --git a/contracts/libraries/Transient.sol b/contracts/libraries/Transient.sol index bef22400..1a96e0b2 100644 --- a/contracts/libraries/Transient.sol +++ b/contracts/libraries/Transient.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 struct tuint256 { // solhint-disable-line contract-name-camelcase uint256 _raw; diff --git a/contracts/libraries/TransientArray.sol b/contracts/libraries/TransientArray.sol index fe68b110..c79fc6c4 100644 --- a/contracts/libraries/TransientArray.sol +++ b/contracts/libraries/TransientArray.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { TransientLib, tuint256, taddress, tbytes32 } from "./Transient.sol"; /// @dev Library for managing transient dynamic arrays (TSTORE, TLOAD) of different types (uint256, address, bytes32). @@ -23,6 +24,7 @@ import { TransientLib, tuint256, taddress, tbytes32 } from "./Transient.sol"; /// } /// ``` library TransientArray { + using Math for uint256; using TransientLib for tuint256; using TransientLib for taddress; using TransientLib for tbytes32; @@ -60,21 +62,32 @@ library TransientArray { return self._items[index].tload(); } - function push(Uint256 storage self, uint256 value) internal { + function get(Uint256 storage self) internal view returns (uint256[] memory array) { + uint256 len = self._length.tload(); + array = new uint256[](len); + for (uint256 i = 0; i < len; i++) { + array[i] = self._items[i].tload(); + } + } + + function set(Uint256 storage self, uint256 index, uint256 value) internal { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + self._items[index].tstore(value); + } + + function push(Uint256 storage self, uint256 value) internal returns (uint256 newLength) { uint256 nextElementIndex = self._length.tload(); self._items[nextElementIndex].tstore(value); unchecked { - self._length.tstore(nextElementIndex + 1); + newLength = nextElementIndex + 1; + self._length.tstore(newLength); } } function pop(Uint256 storage self) internal returns (uint256 ret) { - uint256 currentLength = self._length.tload(); - if (currentLength == 0) revert TransientArray_EmptyArrayPop(); - uint256 newLength; - unchecked { - newLength = currentLength - 1; - } + (bool success, uint256 newLength) = self._length.tload().trySub(1); + if (!success) revert TransientArray_EmptyArrayPop(); + ret = self._items[newLength].tload(); delete self._items[newLength]; self._length.tstore(newLength); @@ -95,21 +108,32 @@ library TransientArray { return self._items[index].tload(); } - function push(Address storage self, address value) internal { + function get(Address storage self) internal view returns (address[] memory array) { + uint256 len = self._length.tload(); + array = new address[](len); + for (uint256 i = 0; i < len; i++) { + array[i] = self._items[i].tload(); + } + } + + function set(Address storage self, uint256 index, address value) internal { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + self._items[index].tstore(value); + } + + function push(Address storage self, address value) internal returns (uint256 newLength) { uint256 nextElementIndex = self._length.tload(); self._items[nextElementIndex].tstore(value); unchecked { - self._length.tstore(nextElementIndex + 1); + newLength = nextElementIndex + 1; + self._length.tstore(newLength); } } function pop(Address storage self) internal returns (address ret) { - uint256 currentLength = self._length.tload(); - if (currentLength == 0) revert TransientArray_EmptyArrayPop(); - uint256 newLength; - unchecked { - newLength = currentLength - 1; - } + (bool success, uint256 newLength) = self._length.tload().trySub(1); + if (!success) revert TransientArray_EmptyArrayPop(); + ret = self._items[newLength].tload(); delete self._items[newLength]; self._length.tstore(newLength); @@ -130,21 +154,32 @@ library TransientArray { return self._items[index].tload(); } - function push(Bytes32 storage self, bytes32 value) internal { + function get(Bytes32 storage self) internal view returns (bytes32[] memory array) { + uint256 len = self._length.tload(); + array = new bytes32[](len); + for (uint256 i = 0; i < len; i++) { + array[i] = self._items[i].tload(); + } + } + + function set(Bytes32 storage self, uint256 index, bytes32 value) internal { + if (index >= self._length.tload()) revert TransientArray_IndexOutOfBounds(); + self._items[index].tstore(value); + } + + function push(Bytes32 storage self, bytes32 value) internal returns (uint256 newLength) { uint256 nextElementIndex = self._length.tload(); self._items[nextElementIndex].tstore(value); unchecked { - self._length.tstore(nextElementIndex + 1); + newLength = nextElementIndex + 1; + self._length.tstore(newLength); } } function pop(Bytes32 storage self) internal returns (bytes32 ret) { - uint256 currentLength = self._length.tload(); - if (currentLength == 0) revert TransientArray_EmptyArrayPop(); - uint256 newLength; - unchecked { - newLength = currentLength - 1; - } + (bool success, uint256 newLength) = self._length.tload().trySub(1); + if (!success) revert TransientArray_EmptyArrayPop(); + ret = self._items[newLength].tload(); delete self._items[newLength]; self._length.tstore(newLength); diff --git a/contracts/libraries/TransientLock.sol b/contracts/libraries/TransientLock.sol index 63b27d4d..c31db6f3 100644 --- a/contracts/libraries/TransientLock.sol +++ b/contracts/libraries/TransientLock.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 import { TransientLib, tuint256 } from "./Transient.sol"; diff --git a/contracts/libraries/TransientSet.sol b/contracts/libraries/TransientSet.sol new file mode 100644 index 00000000..96209460 --- /dev/null +++ b/contracts/libraries/TransientSet.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; // tload/tstore are available since 0.8.24 + +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; + +import { TransientLib, tuint256, taddress, tbytes32 } from "./Transient.sol"; +import { TransientArray } from "./TransientArray.sol"; + +/// @dev Library for managing transient dynamic arrays (TSTORE, TLOAD) of different types (uint256, address, bytes32). +/// ```solidity +/// contract MagicProtocol { +/// using TransientSet for TransientSet.Uint256; +/// +/// TransientSet.Uint256 private mySet; +/// +/// function cook(uint256 value) external { +/// mySet.add(value); +/// require(mySet.length() == 1, "Set should not be empty after add"); +/// require(mySet.contains(value), "Set should contain the added value"); +/// +/// uint256 poppedValue = mySet.pop(); +/// require(poppedValue == value, "Popped value does not match pushed value"); +/// require(mySet.length() == 0, "Set should be empty after pop"); +/// } +/// } +/// ``` +library TransientSet { + using Math for uint256; + using TransientLib for tuint256; + using TransientLib for taddress; + using TransientLib for tbytes32; + using TransientArray for TransientArray.Uint256; + using TransientArray for TransientArray.Address; + using TransientArray for TransientArray.Bytes32; + + error TransientSet_IndexOutOfBounds(); + error TransientSet_EmptySetPop(); + error TransientSet_InsertExistingElement(); + + // Functions for Uint256 + + struct Uint256 { + TransientArray.Uint256 _items; + mapping(uint256 => tuint256) _lookup; // stored as ~index, similar to +1 but unchecked math + } + + function length(Uint256 storage set) internal view returns (uint256) { + return set._items.length(); + } + + function at(Uint256 storage set, uint256 index) internal view returns (uint256) { + return set._items.at(index); + } + + function unsafeAt(Uint256 storage set, uint256 index) internal view returns (uint256) { + return set._items.unsafeAt(index); + } + + function contains(Uint256 storage set, uint256 item) internal view returns (bool) { + return set._lookup[item].tload() != 0; + } + + function get(Uint256 storage set) internal view returns (uint256[] memory) { + return set._items.get(); + } + + function add(Uint256 storage set, uint256 item) internal returns (bool) { + uint256 index = ~set._lookup[item].tload(); + if (index != 0) { + return false; + } + set._lookup[item].tstore(set._items.push(item)); + return true; + } + + function remove(Uint256 storage set, uint256 item) internal returns (bool) { + uint256 index = ~set._lookup[item].tload(); + set._lookup[item].tstore(0); + if (index == 0) { + return false; + } + + uint256 lastItem = set._items.pop(); + if (lastItem != item) { + unchecked { + set._items.set(index - 1, lastItem); + set._lookup[lastItem].tstore(index); + } + } + return true; + } + + // Functions for Address + + struct Address { + TransientArray.Address _items; + mapping(address => tuint256) _lookup; // stored as ~index, similar to +1 but unchecked math + } + + function length(Address storage set) internal view returns (uint256) { + return set._items.length(); + } + + function at(Address storage set, uint256 index) internal view returns (address) { + return set._items.at(index); + } + + function unsafeAt(Address storage set, uint256 index) internal view returns (address) { + return set._items.unsafeAt(index); + } + + function contains(Address storage set, address item) internal view returns (bool) { + return set._lookup[item].tload() != 0; + } + + function get(Address storage set) internal view returns (address[] memory) { + return set._items.get(); + } + + function add(Address storage set, address item) internal returns (bool) { + uint256 index = ~set._lookup[item].tload(); + if (index != 0) { + return false; + } + set._lookup[item].tstore(set._items.push(item)); + return true; + } + + function remove(Address storage set, address item) internal returns (bool) { + uint256 index = ~set._lookup[item].tload(); + set._lookup[item].tstore(0); + if (index == 0) { + return false; + } + + address lastItem = set._items.pop(); + if (lastItem != item) { + unchecked { + set._items.set(index - 1, lastItem); + set._lookup[lastItem].tstore(index); + } + } + return true; + } + + // Functions for Bytes32 + + struct Bytes32 { + TransientArray.Bytes32 _items; + mapping(bytes32 => tuint256) _lookup; // stored as ~index, similar to +1 but unchecked math + } + + function length(Bytes32 storage set) internal view returns (uint256) { + return set._items.length(); + } + + function at(Bytes32 storage set, uint256 index) internal view returns (bytes32) { + return set._items.at(index); + } + + function unsafeAt(Bytes32 storage set, uint256 index) internal view returns (bytes32) { + return set._items.unsafeAt(index); + } + + function contains(Bytes32 storage set, bytes32 item) internal view returns (bool) { + return set._lookup[item].tload() != 0; + } + + function get(Bytes32 storage set) internal view returns (bytes32[] memory) { + return set._items.get(); + } + + function add(Bytes32 storage set, bytes32 item) internal returns (bool) { + uint256 index = ~set._lookup[item].tload(); + if (index != 0) { + return false; + } + set._lookup[item].tstore(set._items.push(item)); + return true; + } + + function remove(Bytes32 storage set, bytes32 item) internal returns (bool) { + uint256 index = ~set._lookup[item].tload(); + set._lookup[item].tstore(0); + if (index == 0) { + return false; + } + + bytes32 lastItem = set._items.pop(); + if (lastItem != item) { + unchecked { + set._items.set(index - 1, lastItem); + set._lookup[lastItem].tstore(index); + } + } + return true; + } +} diff --git a/contracts/mixins/BySig.sol b/contracts/mixins/BySig.sol index 4c666347..9c8165a3 100644 --- a/contracts/mixins/BySig.sol +++ b/contracts/mixins/BySig.sol @@ -2,19 +2,19 @@ pragma solidity ^0.8.0; -import { Context } from "@openzeppelin/contracts/utils/Context.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import { ECDSA } from "../libraries/ECDSA.sol"; import { BySigTraits } from "../libraries/BySigTraits.sol"; import { TransientArray } from "../libraries/TransientArray.sol"; +import { MsgSender } from "../libraries/MsgSender.sol"; /** * @title BySig * @notice Mixin that provides signature-based accessibility to every external method of the smart contract. * @dev Inherit your contract from this mixin and use `_msgSender()` instead of `msg.sender` everywhere. */ -abstract contract BySig is Context, EIP712 { +abstract contract BySig is MsgSender, EIP712 { using Address for address; using BySigTraits for BySigTraits.Value; using TransientArray for TransientArray.Address; @@ -47,7 +47,6 @@ abstract contract BySig is Context, EIP712 { bytes32 constant public SIGNED_CALL_TYPEHASH = keccak256("SignedCall(uint256 traits,bytes data)"); // Various nonces used for signature verification and replay protection. - TransientArray.Address /* transient */ private _msgSenders; mapping(address => uint256) private _bySigAccountNonces; mapping(address => mapping(bytes4 => uint256)) private _bySigSelectorNonces; mapping(address => mapping(uint256 => uint256)) private _bySigUniqueNonces; @@ -124,9 +123,10 @@ abstract contract BySig is Context, EIP712 { if (!_useNonce(signer, sig.traits, sig.data)) revert WrongNonce(); if (!ECDSA.recoverOrIsValidSignature(signer, hashBySig(sig), signature)) revert WrongSignature(); - _msgSenders.push(signer); + bytes4 selector = bytes4(sig.data); + _msgSenderPush(msg.sender, selector, signer); ret = address(this).functionDelegateCall(sig.data); - _msgSenders.pop(); + _msgSenderPop(msg.sender, selector, signer); } /** @@ -141,7 +141,10 @@ abstract contract BySig is Context, EIP712 { * @return ret Result of the executed call. */ function sponsoredCall(address token, uint256 amount, bytes calldata data, bytes calldata extraData) public payable returns(bytes memory ret) { + _msgSenderPush(msg.sender, bytes4(data), _msgSender()); ret = address(this).functionDelegateCall(data); + _msgSenderPop(msg.sender, bytes4(data), _msgSender()); + _chargeSigner(_msgSender(), msg.sender, token, amount, extraData); } @@ -188,18 +191,6 @@ abstract contract BySig is Context, EIP712 { _bySigUniqueNonces[_msgSender()][nonce >> 8] |= 1 << (nonce & 0xff); } - /** - * @dev Returns the address of the message sender, replacing the traditional `msg.sender` with a potentially signed sender. - * @return The address of the message sender. - */ - function _msgSender() internal view override virtual returns (address) { - uint256 length = _msgSenders.length(); - if (length == 0) { - return super._msgSender(); - } - return _msgSenders.unsafeAt(length - 1); - } - function _useNonce(address signer, BySigTraits.Value traits, bytes calldata data) private returns(bool) { BySigTraits.NonceType nonceType = traits.nonceType(); uint256 nonce = traits.nonce(); diff --git a/contracts/tests/mocks/TokenWithBySig.sol b/contracts/tests/mocks/TokenWithBySig.sol index efa43477..3e61d927 100644 --- a/contracts/tests/mocks/TokenWithBySig.sol +++ b/contracts/tests/mocks/TokenWithBySig.sol @@ -6,6 +6,7 @@ import { Context } from "@openzeppelin/contracts/utils/Context.sol"; import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import { TokenMock } from "../../mocks/TokenMock.sol"; import { BySig } from "../../mixins/BySig.sol"; +import { MsgSender } from "../../libraries/MsgSender.sol"; contract TokenWithBySig is TokenMock, BySig { error WrongToken(); @@ -15,8 +16,8 @@ contract TokenWithBySig is TokenMock, BySig { // solhint-disable-next-line no-empty-blocks constructor(string memory name, string memory symbol, string memory version) TokenMock(name, symbol) EIP712(name, version) {} - function _msgSender() internal view override(Context, BySig) returns (address) { - return BySig._msgSender(); + function _msgSender() internal view override(Context, MsgSender) returns (address) { + return MsgSender._msgSender(); } function getChainId() external view returns (uint256) { From 0c0508cdd9dcc56461e7db2ce60f295b8a71389f Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Wed, 10 Sep 2025 16:08:35 +0400 Subject: [PATCH 06/11] Fix using "delete" for transient storage since it only works with storage --- contracts/libraries/TransientArray.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/libraries/TransientArray.sol b/contracts/libraries/TransientArray.sol index c79fc6c4..bcbfcf1b 100644 --- a/contracts/libraries/TransientArray.sol +++ b/contracts/libraries/TransientArray.sol @@ -89,7 +89,7 @@ library TransientArray { if (!success) revert TransientArray_EmptyArrayPop(); ret = self._items[newLength].tload(); - delete self._items[newLength]; + self._items[newLength].tstore(0); self._length.tstore(newLength); } @@ -135,7 +135,7 @@ library TransientArray { if (!success) revert TransientArray_EmptyArrayPop(); ret = self._items[newLength].tload(); - delete self._items[newLength]; + self._items[newLength].tstore(address(0)); self._length.tstore(newLength); } @@ -181,7 +181,7 @@ library TransientArray { if (!success) revert TransientArray_EmptyArrayPop(); ret = self._items[newLength].tload(); - delete self._items[newLength]; + self._items[newLength].tstore(bytes32(0)); self._length.tstore(newLength); } } From fbc2ec81578c33978fa8a54a745f5a45fa0f6293 Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Thu, 11 Sep 2025 09:55:00 +0400 Subject: [PATCH 07/11] Simplify Transient inc/dec --- contracts/libraries/Transient.sol | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/contracts/libraries/Transient.sol b/contracts/libraries/Transient.sol index 1a96e0b2..a50d6b6a 100644 --- a/contracts/libraries/Transient.sol +++ b/contracts/libraries/Transient.sol @@ -63,13 +63,12 @@ library TransientLib { } function inc(tuint256 storage self, bytes4 exception) internal returns (uint256 incremented) { - assembly ("memory-safe") { // solhint-disable-line no-inline-assembly - incremented := add(tload(self.slot), 1) - if iszero(incremented) { + incremented = unsafeInc(self); + if (incremented == 0) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly mstore(0, exception) revert(0, 4) } - tstore(self.slot, incremented) } } @@ -85,14 +84,12 @@ library TransientLib { } function dec(tuint256 storage self, bytes4 exception) internal returns (uint256 decremented) { - assembly ("memory-safe") { // solhint-disable-line no-inline-assembly - let original := tload(self.slot) - if iszero(original) { + decremented = unsafeDec(self); + if (decremented == type(uint256).max) { + assembly ("memory-safe") { // solhint-disable-line no-inline-assembly mstore(0, exception) revert(0, 4) } - decremented := sub(original, 1) - tstore(self.slot, decremented) } } @@ -131,4 +128,3 @@ library TransientLib { } } } - From 8fd7eca16aa21d3bbd21a4e2c94189fc263b5873 Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Fri, 12 Sep 2025 21:00:46 +0400 Subject: [PATCH 08/11] Update contracts/libraries/MsgSender.sol Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- contracts/libraries/MsgSender.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/libraries/MsgSender.sol b/contracts/libraries/MsgSender.sol index f994c926..9a2f087a 100644 --- a/contracts/libraries/MsgSender.sol +++ b/contracts/libraries/MsgSender.sol @@ -3,8 +3,7 @@ pragma solidity ^0.8.0; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { Context } from "@openzeppelin/contracts/utils/Context.sol" -; +import { Context } from "@openzeppelin/contracts/utils/Context.sol"; import { TransientArray } from "./TransientArray.sol"; // Stack of senders for each function selector From 3457fb41fc78990735fba7af0b9ceb48f19c2d9e Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Fri, 12 Sep 2025 21:05:14 +0400 Subject: [PATCH 09/11] Update contracts/mixins/BySig.sol Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- contracts/mixins/BySig.sol | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/mixins/BySig.sol b/contracts/mixins/BySig.sol index 9c8165a3..27c75395 100644 --- a/contracts/mixins/BySig.sol +++ b/contracts/mixins/BySig.sol @@ -141,11 +141,12 @@ abstract contract BySig is MsgSender, EIP712 { * @return ret Result of the executed call. */ function sponsoredCall(address token, uint256 amount, bytes calldata data, bytes calldata extraData) public payable returns(bytes memory ret) { - _msgSenderPush(msg.sender, bytes4(data), _msgSender()); + address sender = _msgSender(); + _msgSenderPush(msg.sender, bytes4(data), sender); ret = address(this).functionDelegateCall(data); - _msgSenderPop(msg.sender, bytes4(data), _msgSender()); + _msgSenderPop(msg.sender, bytes4(data), sender); - _chargeSigner(_msgSender(), msg.sender, token, amount, extraData); + _chargeSigner(sender, msg.sender, token, amount, extraData); } /** From e9d3639745645c28f50278ee5e301f7bfd484b45 Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Fri, 12 Sep 2025 21:13:34 +0400 Subject: [PATCH 10/11] Fix TransientSet lookup --- contracts/libraries/TransientSet.sol | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/libraries/TransientSet.sol b/contracts/libraries/TransientSet.sol index 96209460..03a52dad 100644 --- a/contracts/libraries/TransientSet.sol +++ b/contracts/libraries/TransientSet.sol @@ -42,7 +42,7 @@ library TransientSet { struct Uint256 { TransientArray.Uint256 _items; - mapping(uint256 => tuint256) _lookup; // stored as ~index, similar to +1 but unchecked math + mapping(uint256 => tuint256) _lookup; } function length(Uint256 storage set) internal view returns (uint256) { @@ -66,8 +66,8 @@ library TransientSet { } function add(Uint256 storage set, uint256 item) internal returns (bool) { - uint256 index = ~set._lookup[item].tload(); - if (index != 0) { + uint256 index = set._lookup[item].tload(); + if (index == 0) { return false; } set._lookup[item].tstore(set._items.push(item)); @@ -75,12 +75,12 @@ library TransientSet { } function remove(Uint256 storage set, uint256 item) internal returns (bool) { - uint256 index = ~set._lookup[item].tload(); - set._lookup[item].tstore(0); + uint256 index = set._lookup[item].tload(); if (index == 0) { return false; } + set._lookup[item].tstore(0); uint256 lastItem = set._items.pop(); if (lastItem != item) { unchecked { @@ -95,7 +95,7 @@ library TransientSet { struct Address { TransientArray.Address _items; - mapping(address => tuint256) _lookup; // stored as ~index, similar to +1 but unchecked math + mapping(address => tuint256) _lookup; } function length(Address storage set) internal view returns (uint256) { @@ -119,7 +119,7 @@ library TransientSet { } function add(Address storage set, address item) internal returns (bool) { - uint256 index = ~set._lookup[item].tload(); + uint256 index = set._lookup[item].tload(); if (index != 0) { return false; } @@ -128,7 +128,7 @@ library TransientSet { } function remove(Address storage set, address item) internal returns (bool) { - uint256 index = ~set._lookup[item].tload(); + uint256 index = set._lookup[item].tload(); set._lookup[item].tstore(0); if (index == 0) { return false; @@ -148,7 +148,7 @@ library TransientSet { struct Bytes32 { TransientArray.Bytes32 _items; - mapping(bytes32 => tuint256) _lookup; // stored as ~index, similar to +1 but unchecked math + mapping(bytes32 => tuint256) _lookup; // stored as index, similar to +1 but unchecked math } function length(Bytes32 storage set) internal view returns (uint256) { @@ -172,7 +172,7 @@ library TransientSet { } function add(Bytes32 storage set, bytes32 item) internal returns (bool) { - uint256 index = ~set._lookup[item].tload(); + uint256 index = set._lookup[item].tload(); if (index != 0) { return false; } @@ -181,7 +181,7 @@ library TransientSet { } function remove(Bytes32 storage set, bytes32 item) internal returns (bool) { - uint256 index = ~set._lookup[item].tload(); + uint256 index = set._lookup[item].tload(); set._lookup[item].tstore(0); if (index == 0) { return false; From 49229e2bc027942767c36cf62f85a3f1f4db55ee Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Fri, 12 Sep 2025 23:35:37 +0400 Subject: [PATCH 11/11] Fix some bugs --- contracts/libraries/TransientSet.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/libraries/TransientSet.sol b/contracts/libraries/TransientSet.sol index 03a52dad..9d7b8c1d 100644 --- a/contracts/libraries/TransientSet.sol +++ b/contracts/libraries/TransientSet.sol @@ -67,7 +67,7 @@ library TransientSet { function add(Uint256 storage set, uint256 item) internal returns (bool) { uint256 index = set._lookup[item].tload(); - if (index == 0) { + if (index != 0) { return false; } set._lookup[item].tstore(set._items.push(item)); @@ -129,11 +129,11 @@ library TransientSet { function remove(Address storage set, address item) internal returns (bool) { uint256 index = set._lookup[item].tload(); - set._lookup[item].tstore(0); if (index == 0) { return false; } + set._lookup[item].tstore(0); address lastItem = set._items.pop(); if (lastItem != item) { unchecked { @@ -182,11 +182,11 @@ library TransientSet { function remove(Bytes32 storage set, bytes32 item) internal returns (bool) { uint256 index = set._lookup[item].tload(); - set._lookup[item].tstore(0); if (index == 0) { return false; } + set._lookup[item].tstore(0); bytes32 lastItem = set._items.pop(); if (lastItem != item) { unchecked {