Skip to content

Commit

Permalink
add 1155 directory and rename
Browse files Browse the repository at this point in the history
  • Loading branch information
snreynolds committed Jan 13, 2023
1 parent ca6b6ff commit 4a5a145
Show file tree
Hide file tree
Showing 8 changed files with 818 additions and 0 deletions.
140 changes: 140 additions & 0 deletions src/ERC1155/AllowanceTransferERC1155.sol
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);
}
}
39 changes: 39 additions & 0 deletions src/ERC1155/EIP712ForERC1155.sol
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));
}
}
11 changes: 11 additions & 0 deletions src/ERC1155/Permit2.sol
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.
}
154 changes: 154 additions & 0 deletions src/ERC1155/SignatureTransferERC1155.sol
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();
}
}
Loading

0 comments on commit 4a5a145

Please sign in to comment.