-
Notifications
You must be signed in to change notification settings - Fork 197
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
ca6b6ff
commit 4a5a145
Showing
8 changed files
with
818 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
import {PermitHashERC1155} from "./libraries/PermitHashERC1155.sol"; | ||
import {SignatureVerification} from "../shared/SignatureVerification.sol"; | ||
import {EIP712ForERC1155} from "./EIP712ForERC1155.sol"; | ||
import {IAllowanceTransferERC1155} from "./interfaces/IAllowanceTransferERC1155.sol"; | ||
import {SignatureExpired, InvalidNonce} from "../shared/PermitErrors.sol"; | ||
import {AllowanceERC1155} from "./libraries/AllowanceERC1155.sol"; | ||
|
||
contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155 { | ||
using SignatureVerification for bytes; | ||
using PermitHashERC1155 for PermitSingle; | ||
using PermitHashERC1155 for PermitBatch; | ||
using AllowanceERC1155 for PackedAllowance; | ||
|
||
/// @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, expiration on the allowance, and nonce | ||
mapping(address => mapping(address => mapping(address => PackedAllowance))) public allowance; | ||
|
||
/// @inheritdoc IAllowanceTransferERC1155 | ||
function approve(address token, address spender, uint160 amount, uint48 expiration) external { | ||
PackedAllowance storage allowed = allowance[msg.sender][token][spender]; | ||
allowed.updateAmountAndExpiration(amount, expiration); | ||
emit Approval(msg.sender, token, spender, amount, expiration); | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransferERC1155 | ||
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external { | ||
if (block.timestamp > permitSingle.sigDeadline) revert SignatureExpired(permitSingle.sigDeadline); | ||
|
||
// Verify the signer address from the signature. | ||
signature.verify(_hashTypedData(permitSingle.hash()), owner); | ||
|
||
_updateApproval(permitSingle.details, owner, permitSingle.spender); | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransferERC1155 | ||
function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external { | ||
if (block.timestamp > permitBatch.sigDeadline) revert SignatureExpired(permitBatch.sigDeadline); | ||
|
||
// Verify the signer address from the signature. | ||
signature.verify(_hashTypedData(permitBatch.hash()), owner); | ||
|
||
address spender = permitBatch.spender; | ||
unchecked { | ||
uint256 length = permitBatch.details.length; | ||
for (uint256 i = 0; i < length; ++i) { | ||
_updateApproval(permitBatch.details[i], owner, spender); | ||
} | ||
} | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransferERC1155 | ||
function transferFrom(address from, address to, uint160 amount, address token) external { | ||
_transfer(from, to, amount, token); | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransferERC1155 | ||
function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external { | ||
unchecked { | ||
uint256 length = transferDetails.length; | ||
for (uint256 i = 0; i < length; ++i) { | ||
AllowanceTransferDetails memory transferDetail = transferDetails[i]; | ||
_transfer(transferDetail.from, transferDetail.to, transferDetail.amount, transferDetail.token); | ||
} | ||
} | ||
} | ||
|
||
/// @notice Internal function for transferring tokens using stored allowances | ||
/// @dev Will fail if the allowed timeframe has passed | ||
function _transfer(address from, address to, uint160 amount, address token) private { | ||
PackedAllowance storage allowed = allowance[from][token][msg.sender]; | ||
|
||
if (block.timestamp > allowed.expiration) revert AllowanceExpired(allowed.expiration); | ||
|
||
uint256 maxAmount = allowed.amount; | ||
if (maxAmount != type(uint160).max) { | ||
if (amount > maxAmount) { | ||
revert InsufficientAllowance(maxAmount); | ||
} else { | ||
unchecked { | ||
allowed.amount = uint160(maxAmount) - amount; | ||
} | ||
} | ||
} | ||
|
||
// Transfer the tokens from the from address to the recipient. | ||
ERC20(token).safeTransferFrom(from, to, amount); | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransferERC1155 | ||
function lockdown(TokenSpenderPair[] calldata approvals) external { | ||
address owner = msg.sender; | ||
// Revoke allowances for each pair of spenders and tokens. | ||
unchecked { | ||
uint256 length = approvals.length; | ||
for (uint256 i = 0; i < length; ++i) { | ||
address token = approvals[i].token; | ||
address spender = approvals[i].spender; | ||
|
||
allowance[owner][token][spender].amount = 0; | ||
emit Lockdown(owner, token, spender); | ||
} | ||
} | ||
} | ||
|
||
/// @inheritdoc IAllowanceTransferERC1155 | ||
function invalidateNonces(address token, address spender, uint48 newNonce) external { | ||
uint48 oldNonce = allowance[msg.sender][token][spender].nonce; | ||
|
||
if (newNonce <= oldNonce) revert InvalidNonce(); | ||
|
||
// Limit the amount of nonces that can be invalidated in one transaction. | ||
unchecked { | ||
uint48 delta = newNonce - oldNonce; | ||
if (delta > type(uint16).max) revert ExcessiveInvalidation(); | ||
} | ||
|
||
allowance[msg.sender][token][spender].nonce = newNonce; | ||
emit NonceInvalidation(msg.sender, token, spender, newNonce, oldNonce); | ||
} | ||
|
||
/// @notice Sets the new values for amount, expiration, and nonce. | ||
/// @dev Will check that the signed nonce is equal to the current nonce and then incrememnt the nonce value by 1. | ||
/// @dev Emits a Permit event. | ||
function _updateApproval(PermitDetails memory details, address owner, address spender) private { | ||
uint48 nonce = details.nonce; | ||
address token = details.token; | ||
uint160 amount = details.amount; | ||
uint48 expiration = details.expiration; | ||
PackedAllowance storage allowed = allowance[owner][token][spender]; | ||
|
||
if (allowed.nonce != nonce) revert InvalidNonce(); | ||
|
||
allowed.updateAll(amount, expiration, nonce); | ||
emit Permit(owner, token, spender, amount, expiration, nonce); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
/// @notice EIP712 helpers for Permit2 ERC1155s | ||
/// @dev Maintains cross-chain replay protection in the event of a fork | ||
/// @dev Reference: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/EIP712.sol | ||
contract EIP712ForERC1155 { | ||
// Cache the domain separator as an immutable value, but also store the chain id that it | ||
// corresponds to, in order to invalidate the cached domain separator if the chain id changes. | ||
bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; | ||
uint256 private immutable _CACHED_CHAIN_ID; | ||
|
||
bytes32 private constant _HASHED_NAME = keccak256("Permit2ERC1155"); | ||
bytes32 private constant _TYPE_HASH = | ||
keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)"); | ||
|
||
constructor() { | ||
_CACHED_CHAIN_ID = block.chainid; | ||
_CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME); | ||
} | ||
|
||
/// @notice Returns the domain separator for the current chain. | ||
/// @dev Uses cached version if chainid and address are unchanged from construction. | ||
function DOMAIN_SEPARATOR() public view returns (bytes32) { | ||
return block.chainid == _CACHED_CHAIN_ID | ||
? _CACHED_DOMAIN_SEPARATOR | ||
: _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME); | ||
} | ||
|
||
/// @notice Builds a domain separator using the current chainId and contract address. | ||
function _buildDomainSeparator(bytes32 typeHash, bytes32 nameHash) private view returns (bytes32) { | ||
return keccak256(abi.encode(typeHash, nameHash, block.chainid, address(this))); | ||
} | ||
|
||
/// @notice Creates an EIP-712 typed data hash | ||
function _hashTypedData(bytes32 dataHash) internal view returns (bytes32) { | ||
return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR(), dataHash)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
import {SignatureTransferERC1155} from "./SignatureTransferERC1155.sol"; | ||
import {AllowanceTransferERC1155} from "./AllowanceTransferERC1155.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 Permit2ERC1155 is SignatureTransferERC1155, AllowanceTransferERC1155 { | ||
// Permit2 unifies the two contracts so users have maximal flexibility with their approval. | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.17; | ||
|
||
import {ISignatureTransferERC1155} from "./interfaces/ISignatureTransferERC1155.sol"; | ||
import {SignatureExpired, InvalidNonce} from "../shared/PermitErrors.sol"; | ||
import {SignatureVerification} from "../shared/SignatureVerification.sol"; | ||
import {PermitHashERC1155} from "./libraries/PermitHashERC1155.sol"; | ||
import {EIP712ForERC1155} from "./EIP712ForERC1155.sol"; | ||
|
||
contract SignatureTransferERC1155 is ISignatureTransferERC1155, EIP712ForERC1155 { | ||
using SignatureVerification for bytes; | ||
using PermitHashERC1155 for PermitTransferFrom; | ||
using PermitHashERC1155 for PermitBatchTransferFrom; | ||
|
||
/// @inheritdoc ISignatureTransferERC115 | ||
mapping(address => mapping(uint256 => uint256)) public nonceBitmap; | ||
|
||
/// @inheritdoc ISignatureTransferERC1155 | ||
function permitTransferFrom( | ||
PermitTransferFrom memory permit, | ||
SignatureTransferDetails calldata transferDetails, | ||
address owner, | ||
bytes calldata signature | ||
) external { | ||
_permitTransferFrom(permit, transferDetails, owner, permit.hash(), signature); | ||
} | ||
|
||
/// @inheritdoc ISignatureTransferERC1155 | ||
function permitWitnessTransferFrom( | ||
PermitTransferFrom memory permit, | ||
SignatureTransferDetails calldata transferDetails, | ||
address owner, | ||
bytes32 witness, | ||
string calldata witnessTypeString, | ||
bytes calldata signature | ||
) external { | ||
_permitTransferFrom( | ||
permit, transferDetails, owner, permit.hashWithWitness(witness, witnessTypeString), signature | ||
); | ||
} | ||
|
||
/// @notice Transfers a token using a signed permit message. | ||
/// @param permit The permit data signed over by the owner | ||
/// @param dataHash The EIP-712 hash of permit data to include when checking signature | ||
/// @param owner The owner of the tokens to transfer | ||
/// @param transferDetails The spender's requested transfer details for the permitted token | ||
/// @param signature The signature to verify | ||
function _permitTransferFrom( | ||
PermitTransferFrom memory permit, | ||
SignatureTransferDetails calldata transferDetails, | ||
address owner, | ||
bytes32 dataHash, | ||
bytes calldata signature | ||
) private { | ||
uint256 requestedAmount = transferDetails.requestedAmount; | ||
|
||
if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline); | ||
if (requestedAmount > permit.permitted.amount) revert InvalidAmount(permit.permitted.amount); | ||
|
||
_useUnorderedNonce(owner, permit.nonce); | ||
|
||
signature.verify(_hashTypedData(dataHash), owner); | ||
|
||
ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount); | ||
} | ||
|
||
/// @inheritdoc ISignatureTransferERC1155 | ||
function permitTransferFrom( | ||
PermitBatchTransferFrom memory permit, | ||
SignatureTransferDetails[] calldata transferDetails, | ||
address owner, | ||
bytes calldata signature | ||
) external { | ||
_permitTransferFrom(permit, transferDetails, owner, permit.hash(), signature); | ||
} | ||
|
||
/// @inheritdoc ISignatureTransferERC1155 | ||
function permitWitnessTransferFrom( | ||
PermitBatchTransferFrom memory permit, | ||
SignatureTransferDetails[] calldata transferDetails, | ||
address owner, | ||
bytes32 witness, | ||
string calldata witnessTypeString, | ||
bytes calldata signature | ||
) external { | ||
_permitTransferFrom( | ||
permit, transferDetails, owner, permit.hashWithWitness(witness, witnessTypeString), signature | ||
); | ||
} | ||
|
||
/// @notice Transfers tokens using a signed permit messages | ||
/// @param permit The permit data signed over by the owner | ||
/// @param dataHash The EIP-712 hash of permit data to include when checking signature | ||
/// @param owner The owner of the tokens to transfer | ||
/// @param signature The signature to verify | ||
function _permitTransferFrom( | ||
PermitBatchTransferFrom memory permit, | ||
SignatureTransferDetails[] calldata transferDetails, | ||
address owner, | ||
bytes32 dataHash, | ||
bytes calldata signature | ||
) private { | ||
uint256 numPermitted = permit.permitted.length; | ||
|
||
if (block.timestamp > permit.deadline) revert SignatureExpired(permit.deadline); | ||
if (numPermitted != transferDetails.length) revert LengthMismatch(); | ||
|
||
_useUnorderedNonce(owner, permit.nonce); | ||
signature.verify(_hashTypedData(dataHash), owner); | ||
|
||
unchecked { | ||
for (uint256 i = 0; i < numPermitted; ++i) { | ||
TokenPermissions memory permitted = permit.permitted[i]; | ||
uint256 requestedAmount = transferDetails[i].requestedAmount; | ||
|
||
if (requestedAmount > permitted.amount) revert InvalidAmount(permitted.amount); | ||
|
||
if (requestedAmount != 0) { | ||
// allow spender to specify which of the permitted tokens should be transferred | ||
ERC20(permitted.token).safeTransferFrom(owner, transferDetails[i].to, requestedAmount); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// @inheritdoc ISignatureTransferERC1155 | ||
function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external { | ||
nonceBitmap[msg.sender][wordPos] |= mask; | ||
|
||
emit UnorderedNonceInvalidation(msg.sender, wordPos, mask); | ||
} | ||
|
||
/// @notice Returns the index of the bitmap and the bit position within the bitmap. Used for unordered nonces | ||
/// @param nonce The nonce to get the associated word and bit positions | ||
/// @return wordPos The word position or index into the nonceBitmap | ||
/// @return bitPos The bit position | ||
/// @dev The first 248 bits of the nonce value is the index of the desired bitmap | ||
/// @dev The last 8 bits of the nonce value is the position of the bit in the bitmap | ||
function bitmapPositions(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) { | ||
wordPos = uint248(nonce >> 8); | ||
bitPos = uint8(nonce); | ||
} | ||
|
||
/// @notice Checks whether a nonce is taken and sets the bit at the bit position in the bitmap at the word position | ||
/// @param from The address to use the nonce at | ||
/// @param nonce The nonce to spend | ||
function _useUnorderedNonce(address from, uint256 nonce) internal { | ||
(uint256 wordPos, uint256 bitPos) = bitmapPositions(nonce); | ||
uint256 bit = 1 << bitPos; | ||
uint256 flipped = nonceBitmap[from][wordPos] ^= bit; | ||
|
||
if (flipped & bit == 0) revert InvalidNonce(); | ||
} | ||
} |
Oops, something went wrong.