From 6147847795321876bab3a92d6379a0e325d99e15 Mon Sep 17 00:00:00 2001 From: Gabriele Rigo Date: Mon, 2 Dec 2024 17:20:13 +0100 Subject: [PATCH 1/2] feat: add flash allowance initial work - added contracts to enable transient approval --- src/FlashAllowanceTransfer.sol | 78 ++++++++++++++++++++++ src/interfaces/IFlashAllowanceTransfer.sol | 31 +++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/FlashAllowanceTransfer.sol create mode 100644 src/interfaces/IFlashAllowanceTransfer.sol diff --git a/src/FlashAllowanceTransfer.sol b/src/FlashAllowanceTransfer.sol new file mode 100644 index 00000000..ac64b205 --- /dev/null +++ b/src/FlashAllowanceTransfer.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; +import {IAllowanceTransfer} from "./interfaces/IAllowanceTransfer.sol"; +import {IFlashAllowanceTransfer} from "./interfaces/IFlashAllowanceTransfer.sol"; + +contract FlashAllowanceTransfer is IFlashAllowanceTransfer { + using SafeTransferLib for ERC20; + + bytes32 private constant _TEMPORARY_ALLOWANCES_SLOT = bytes32(uint256(keccak256("Allowance.allowance")) - 1); + + /// @notice Maps users to tokens to spender addresses and information about the approval on the token + /// @dev Indexed in the order of token owner address, token address, spender address + /// @dev The stored word saves the allowed amount. Defined and commented, but used equivalent in inline assembly. + //mapping(address => mapping(address => mapping(address => uint256))) private allowance; + + /// @inheritdoc IFlashAllowanceTransfer + /// @dev Does not emit event as approval is reset at the end of txn, and an event is emitted by the transferFrom call + function flashApprove(address token, address spender, uint256 amount) external { + _setTemporaryAllowance(msg.sender, token, spender, amount); + } + + /// @inheritdoc IFlashAllowanceTransfer + function flashTransferFrom(address from, address to, uint256 amount, address token) external { + _flashTransfer(from, to, amount, token); + } + + /// @inheritdoc IFlashAllowanceTransfer + function flashTransferFrom(IAllowanceTransfer.AllowanceTransferDetails[] calldata transferDetails) external { + unchecked { + uint256 length = transferDetails.length; + for (uint256 i = 0; i < length; ++i) { + IAllowanceTransfer.AllowanceTransferDetails memory transferDetail = transferDetails[i]; + _flashTransfer(transferDetail.from, transferDetail.to, uint256(transferDetail.amount), transferDetail.token); + } + } + } + + /// @notice Internal function for transferring tokens using transient allowances + function _flashTransfer(address from, address to, uint256 amount, address token) private { + uint256 allowedAmount = _getTemporaryAllowance(from, token, msg.sender); + + if (allowedAmount != type(uint256).max) { + if (amount > allowedAmount) { + revert IAllowanceTransfer.InsufficientAllowance(allowedAmount); + } else { + unchecked { + allowedAmount -= amount; + } + } + } + + // Transfer the tokens from the from address to the recipient. + ERC20(token).safeTransferFrom(from, to, amount); + } + + function _setTemporaryAllowance(address owner, address token, address spender, uint256 value) internal { + bytes32 slot = _allowanceSlot(owner, token, spender); + assembly ("memory-safe") { + tstore(slot, value) + } + } + + function _getTemporaryAllowance(address owner, address token, address spender) internal view returns (uint256 allowed) { + bytes32 slot = _allowanceSlot(owner, token, spender); + assembly ("memory-safe") { + allowed := tload(slot) + } + } + + /// @notice Returns the position key for the allowance + /// @dev equivalent to mapping(address => mapping(address => mapping(address => uint256))) + function _allowanceSlot(address owner, address spender, address token) private pure returns (bytes32) { + return keccak256(abi.encode(spender, keccak256(abi.encode(token, keccak256(abi.encode(owner, _TEMPORARY_ALLOWANCES_SLOT)))))); + } +} diff --git a/src/interfaces/IFlashAllowanceTransfer.sol b/src/interfaces/IFlashAllowanceTransfer.sol new file mode 100644 index 00000000..42f9f225 --- /dev/null +++ b/src/interfaces/IFlashAllowanceTransfer.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IAllowanceTransfer.sol"; + +/// @title AllowanceTransfer +/// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts +/// @dev Requires user's token approval on the Permit2 contract +interface IFlashAllowanceTransfer { + /// @notice Approves the spender to use up to amount of the specified token + /// @param token The token to approve + /// @param spender The spender address to approve + /// @param amount The approved amount of the token + /// @dev Setting amount to type(uint160).max sets an unlimited approval + function flashApprove(address token, address spender, uint256 amount) external; + + /// @notice Transfer approved tokens from one address to another + /// @param from The address to transfer from + /// @param to The address of the recipient + /// @param amount The amount of the token to transfer + /// @param token The token address to transfer + /// @dev Requires the from address to have approved at least the desired amount + /// of tokens to msg.sender. + function flashTransferFrom(address from, address to, uint256 amount, address token) external; + + /// @notice Transfer approved tokens in a batch + /// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers + /// @dev Requires the from addresses to have approved at least the desired amount + /// of tokens to msg.sender. + function flashTransferFrom(IAllowanceTransfer.AllowanceTransferDetails[] calldata transferDetails) external; +} From f1973953a6b6941928d34b6be29f0b951173ab36 Mon Sep 17 00:00:00 2001 From: Gabriele Rigo Date: Mon, 2 Dec 2024 17:20:37 +0100 Subject: [PATCH 2/2] update solc in project and compile --- .forge-snapshots/batchTransferFrom.snap | 2 +- .forge-snapshots/batchTransferFromMultiToken.snap | 2 +- .forge-snapshots/lockdown.snap | 2 +- ...rmit2 + transferFrom2 with WETH9's mainnet address.snap | 2 +- ...2 + transferFrom2 with a non EIP-2612 native token.snap | 2 +- ...mit2 + transferFrom2 with an EIP-2612 native token.snap | 2 +- .forge-snapshots/permitBatchCleanWrite.snap | 2 +- .forge-snapshots/permitBatchDirtyWrite.snap | 2 +- .../permitBatchTransferFromMultipleTokens.snap | 2 +- .forge-snapshots/permitBatchTransferFromSingleToken.snap | 2 +- .forge-snapshots/permitCleanWrite.snap | 2 +- .forge-snapshots/permitCompactSig.snap | 2 +- .forge-snapshots/permitDirtyNonce.snap | 2 +- .forge-snapshots/permitDirtyWrite.snap | 2 +- .forge-snapshots/permitInvalidSigner.snap | 2 +- .forge-snapshots/permitSetMaxAllowanceCleanWrite.snap | 2 +- .forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap | 2 +- .forge-snapshots/permitSignatureExpired.snap | 2 +- .forge-snapshots/permitTransferFromBatchTypedWitness.snap | 2 +- .forge-snapshots/permitTransferFromCompactSig.snap | 2 +- .forge-snapshots/permitTransferFromSingleToken.snap | 2 +- .forge-snapshots/permitTransferFromTypedWitness.snap | 2 +- ...t + safeTransferFrom with an EIP-2612 native token.snap | 2 +- ...2 + transferFrom2 with a non EIP-2612 native token.snap | 2 +- .forge-snapshots/single recipient 2 tokens.snap | 2 +- .forge-snapshots/single recipient many tokens.snap | 2 +- .forge-snapshots/transferFrom with different owners.snap | 2 +- .forge-snapshots/transferFrom.snap | 2 +- foundry.toml | 2 +- src/AllowanceTransfer.sol | 2 +- src/EIP712.sol | 2 +- src/Permit2.sol | 7 ++++--- src/PermitErrors.sol | 2 +- src/SignatureTransfer.sol | 2 +- test/AllowanceTransferInvariants.t.sol | 2 +- test/actors/Permitter.sol | 2 +- test/actors/Spender.sol | 2 +- 37 files changed, 40 insertions(+), 39 deletions(-) diff --git a/.forge-snapshots/batchTransferFrom.snap b/.forge-snapshots/batchTransferFrom.snap index 12330bfb..2cdbff48 100644 --- a/.forge-snapshots/batchTransferFrom.snap +++ b/.forge-snapshots/batchTransferFrom.snap @@ -1 +1 @@ -61797 \ No newline at end of file +40275 \ No newline at end of file diff --git a/.forge-snapshots/batchTransferFromMultiToken.snap b/.forge-snapshots/batchTransferFromMultiToken.snap index 6372f872..d45c5234 100644 --- a/.forge-snapshots/batchTransferFromMultiToken.snap +++ b/.forge-snapshots/batchTransferFromMultiToken.snap @@ -1 +1 @@ -81786 \ No newline at end of file +60132 \ No newline at end of file diff --git a/.forge-snapshots/lockdown.snap b/.forge-snapshots/lockdown.snap index a193479b..37b36262 100644 --- a/.forge-snapshots/lockdown.snap +++ b/.forge-snapshots/lockdown.snap @@ -1 +1 @@ -28435 \ No newline at end of file +6450 \ No newline at end of file diff --git a/.forge-snapshots/permit2 + transferFrom2 with WETH9's mainnet address.snap b/.forge-snapshots/permit2 + transferFrom2 with WETH9's mainnet address.snap index 95b3b96c..85befeb2 100644 --- a/.forge-snapshots/permit2 + transferFrom2 with WETH9's mainnet address.snap +++ b/.forge-snapshots/permit2 + transferFrom2 with WETH9's mainnet address.snap @@ -1 +1 @@ -60346 \ No newline at end of file +60343 \ No newline at end of file diff --git a/.forge-snapshots/permit2 + transferFrom2 with a non EIP-2612 native token.snap b/.forge-snapshots/permit2 + transferFrom2 with a non EIP-2612 native token.snap index 70b916f2..f6fd21a4 100644 --- a/.forge-snapshots/permit2 + transferFrom2 with a non EIP-2612 native token.snap +++ b/.forge-snapshots/permit2 + transferFrom2 with a non EIP-2612 native token.snap @@ -1 +1 @@ -60811 \ No newline at end of file +60784 \ No newline at end of file diff --git a/.forge-snapshots/permit2 + transferFrom2 with an EIP-2612 native token.snap b/.forge-snapshots/permit2 + transferFrom2 with an EIP-2612 native token.snap index ac024b17..e38eab09 100644 --- a/.forge-snapshots/permit2 + transferFrom2 with an EIP-2612 native token.snap +++ b/.forge-snapshots/permit2 + transferFrom2 with an EIP-2612 native token.snap @@ -1 +1 @@ -46296 \ No newline at end of file +46230 \ No newline at end of file diff --git a/.forge-snapshots/permitBatchCleanWrite.snap b/.forge-snapshots/permitBatchCleanWrite.snap index ff36c94f..3d6111c0 100644 --- a/.forge-snapshots/permitBatchCleanWrite.snap +++ b/.forge-snapshots/permitBatchCleanWrite.snap @@ -1 +1 @@ -91924 \ No newline at end of file +69636 \ No newline at end of file diff --git a/.forge-snapshots/permitBatchDirtyWrite.snap b/.forge-snapshots/permitBatchDirtyWrite.snap index 2020125e..89b51398 100644 --- a/.forge-snapshots/permitBatchDirtyWrite.snap +++ b/.forge-snapshots/permitBatchDirtyWrite.snap @@ -1 +1 @@ -57724 \ No newline at end of file +35436 \ No newline at end of file diff --git a/.forge-snapshots/permitBatchTransferFromMultipleTokens.snap b/.forge-snapshots/permitBatchTransferFromMultipleTokens.snap index bd650401..cad71026 100644 --- a/.forge-snapshots/permitBatchTransferFromMultipleTokens.snap +++ b/.forge-snapshots/permitBatchTransferFromMultipleTokens.snap @@ -1 +1 @@ -143387 \ No newline at end of file +120840 \ No newline at end of file diff --git a/.forge-snapshots/permitBatchTransferFromSingleToken.snap b/.forge-snapshots/permitBatchTransferFromSingleToken.snap index c1642c97..8e91e0e8 100644 --- a/.forge-snapshots/permitBatchTransferFromSingleToken.snap +++ b/.forge-snapshots/permitBatchTransferFromSingleToken.snap @@ -1 +1 @@ -88867 \ No newline at end of file +66552 \ No newline at end of file diff --git a/.forge-snapshots/permitCleanWrite.snap b/.forge-snapshots/permitCleanWrite.snap index c49bc2ab..2bafb98d 100644 --- a/.forge-snapshots/permitCleanWrite.snap +++ b/.forge-snapshots/permitCleanWrite.snap @@ -1 +1 @@ -63119 \ No newline at end of file +40904 \ No newline at end of file diff --git a/.forge-snapshots/permitCompactSig.snap b/.forge-snapshots/permitCompactSig.snap index eb882987..cb2734ac 100644 --- a/.forge-snapshots/permitCompactSig.snap +++ b/.forge-snapshots/permitCompactSig.snap @@ -1 +1 @@ -63094 \ No newline at end of file +40941 \ No newline at end of file diff --git a/.forge-snapshots/permitDirtyNonce.snap b/.forge-snapshots/permitDirtyNonce.snap index a909d364..07272edf 100644 --- a/.forge-snapshots/permitDirtyNonce.snap +++ b/.forge-snapshots/permitDirtyNonce.snap @@ -1 +1 @@ -44014 \ No newline at end of file +21799 \ No newline at end of file diff --git a/.forge-snapshots/permitDirtyWrite.snap b/.forge-snapshots/permitDirtyWrite.snap index 451c9a87..416dea90 100644 --- a/.forge-snapshots/permitDirtyWrite.snap +++ b/.forge-snapshots/permitDirtyWrite.snap @@ -1 +1 @@ -46019 \ No newline at end of file +23804 \ No newline at end of file diff --git a/.forge-snapshots/permitInvalidSigner.snap b/.forge-snapshots/permitInvalidSigner.snap index 73ab357b..6a30942d 100644 --- a/.forge-snapshots/permitInvalidSigner.snap +++ b/.forge-snapshots/permitInvalidSigner.snap @@ -1 +1 @@ -40301 \ No newline at end of file +18168 \ No newline at end of file diff --git a/.forge-snapshots/permitSetMaxAllowanceCleanWrite.snap b/.forge-snapshots/permitSetMaxAllowanceCleanWrite.snap index 6e633733..007bfffc 100644 --- a/.forge-snapshots/permitSetMaxAllowanceCleanWrite.snap +++ b/.forge-snapshots/permitSetMaxAllowanceCleanWrite.snap @@ -1 +1 @@ -61114 \ No newline at end of file +38899 \ No newline at end of file diff --git a/.forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap b/.forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap index a909d364..07272edf 100644 --- a/.forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap +++ b/.forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap @@ -1 +1 @@ -44014 \ No newline at end of file +21799 \ No newline at end of file diff --git a/.forge-snapshots/permitSignatureExpired.snap b/.forge-snapshots/permitSignatureExpired.snap index 309c6d2b..5e8d0217 100644 --- a/.forge-snapshots/permitSignatureExpired.snap +++ b/.forge-snapshots/permitSignatureExpired.snap @@ -1 +1 @@ -31700 \ No newline at end of file +9332 \ No newline at end of file diff --git a/.forge-snapshots/permitTransferFromBatchTypedWitness.snap b/.forge-snapshots/permitTransferFromBatchTypedWitness.snap index 3adcc82d..3182fecf 100644 --- a/.forge-snapshots/permitTransferFromBatchTypedWitness.snap +++ b/.forge-snapshots/permitTransferFromBatchTypedWitness.snap @@ -1 +1 @@ -120325 \ No newline at end of file +97318 \ No newline at end of file diff --git a/.forge-snapshots/permitTransferFromCompactSig.snap b/.forge-snapshots/permitTransferFromCompactSig.snap index 3214529f..56747705 100644 --- a/.forge-snapshots/permitTransferFromCompactSig.snap +++ b/.forge-snapshots/permitTransferFromCompactSig.snap @@ -1 +1 @@ -86066 \ No newline at end of file +63987 \ No newline at end of file diff --git a/.forge-snapshots/permitTransferFromSingleToken.snap b/.forge-snapshots/permitTransferFromSingleToken.snap index 8c739819..c64559d7 100644 --- a/.forge-snapshots/permitTransferFromSingleToken.snap +++ b/.forge-snapshots/permitTransferFromSingleToken.snap @@ -1 +1 @@ -86092 \ No newline at end of file +63951 \ No newline at end of file diff --git a/.forge-snapshots/permitTransferFromTypedWitness.snap b/.forge-snapshots/permitTransferFromTypedWitness.snap index bf396ab1..4b3b26e6 100644 --- a/.forge-snapshots/permitTransferFromTypedWitness.snap +++ b/.forge-snapshots/permitTransferFromTypedWitness.snap @@ -1 +1 @@ -87817 \ No newline at end of file +65078 \ No newline at end of file diff --git a/.forge-snapshots/safePermit + safeTransferFrom with an EIP-2612 native token.snap b/.forge-snapshots/safePermit + safeTransferFrom with an EIP-2612 native token.snap index 5424434f..ab4c2cae 100644 --- a/.forge-snapshots/safePermit + safeTransferFrom with an EIP-2612 native token.snap +++ b/.forge-snapshots/safePermit + safeTransferFrom with an EIP-2612 native token.snap @@ -1 +1 @@ -48268 \ No newline at end of file +48296 \ No newline at end of file diff --git a/.forge-snapshots/simplePermit2 + transferFrom2 with a non EIP-2612 native token.snap b/.forge-snapshots/simplePermit2 + transferFrom2 with a non EIP-2612 native token.snap index 70b916f2..f6fd21a4 100644 --- a/.forge-snapshots/simplePermit2 + transferFrom2 with a non EIP-2612 native token.snap +++ b/.forge-snapshots/simplePermit2 + transferFrom2 with a non EIP-2612 native token.snap @@ -1 +1 @@ -60811 \ No newline at end of file +60784 \ No newline at end of file diff --git a/.forge-snapshots/single recipient 2 tokens.snap b/.forge-snapshots/single recipient 2 tokens.snap index 37c51f05..95f589ba 100644 --- a/.forge-snapshots/single recipient 2 tokens.snap +++ b/.forge-snapshots/single recipient 2 tokens.snap @@ -1 +1 @@ -118525 \ No newline at end of file +96094 \ No newline at end of file diff --git a/.forge-snapshots/single recipient many tokens.snap b/.forge-snapshots/single recipient many tokens.snap index 770e7a83..d39883c7 100644 --- a/.forge-snapshots/single recipient many tokens.snap +++ b/.forge-snapshots/single recipient many tokens.snap @@ -1 +1 @@ -133544 \ No newline at end of file +110164 \ No newline at end of file diff --git a/.forge-snapshots/transferFrom with different owners.snap b/.forge-snapshots/transferFrom with different owners.snap index 26fba36e..dfe2ef6f 100644 --- a/.forge-snapshots/transferFrom with different owners.snap +++ b/.forge-snapshots/transferFrom with different owners.snap @@ -1 +1 @@ -61886 \ No newline at end of file +40232 \ No newline at end of file diff --git a/.forge-snapshots/transferFrom.snap b/.forge-snapshots/transferFrom.snap index ebbbd605..3a11432c 100644 --- a/.forge-snapshots/transferFrom.snap +++ b/.forge-snapshots/transferFrom.snap @@ -1 +1 @@ -52197 \ No newline at end of file +30322 \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 27013c9c..d4140c39 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc = "0.8.17" +solc = "0.8.28" bytecode_hash = "none" optimizer = true via_ir = true diff --git a/src/AllowanceTransfer.sol b/src/AllowanceTransfer.sol index 56c4cce0..b8d9bdd8 100644 --- a/src/AllowanceTransfer.sol +++ b/src/AllowanceTransfer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.17; +pragma solidity 0.8.28; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; diff --git a/src/EIP712.sol b/src/EIP712.sol index 971a03db..4b3db17c 100644 --- a/src/EIP712.sol +++ b/src/EIP712.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.17; +pragma solidity 0.8.28; import {IEIP712} from "./interfaces/IEIP712.sol"; diff --git a/src/Permit2.sol b/src/Permit2.sol index 7249e40a..8f48813f 100644 --- a/src/Permit2.sol +++ b/src/Permit2.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.17; +pragma solidity 0.8.28; import {SignatureTransfer} from "./SignatureTransfer.sol"; import {AllowanceTransfer} from "./AllowanceTransfer.sol"; +import {FlashAllowanceTransfer} from "./FlashAllowanceTransfer.sol"; /// @notice Permit2 handles signature-based transfers in SignatureTransfer and allowance-based transfers in AllowanceTransfer. /// @dev Users must approve Permit2 before calling any of the transfer functions. -contract Permit2 is SignatureTransfer, AllowanceTransfer { -// Permit2 unifies the two contracts so users have maximal flexibility with their approval. +contract Permit2 is SignatureTransfer, AllowanceTransfer, FlashAllowanceTransfer { +// Permit2 unifies the three contracts so users have maximal flexibility with their approval. } diff --git a/src/PermitErrors.sol b/src/PermitErrors.sol index 2c42e2d1..8e84ac91 100644 --- a/src/PermitErrors.sol +++ b/src/PermitErrors.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.17; +pragma solidity 0.8.28; /// @notice Shared errors between signature based transfers and allowance based transfers. diff --git a/src/SignatureTransfer.sol b/src/SignatureTransfer.sol index c026553a..54b7717e 100644 --- a/src/SignatureTransfer.sol +++ b/src/SignatureTransfer.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.17; +pragma solidity 0.8.28; import {ISignatureTransfer} from "./interfaces/ISignatureTransfer.sol"; import {SignatureExpired, InvalidNonce} from "./PermitErrors.sol"; diff --git a/test/AllowanceTransferInvariants.t.sol b/test/AllowanceTransferInvariants.t.sol index a980f7ce..4b9e5fbf 100644 --- a/test/AllowanceTransferInvariants.t.sol +++ b/test/AllowanceTransferInvariants.t.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.17; +pragma solidity 0.8.28; import {Test} from "forge-std/Test.sol"; import {StdInvariant} from "forge-std/StdInvariant.sol"; diff --git a/test/actors/Permitter.sol b/test/actors/Permitter.sol index 76dbbc17..3aff7b29 100644 --- a/test/actors/Permitter.sol +++ b/test/actors/Permitter.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.17; +pragma solidity 0.8.28; import {Vm} from "forge-std/Vm.sol"; import {Permit2} from "../../src/Permit2.sol"; diff --git a/test/actors/Spender.sol b/test/actors/Spender.sol index 22483789..919265c4 100644 --- a/test/actors/Spender.sol +++ b/test/actors/Spender.sol @@ -1,4 +1,4 @@ -pragma solidity 0.8.17; +pragma solidity 0.8.28; import {Test} from "forge-std/Test.sol"; import {Permit2} from "../../src/Permit2.sol";