Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add flash allowance functionality #262

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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 .forge-snapshots/batchTransferFrom.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
61797
40275
2 changes: 1 addition & 1 deletion .forge-snapshots/batchTransferFromMultiToken.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
81786
60132
2 changes: 1 addition & 1 deletion .forge-snapshots/lockdown.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
28435
6450
Original file line number Diff line number Diff line change
@@ -1 +1 @@
60346
60343
Original file line number Diff line number Diff line change
@@ -1 +1 @@
60811
60784
Original file line number Diff line number Diff line change
@@ -1 +1 @@
46296
46230
2 changes: 1 addition & 1 deletion .forge-snapshots/permitBatchCleanWrite.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
91924
69636
2 changes: 1 addition & 1 deletion .forge-snapshots/permitBatchDirtyWrite.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
57724
35436
Original file line number Diff line number Diff line change
@@ -1 +1 @@
143387
120840
2 changes: 1 addition & 1 deletion .forge-snapshots/permitBatchTransferFromSingleToken.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
88867
66552
2 changes: 1 addition & 1 deletion .forge-snapshots/permitCleanWrite.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
63119
40904
2 changes: 1 addition & 1 deletion .forge-snapshots/permitCompactSig.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
63094
40941
2 changes: 1 addition & 1 deletion .forge-snapshots/permitDirtyNonce.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
44014
21799
2 changes: 1 addition & 1 deletion .forge-snapshots/permitDirtyWrite.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
46019
23804
2 changes: 1 addition & 1 deletion .forge-snapshots/permitInvalidSigner.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
40301
18168
2 changes: 1 addition & 1 deletion .forge-snapshots/permitSetMaxAllowanceCleanWrite.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
61114
38899
2 changes: 1 addition & 1 deletion .forge-snapshots/permitSetMaxAllowanceDirtyWrite.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
44014
21799
2 changes: 1 addition & 1 deletion .forge-snapshots/permitSignatureExpired.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
31700
9332
2 changes: 1 addition & 1 deletion .forge-snapshots/permitTransferFromBatchTypedWitness.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
120325
97318
2 changes: 1 addition & 1 deletion .forge-snapshots/permitTransferFromCompactSig.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
86066
63987
2 changes: 1 addition & 1 deletion .forge-snapshots/permitTransferFromSingleToken.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
86092
63951
2 changes: 1 addition & 1 deletion .forge-snapshots/permitTransferFromTypedWitness.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
87817
65078
Original file line number Diff line number Diff line change
@@ -1 +1 @@
48268
48296
Original file line number Diff line number Diff line change
@@ -1 +1 @@
60811
60784
2 changes: 1 addition & 1 deletion .forge-snapshots/single recipient 2 tokens.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
118525
96094
2 changes: 1 addition & 1 deletion .forge-snapshots/single recipient many tokens.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
133544
110164
2 changes: 1 addition & 1 deletion .forge-snapshots/transferFrom with different owners.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
61886
40232
2 changes: 1 addition & 1 deletion .forge-snapshots/transferFrom.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
52197
30322
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[profile.default]
solc = "0.8.17"
solc = "0.8.28"
bytecode_hash = "none"
optimizer = true
via_ir = true
Expand Down
2 changes: 1 addition & 1 deletion src/AllowanceTransfer.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
2 changes: 1 addition & 1 deletion src/EIP712.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
pragma solidity 0.8.28;

import {IEIP712} from "./interfaces/IEIP712.sol";

Expand Down
78 changes: 78 additions & 0 deletions src/FlashAllowanceTransfer.sol
Original file line number Diff line number Diff line change
@@ -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))))));
}
}
7 changes: 4 additions & 3 deletions src/Permit2.sol
Original file line number Diff line number Diff line change
@@ -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.
}
2 changes: 1 addition & 1 deletion src/PermitErrors.sol
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
2 changes: 1 addition & 1 deletion src/SignatureTransfer.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
31 changes: 31 additions & 0 deletions src/interfaces/IFlashAllowanceTransfer.sol
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion test/AllowanceTransferInvariants.t.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
2 changes: 1 addition & 1 deletion test/actors/Permitter.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
2 changes: 1 addition & 1 deletion test/actors/Spender.sol
Original file line number Diff line number Diff line change
@@ -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";
Expand Down