Skip to content

Commit

Permalink
flip conduit allowance value for permit()
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanio committed Nov 29, 2023
1 parent 057bb1f commit 80d64b8
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/lib/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ uint256 constant SOLADY_ERC20_BALANCE_SLOT_SEED = 0x87a211a2;
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 constant SOLADY_ERC20_TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
uint256 constant SOLADY_ERC20_APPROVAL_EVENT_SIGNATURE = 0;
/// @dev Solady ERC20 nonces slot seed with signature prefix.
uint256 constant SOLADY_ERC20_NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901;
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 constant SOLADY_ERC20_DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/// @dev Solady ERC20 version hash: `keccak256("1")`.
bytes32 constant SOLADY_ERC20_VERSION_HASH =
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
/// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
bytes32 constant SOLADY_ERC20_PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
83 changes: 82 additions & 1 deletion src/tokens/erc20/ERC20ConduitPreapproved_Solady.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import {
CONDUIT,
SOLADY_ERC20_ALLOWANCE_SLOT_SEED,
SOLADY_ERC20_BALANCE_SLOT_SEED,
SOLADY_ERC20_TRANSFER_EVENT_SIGNATURE
SOLADY_ERC20_TRANSFER_EVENT_SIGNATURE,
SOLADY_ERC20_NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX,
SOLADY_ERC20_DOMAIN_TYPEHASH,
SOLADY_ERC20_PERMIT_TYPEHASH,
SOLADY_ERC20_VERSION_TYPEHASH,
SOLADY_ERC20_APPROVAL_EVENT_SIGNATURE
} from "../../lib/Constants.sol";
import {IPreapprovalForAll} from "../../interfaces/IPreapprovalForAll.sol";

Expand Down Expand Up @@ -103,6 +108,82 @@ abstract contract ERC20ConduitPreapproved_Solady is ERC20, IPreapprovalForAll {
return true;
}

function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
/// @solidity memory-safe-assembly
assembly {
// Revert if the block timestamp greater than `deadline`.
if gt(timestamp(), deadline) {
mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
revert(0x1c, 0x04)
}
let m := mload(0x40) // Grab the free memory pointer.
// Clean the upper 96 bits.
owner := shr(96, shl(96, owner))
spender := shr(96, shl(96, spender))
// Compute the nonce slot and load its value.
mstore(0x0e, SOLADY_ERC20_NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX)
mstore(0x00, owner)
let nonceSlot := keccak256(0x0c, 0x20)
let nonceValue := sload(nonceSlot)
// Prepare the domain separator.
mstore(m, SOLADY_ERC20_DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), SOLADY_ERC20_VERSION_HASH)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
mstore(0x2e, keccak256(m, 0xa0))
// Prepare the struct hash.
mstore(m, SOLADY_ERC20_PERMIT_TYPEHASH)
mstore(add(m, 0x20), owner)
mstore(add(m, 0x40), spender)
mstore(add(m, 0x60), value)
mstore(add(m, 0x80), nonceValue)
mstore(add(m, 0xa0), deadline)
mstore(0x4e, keccak256(m, 0xc0))
// Prepare the ecrecover calldata.
mstore(0x00, keccak256(0x2c, 0x42))
mstore(0x20, and(0xff, v))
mstore(0x40, r)
mstore(0x60, s)
let t := staticcall(gas(), 1, 0, 0x80, 0x20, 0x20)
// If the ecrecover fails, the returndatasize will be 0x00,
// `owner` will be be checked if it equals the hash at 0x00,
// which evaluates to false (i.e. 0), and we will revert.
// If the ecrecover succeeds, the returndatasize will be 0x20,
// `owner` will be compared against the returned address at 0x20.
if iszero(eq(mload(returndatasize()), owner)) {
mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
revert(0x1c, 0x04)
}
// Increment and store the updated nonce.
sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds.
// Compute the allowance slot and store the value.
// The `owner` is already at slot 0x20.
mstore(0x40, or(shl(160, SOLADY_ERC20_ALLOWANCE_SLOT_SEED), spender))

// "flip" allowance value if caller is CONDUIT and if value is 0 or type(uint256).max.
value :=
xor(value, mul(and(eq(caller(), CONDUIT), iszero(and(value, not(value)))), not(0)))

sstore(keccak256(0x2c, 0x34), value)
// Emit the {Approval} event.
log3(add(m, 0x60), 0x20, SOLADY_ERC20_APPROVAL_EVENT_SIGNATURE, owner, spender)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}

function _spendAllowance(address owner, address spender, uint256 amount) internal virtual override {
if (spender == CONDUIT) {
uint256 allowance_ = super.allowance(owner, spender);
Expand Down

0 comments on commit 80d64b8

Please sign in to comment.