Skip to content

Commit

Permalink
erc1155 changes
Browse files Browse the repository at this point in the history
  • Loading branch information
snreynolds committed Jan 13, 2023
1 parent 4a5a145 commit 00ad685
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 41 deletions.
105 changes: 82 additions & 23 deletions src/ERC1155/AllowanceTransferERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity 0.8.17;

import {PermitHashERC1155} from "./libraries/PermitHashERC1155.sol";
import {ERC1155} from "solmate/src/tokens/ERC1155.sol";
import {SignatureVerification} from "../shared/SignatureVerification.sol";
import {EIP712ForERC1155} from "./EIP712ForERC1155.sol";
import {IAllowanceTransferERC1155} from "./interfaces/IAllowanceTransferERC1155.sol";
Expand All @@ -15,15 +16,26 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
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;
/// @dev Indexed in the order of token owner address, token address, spender address, tokenId
/// @dev The stored word saves the allowed amount of the tokenId, expiration on the allowance, and nonce
mapping(address => mapping(address => mapping(address => mapping(uint256 => PackedAllowance)))) public allowance;

/// @notice Maps users to tokens to spender and sets whether or not the spender has operator status on an entire token collection.
/// @dev Indexed in the order of token owner address, token address, then spender address.
/// @dev Sets a timestamp at which the spender no longer has operator status. Max expiration is type(uint48).max
mapping(address => mapping(address => mapping(address => PackedOperatorAllowance))) public operators;

/// @inheritdoc IAllowanceTransferERC1155
function approve(address token, address spender, uint160 amount, uint48 expiration) external {
PackedAllowance storage allowed = allowance[msg.sender][token][spender];
function approve(address token, address spender, uint160 amount, uint256 tokenId, uint48 expiration) external {
PackedAllowance storage allowed = allowance[msg.sender][token][spender][tokenId];
allowed.updateAmountAndExpiration(amount, expiration);
emit Approval(msg.sender, token, spender, amount, expiration);
emit Approval(msg.sender, token, spender, tokenId, amount, expiration);
}

/// @inheritdoc IAllowanceTransferERC1155
function setApprovalForAll(address token, address spender, uint48 expiration) external {
operators[msg.sender][token][spender].expiration = expiration;
emit ApprovalForAll(msg.sender, token, spender, expiration);
}

/// @inheritdoc IAllowanceTransferERC1155
Expand Down Expand Up @@ -53,8 +65,8 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
}

/// @inheritdoc IAllowanceTransferERC1155
function transferFrom(address from, address to, uint160 amount, address token) external {
_transfer(from, to, amount, token);
function transferFrom(address from, address to, uint256 tokenId, uint160 amount, address token) external {
_transfer(from, to, tokenId, amount, token);
}

/// @inheritdoc IAllowanceTransferERC1155
Expand All @@ -63,22 +75,37 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
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);
_transfer(
transferDetail.from,
transferDetail.to,
transferDetail.tokenId,
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];
function _transfer(address from, address to, uint256 tokenId, uint160 amount, address token) private {
PackedAllowance storage allowed = allowance[from][token][msg.sender][tokenId];

PackedOperatorAllowance storage operator = operators[from][token][msg.sender];
bool operatorExpired = block.timestamp > operator.expiration;

if (block.timestamp > allowed.expiration) revert AllowanceExpired(allowed.expiration);
// At least one of the approval methods must not be expired.
if (block.timestamp > allowed.expiration && operatorExpired) {
revert AllowanceExpired(allowed.expiration, operator.expiration);
}

uint256 maxAmount = allowed.amount;
if (maxAmount != type(uint160).max) {
if (amount > maxAmount) {
revert InsufficientAllowance(maxAmount);
// There is not a valid approval on the allowance mapping.
// However, only revert if there is also not a valid approval on the operator mapping.
// Otherwise, the spender is an operator & can transfer any amount of any tokenId in the collection.
if (operatorExpired) revert InsufficientAllowance(maxAmount);
} else {
unchecked {
allowed.amount = uint160(maxAmount) - amount;
Expand All @@ -87,28 +114,58 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
}

// Transfer the tokens from the from address to the recipient.
ERC20(token).safeTransferFrom(from, to, amount);
ERC1155(token).safeTransferFrom(from, to, tokenId, amount, "");
}

/// @inheritdoc IAllowanceTransferERC1155
function lockdown(TokenSpenderPair[] calldata approvals) external {
function lockdown(TokenSpenderPair[] calldata operatorApprovals, TokenSpenderTokenId[] calldata tokenIdApprovals)
external
{
address owner = msg.sender;
// Revoke allowances for each pair of spenders and tokens.

unchecked {
uint256 length = approvals.length;
// Revoke operator allowances for each pair of spenders and tokens.
uint256 length = operatorApprovals.length;
for (uint256 i = 0; i < length; ++i) {
address token = approvals[i].token;
address spender = approvals[i].spender;
address token = operatorApprovals[i].token;
address spender = operatorApprovals[i].spender;

allowance[owner][token][spender].amount = 0;
operators[owner][token][spender].expiration = 0;
emit Lockdown(owner, token, spender);
}
}

unchecked {
// Revoke tokenId allowances for each tuple of token, spender, and tokenId.
uint256 length = tokenIdApprovals.length;
for (uint256 i = 0; i < length; i++) {
address token = tokenIdApprovals[i].token;
address spender = tokenIdApprovals[i].spender;
uint256 tokenId = tokenIdApprovals[i].tokenId;
allowance[owner][token][spender][tokenId].amount = 0;
}
}
}

/// @inheritdoc IAllowanceTransferERC1155
function invalidateNonces(address token, address spender, uint256 tokenId, uint48 newNonce) external {
uint48 oldNonce = allowance[msg.sender][token][spender][tokenId].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][tokenId].nonce = newNonce;
emit NonceInvalidation(msg.sender, token, spender, tokenId, newNonce, oldNonce);
}

/// @inheritdoc IAllowanceTransferERC1155
function invalidateNonces(address token, address spender, uint48 newNonce) external {
uint48 oldNonce = allowance[msg.sender][token][spender].nonce;
uint48 oldNonce = operators[msg.sender][token][spender].nonce;

if (newNonce <= oldNonce) revert InvalidNonce();

Expand All @@ -118,7 +175,7 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
if (delta > type(uint16).max) revert ExcessiveInvalidation();
}

allowance[msg.sender][token][spender].nonce = newNonce;
operators[msg.sender][token][spender].nonce = newNonce;
emit NonceInvalidation(msg.sender, token, spender, newNonce, oldNonce);
}

Expand All @@ -129,8 +186,10 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
uint48 nonce = details.nonce;
address token = details.token;
uint160 amount = details.amount;
uint256 tokenId = details.tokenId;
uint48 expiration = details.expiration;
PackedAllowance storage allowed = allowance[owner][token][spender];

PackedAllowance storage allowed = allowance[owner][token][spender][tokenId];

if (allowed.nonce != nonce) revert InvalidNonce();

Expand Down
13 changes: 9 additions & 4 deletions src/ERC1155/SignatureTransferERC1155.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// 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";
import {ISignatureTransferERC1155} from "./interfaces/ISignatureTransferERC1155.sol";
import {ERC1155} from "solmate/src/tokens/ERC1155.sol";

contract SignatureTransferERC1155 is ISignatureTransferERC1155, EIP712ForERC1155 {
using SignatureVerification for bytes;
using PermitHashERC1155 for PermitTransferFrom;
using PermitHashERC1155 for PermitBatchTransferFrom;

/// @inheritdoc ISignatureTransferERC115
/// @inheritdoc ISignatureTransferERC1155
mapping(address => mapping(uint256 => uint256)) public nonceBitmap;

/// @inheritdoc ISignatureTransferERC1155
Expand Down Expand Up @@ -61,7 +62,9 @@ contract SignatureTransferERC1155 is ISignatureTransferERC1155, EIP712ForERC1155

signature.verify(_hashTypedData(dataHash), owner);

ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount);
ERC1155(permit.permitted.token).safeTransferFrom(
owner, transferDetails.to, permit.permitted.tokenId, requestedAmount, ""
);
}

/// @inheritdoc ISignatureTransferERC1155
Expand Down Expand Up @@ -117,7 +120,9 @@ contract SignatureTransferERC1155 is ISignatureTransferERC1155, EIP712ForERC1155

if (requestedAmount != 0) {
// allow spender to specify which of the permitted tokens should be transferred
ERC20(permitted.token).safeTransferFrom(owner, transferDetails[i].to, requestedAmount);
ERC1155(permitted.token).safeTransferFrom(
owner, transferDetails[i].to, permitted.tokenId, requestedAmount, ""
);
}
}
}
Expand Down
80 changes: 69 additions & 11 deletions src/ERC1155/interfaces/IAllowanceTransferERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ pragma solidity ^0.8.17;
/// @dev Requires user's token approval on the Permit2 contract
interface IAllowanceTransferERC1155 {
/// @notice Thrown when an allowance on a token has expired.
/// @param deadline The timestamp at which the allowed amount is no longer valid
error AllowanceExpired(uint256 deadline);
/// @param allowanceDeadline The timestamp at which the permissions on the token for a specific tokenId are no longer valid
/// @param operatorDeadline The timestamp at which the permissions given to an operator of an entire collection are no longer valid.
error AllowanceExpired(uint256 allowanceDeadline, uint256 operatorDeadline);

/// @notice Thrown when an allowance on a token has been depleted.
/// @param amount The maximum amount allowed
Expand All @@ -16,16 +17,34 @@ interface IAllowanceTransferERC1155 {
/// @notice Thrown when too many nonces are invalidated.
error ExcessiveInvalidation();

/// @notice Emits an event when the owner successfully invalidates an ordered nonce.
/// @notice Emits an event when the owner successfully invalidates an ordered nonce for the allowance mapping.
event NonceInvalidation(
address indexed owner,
address indexed token,
address indexed spender,
uint256 tokenId,
uint48 newNonce,
uint48 oldNonce
);

/// @notice Emits an event when the owner successfully invalidates an ordered nonce for the operator mapping.
event NonceInvalidation(
address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce
);

/// @notice Emits an event when the owner successfully sets permissions on a token for the spender.
event Approval(
address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration
address indexed owner,
address indexed token,
address indexed spender,
uint256 tokenId,
uint160 amount,
uint48 expiration
);

/// @notice Emits an event when the owner successfully gives a spender operator permissions on a token.
event ApprovalForAll(address indexed owner, address indexed token, address indexed spender, uint48 expiration);

/// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.
event Permit(
address indexed owner,
Expand All @@ -41,8 +60,10 @@ interface IAllowanceTransferERC1155 {

/// @notice The permit data for a token
struct PermitDetails {
// ERC20 token address
// ERC1155 token address
address token;
// tokenId
uint256 tokenId;
// the maximum amount allowed to spend
uint160 amount;
// timestamp at which a spender's token allowances become invalid
Expand Down Expand Up @@ -83,6 +104,13 @@ interface IAllowanceTransferERC1155 {
uint48 nonce;
}

/// @notice The saved expiration on the operator.
/// @dev Holds a nonce value to prevent replay protection.
struct PackedOperatorAllowance {
uint48 expiration;
uint48 nonce;
}

/// @notice A token spender pair.
struct TokenSpenderPair {
// the token the spender is approved
Expand All @@ -91,6 +119,16 @@ interface IAllowanceTransferERC1155 {
address spender;
}

/// @notice A token spender pair.
struct TokenSpenderTokenId {
// the token the spender is approved
address token;
// the spender address
address spender;
// the tokenId approved
uint256 tokenId;
}

/// @notice Details for a token transfer.
struct AllowanceTransferDetails {
// the owner of the token
Expand All @@ -101,12 +139,14 @@ interface IAllowanceTransferERC1155 {
uint160 amount;
// the token to be transferred
address token;
// the tokenId of the token
uint256 tokenId;
}

/// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.
/// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]
/// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals.
function allowance(address, address, address) external view returns (uint160, uint48, uint48);
function allowance(address, address, address, uint256) external view returns (uint160, uint48, uint48);

/// @notice Approves the spender to use up to amount of the specified token up until the expiration
/// @param token The token to approve
Expand All @@ -115,7 +155,15 @@ interface IAllowanceTransferERC1155 {
/// @param expiration The timestamp at which the approval is no longer valid
/// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
/// @dev Setting amount to type(uint160).max sets an unlimited approval
function approve(address token, address spender, uint160 amount, uint48 expiration) external;
function approve(address token, address spender, uint160 amount, uint256 tokenId, uint48 expiration) external;

/// @notice Approves the spender to be an operator of the specified token up until the expiration
/// @param token The token to approve
/// @param spender The spender address to approve
/// @param expiration The timestamp at which the operator approval is no longer valid
/// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
/// @dev Passing in expiration as 0 DOES NOT set the expiration to the block.timestamp unlike `approve`.
function setApprovalForAll(address token, address spender, uint48 expiration) external;

/// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
Expand All @@ -138,7 +186,7 @@ interface IAllowanceTransferERC1155 {
/// @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 transferFrom(address from, address to, uint160 amount, address token) external;
function transferFrom(address from, address to, uint256 tokenId, uint160 amount, address token) external;

/// @notice Transfer approved tokens in a batch
/// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers
Expand All @@ -148,10 +196,20 @@ interface IAllowanceTransferERC1155 {

/// @notice Enables performing a "lockdown" of the sender's Permit2 identity
/// by batch revoking approvals
/// @param approvals Array of approvals to revoke.
function lockdown(TokenSpenderPair[] calldata approvals) external;
/// @param operatorApprovals Array of approvals to revoke on the operator mapping. Removes operator permissions.
/// @param tokenIdApprovals Array of approvals to revoke on the allowance mapping. Removes spender permissions on certain tokenIds.
function lockdown(TokenSpenderPair[] calldata operatorApprovals, TokenSpenderTokenId[] calldata tokenIdApprovals)
external;

/// @notice Invalidate nonces for a given (token, spender, tokenId) tuple on the allowance mapping.
/// @param token The token to invalidate nonces for
/// @param spender The spender to invalidate nonces for
/// @param tokenId The tokenId to invalidate the nonces for
/// @param newNonce The new nonce to set. Invalidates all nonces less than it.
/// @dev Can't invalidate more than 2**16 nonces per transaction.
function invalidateNonces(address token, address spender, uint256 tokenId, uint48 newNonce) external;

/// @notice Invalidate nonces for a given (token, spender) pair
/// @notice Invalidate nonces for a given (token, spender) pair on the operator mapping.
/// @param token The token to invalidate nonces for
/// @param spender The spender to invalidate nonces for
/// @param newNonce The new nonce to set. Invalidates all nonces less than it.
Expand Down
2 changes: 2 additions & 0 deletions src/ERC1155/interfaces/ISignatureTransferERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ interface ISignatureTransferERC1155 {
address token;
// the maximum amount that can be spent
uint256 amount;
// the tokenId that can be spent
uint256 tokenId;
}

/// @notice The signed permit message for a single token transfer
Expand Down
Loading

0 comments on commit 00ad685

Please sign in to comment.