Skip to content

Commit 00ad685

Browse files
committed
erc1155 changes
1 parent 4a5a145 commit 00ad685

File tree

6 files changed

+169
-41
lines changed

6 files changed

+169
-41
lines changed

src/ERC1155/AllowanceTransferERC1155.sol

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
pragma solidity 0.8.17;
33

44
import {PermitHashERC1155} from "./libraries/PermitHashERC1155.sol";
5+
import {ERC1155} from "solmate/src/tokens/ERC1155.sol";
56
import {SignatureVerification} from "../shared/SignatureVerification.sol";
67
import {EIP712ForERC1155} from "./EIP712ForERC1155.sol";
78
import {IAllowanceTransferERC1155} from "./interfaces/IAllowanceTransferERC1155.sol";
@@ -15,15 +16,26 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
1516
using AllowanceERC1155 for PackedAllowance;
1617

1718
/// @notice Maps users to tokens to spender addresses and information about the approval on the token
18-
/// @dev Indexed in the order of token owner address, token address, spender address
19-
/// @dev The stored word saves the allowed amount, expiration on the allowance, and nonce
20-
mapping(address => mapping(address => mapping(address => PackedAllowance))) public allowance;
19+
/// @dev Indexed in the order of token owner address, token address, spender address, tokenId
20+
/// @dev The stored word saves the allowed amount of the tokenId, expiration on the allowance, and nonce
21+
mapping(address => mapping(address => mapping(address => mapping(uint256 => PackedAllowance)))) public allowance;
22+
23+
/// @notice Maps users to tokens to spender and sets whether or not the spender has operator status on an entire token collection.
24+
/// @dev Indexed in the order of token owner address, token address, then spender address.
25+
/// @dev Sets a timestamp at which the spender no longer has operator status. Max expiration is type(uint48).max
26+
mapping(address => mapping(address => mapping(address => PackedOperatorAllowance))) public operators;
2127

2228
/// @inheritdoc IAllowanceTransferERC1155
23-
function approve(address token, address spender, uint160 amount, uint48 expiration) external {
24-
PackedAllowance storage allowed = allowance[msg.sender][token][spender];
29+
function approve(address token, address spender, uint160 amount, uint256 tokenId, uint48 expiration) external {
30+
PackedAllowance storage allowed = allowance[msg.sender][token][spender][tokenId];
2531
allowed.updateAmountAndExpiration(amount, expiration);
26-
emit Approval(msg.sender, token, spender, amount, expiration);
32+
emit Approval(msg.sender, token, spender, tokenId, amount, expiration);
33+
}
34+
35+
/// @inheritdoc IAllowanceTransferERC1155
36+
function setApprovalForAll(address token, address spender, uint48 expiration) external {
37+
operators[msg.sender][token][spender].expiration = expiration;
38+
emit ApprovalForAll(msg.sender, token, spender, expiration);
2739
}
2840

2941
/// @inheritdoc IAllowanceTransferERC1155
@@ -53,8 +65,8 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
5365
}
5466

5567
/// @inheritdoc IAllowanceTransferERC1155
56-
function transferFrom(address from, address to, uint160 amount, address token) external {
57-
_transfer(from, to, amount, token);
68+
function transferFrom(address from, address to, uint256 tokenId, uint160 amount, address token) external {
69+
_transfer(from, to, tokenId, amount, token);
5870
}
5971

6072
/// @inheritdoc IAllowanceTransferERC1155
@@ -63,22 +75,37 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
6375
uint256 length = transferDetails.length;
6476
for (uint256 i = 0; i < length; ++i) {
6577
AllowanceTransferDetails memory transferDetail = transferDetails[i];
66-
_transfer(transferDetail.from, transferDetail.to, transferDetail.amount, transferDetail.token);
78+
_transfer(
79+
transferDetail.from,
80+
transferDetail.to,
81+
transferDetail.tokenId,
82+
transferDetail.amount,
83+
transferDetail.token
84+
);
6785
}
6886
}
6987
}
7088

7189
/// @notice Internal function for transferring tokens using stored allowances
7290
/// @dev Will fail if the allowed timeframe has passed
73-
function _transfer(address from, address to, uint160 amount, address token) private {
74-
PackedAllowance storage allowed = allowance[from][token][msg.sender];
91+
function _transfer(address from, address to, uint256 tokenId, uint160 amount, address token) private {
92+
PackedAllowance storage allowed = allowance[from][token][msg.sender][tokenId];
93+
94+
PackedOperatorAllowance storage operator = operators[from][token][msg.sender];
95+
bool operatorExpired = block.timestamp > operator.expiration;
7596

76-
if (block.timestamp > allowed.expiration) revert AllowanceExpired(allowed.expiration);
97+
// At least one of the approval methods must not be expired.
98+
if (block.timestamp > allowed.expiration && operatorExpired) {
99+
revert AllowanceExpired(allowed.expiration, operator.expiration);
100+
}
77101

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

89116
// Transfer the tokens from the from address to the recipient.
90-
ERC20(token).safeTransferFrom(from, to, amount);
117+
ERC1155(token).safeTransferFrom(from, to, tokenId, amount, "");
91118
}
92119

93120
/// @inheritdoc IAllowanceTransferERC1155
94-
function lockdown(TokenSpenderPair[] calldata approvals) external {
121+
function lockdown(TokenSpenderPair[] calldata operatorApprovals, TokenSpenderTokenId[] calldata tokenIdApprovals)
122+
external
123+
{
95124
address owner = msg.sender;
96-
// Revoke allowances for each pair of spenders and tokens.
125+
97126
unchecked {
98-
uint256 length = approvals.length;
127+
// Revoke operator allowances for each pair of spenders and tokens.
128+
uint256 length = operatorApprovals.length;
99129
for (uint256 i = 0; i < length; ++i) {
100-
address token = approvals[i].token;
101-
address spender = approvals[i].spender;
130+
address token = operatorApprovals[i].token;
131+
address spender = operatorApprovals[i].spender;
102132

103-
allowance[owner][token][spender].amount = 0;
133+
operators[owner][token][spender].expiration = 0;
104134
emit Lockdown(owner, token, spender);
105135
}
106136
}
137+
138+
unchecked {
139+
// Revoke tokenId allowances for each tuple of token, spender, and tokenId.
140+
uint256 length = tokenIdApprovals.length;
141+
for (uint256 i = 0; i < length; i++) {
142+
address token = tokenIdApprovals[i].token;
143+
address spender = tokenIdApprovals[i].spender;
144+
uint256 tokenId = tokenIdApprovals[i].tokenId;
145+
allowance[owner][token][spender][tokenId].amount = 0;
146+
}
147+
}
148+
}
149+
150+
/// @inheritdoc IAllowanceTransferERC1155
151+
function invalidateNonces(address token, address spender, uint256 tokenId, uint48 newNonce) external {
152+
uint48 oldNonce = allowance[msg.sender][token][spender][tokenId].nonce;
153+
154+
if (newNonce <= oldNonce) revert InvalidNonce();
155+
156+
// Limit the amount of nonces that can be invalidated in one transaction.
157+
unchecked {
158+
uint48 delta = newNonce - oldNonce;
159+
if (delta > type(uint16).max) revert ExcessiveInvalidation();
160+
}
161+
162+
allowance[msg.sender][token][spender][tokenId].nonce = newNonce;
163+
emit NonceInvalidation(msg.sender, token, spender, tokenId, newNonce, oldNonce);
107164
}
108165

109166
/// @inheritdoc IAllowanceTransferERC1155
110167
function invalidateNonces(address token, address spender, uint48 newNonce) external {
111-
uint48 oldNonce = allowance[msg.sender][token][spender].nonce;
168+
uint48 oldNonce = operators[msg.sender][token][spender].nonce;
112169

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

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

121-
allowance[msg.sender][token][spender].nonce = newNonce;
178+
operators[msg.sender][token][spender].nonce = newNonce;
122179
emit NonceInvalidation(msg.sender, token, spender, newNonce, oldNonce);
123180
}
124181

@@ -129,8 +186,10 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
129186
uint48 nonce = details.nonce;
130187
address token = details.token;
131188
uint160 amount = details.amount;
189+
uint256 tokenId = details.tokenId;
132190
uint48 expiration = details.expiration;
133-
PackedAllowance storage allowed = allowance[owner][token][spender];
191+
192+
PackedAllowance storage allowed = allowance[owner][token][spender][tokenId];
134193

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

src/ERC1155/SignatureTransferERC1155.sol

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.17;
33

4-
import {ISignatureTransferERC1155} from "./interfaces/ISignatureTransferERC1155.sol";
54
import {SignatureExpired, InvalidNonce} from "../shared/PermitErrors.sol";
65
import {SignatureVerification} from "../shared/SignatureVerification.sol";
76
import {PermitHashERC1155} from "./libraries/PermitHashERC1155.sol";
87
import {EIP712ForERC1155} from "./EIP712ForERC1155.sol";
8+
import {ISignatureTransferERC1155} from "./interfaces/ISignatureTransferERC1155.sol";
9+
import {ERC1155} from "solmate/src/tokens/ERC1155.sol";
910

1011
contract SignatureTransferERC1155 is ISignatureTransferERC1155, EIP712ForERC1155 {
1112
using SignatureVerification for bytes;
1213
using PermitHashERC1155 for PermitTransferFrom;
1314
using PermitHashERC1155 for PermitBatchTransferFrom;
1415

15-
/// @inheritdoc ISignatureTransferERC115
16+
/// @inheritdoc ISignatureTransferERC1155
1617
mapping(address => mapping(uint256 => uint256)) public nonceBitmap;
1718

1819
/// @inheritdoc ISignatureTransferERC1155
@@ -61,7 +62,9 @@ contract SignatureTransferERC1155 is ISignatureTransferERC1155, EIP712ForERC1155
6162

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

64-
ERC20(permit.permitted.token).safeTransferFrom(owner, transferDetails.to, requestedAmount);
65+
ERC1155(permit.permitted.token).safeTransferFrom(
66+
owner, transferDetails.to, permit.permitted.tokenId, requestedAmount, ""
67+
);
6568
}
6669

6770
/// @inheritdoc ISignatureTransferERC1155
@@ -117,7 +120,9 @@ contract SignatureTransferERC1155 is ISignatureTransferERC1155, EIP712ForERC1155
117120

118121
if (requestedAmount != 0) {
119122
// allow spender to specify which of the permitted tokens should be transferred
120-
ERC20(permitted.token).safeTransferFrom(owner, transferDetails[i].to, requestedAmount);
123+
ERC1155(permitted.token).safeTransferFrom(
124+
owner, transferDetails[i].to, permitted.tokenId, requestedAmount, ""
125+
);
121126
}
122127
}
123128
}

src/ERC1155/interfaces/IAllowanceTransferERC1155.sol

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ pragma solidity ^0.8.17;
66
/// @dev Requires user's token approval on the Permit2 contract
77
interface IAllowanceTransferERC1155 {
88
/// @notice Thrown when an allowance on a token has expired.
9-
/// @param deadline The timestamp at which the allowed amount is no longer valid
10-
error AllowanceExpired(uint256 deadline);
9+
/// @param allowanceDeadline The timestamp at which the permissions on the token for a specific tokenId are no longer valid
10+
/// @param operatorDeadline The timestamp at which the permissions given to an operator of an entire collection are no longer valid.
11+
error AllowanceExpired(uint256 allowanceDeadline, uint256 operatorDeadline);
1112

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

19-
/// @notice Emits an event when the owner successfully invalidates an ordered nonce.
20+
/// @notice Emits an event when the owner successfully invalidates an ordered nonce for the allowance mapping.
21+
event NonceInvalidation(
22+
address indexed owner,
23+
address indexed token,
24+
address indexed spender,
25+
uint256 tokenId,
26+
uint48 newNonce,
27+
uint48 oldNonce
28+
);
29+
30+
/// @notice Emits an event when the owner successfully invalidates an ordered nonce for the operator mapping.
2031
event NonceInvalidation(
2132
address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce
2233
);
2334

2435
/// @notice Emits an event when the owner successfully sets permissions on a token for the spender.
2536
event Approval(
26-
address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration
37+
address indexed owner,
38+
address indexed token,
39+
address indexed spender,
40+
uint256 tokenId,
41+
uint160 amount,
42+
uint48 expiration
2743
);
2844

45+
/// @notice Emits an event when the owner successfully gives a spender operator permissions on a token.
46+
event ApprovalForAll(address indexed owner, address indexed token, address indexed spender, uint48 expiration);
47+
2948
/// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.
3049
event Permit(
3150
address indexed owner,
@@ -41,8 +60,10 @@ interface IAllowanceTransferERC1155 {
4160

4261
/// @notice The permit data for a token
4362
struct PermitDetails {
44-
// ERC20 token address
63+
// ERC1155 token address
4564
address token;
65+
// tokenId
66+
uint256 tokenId;
4667
// the maximum amount allowed to spend
4768
uint160 amount;
4869
// timestamp at which a spender's token allowances become invalid
@@ -83,6 +104,13 @@ interface IAllowanceTransferERC1155 {
83104
uint48 nonce;
84105
}
85106

107+
/// @notice The saved expiration on the operator.
108+
/// @dev Holds a nonce value to prevent replay protection.
109+
struct PackedOperatorAllowance {
110+
uint48 expiration;
111+
uint48 nonce;
112+
}
113+
86114
/// @notice A token spender pair.
87115
struct TokenSpenderPair {
88116
// the token the spender is approved
@@ -91,6 +119,16 @@ interface IAllowanceTransferERC1155 {
91119
address spender;
92120
}
93121

122+
/// @notice A token spender pair.
123+
struct TokenSpenderTokenId {
124+
// the token the spender is approved
125+
address token;
126+
// the spender address
127+
address spender;
128+
// the tokenId approved
129+
uint256 tokenId;
130+
}
131+
94132
/// @notice Details for a token transfer.
95133
struct AllowanceTransferDetails {
96134
// the owner of the token
@@ -101,12 +139,14 @@ interface IAllowanceTransferERC1155 {
101139
uint160 amount;
102140
// the token to be transferred
103141
address token;
142+
// the tokenId of the token
143+
uint256 tokenId;
104144
}
105145

106146
/// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.
107147
/// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]
108148
/// @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.
109-
function allowance(address, address, address) external view returns (uint160, uint48, uint48);
149+
function allowance(address, address, address, uint256) external view returns (uint160, uint48, uint48);
110150

111151
/// @notice Approves the spender to use up to amount of the specified token up until the expiration
112152
/// @param token The token to approve
@@ -115,7 +155,15 @@ interface IAllowanceTransferERC1155 {
115155
/// @param expiration The timestamp at which the approval is no longer valid
116156
/// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
117157
/// @dev Setting amount to type(uint160).max sets an unlimited approval
118-
function approve(address token, address spender, uint160 amount, uint48 expiration) external;
158+
function approve(address token, address spender, uint160 amount, uint256 tokenId, uint48 expiration) external;
159+
160+
/// @notice Approves the spender to be an operator of the specified token up until the expiration
161+
/// @param token The token to approve
162+
/// @param spender The spender address to approve
163+
/// @param expiration The timestamp at which the operator approval is no longer valid
164+
/// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
165+
/// @dev Passing in expiration as 0 DOES NOT set the expiration to the block.timestamp unlike `approve`.
166+
function setApprovalForAll(address token, address spender, uint48 expiration) external;
119167

120168
/// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature
121169
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
@@ -138,7 +186,7 @@ interface IAllowanceTransferERC1155 {
138186
/// @param token The token address to transfer
139187
/// @dev Requires the from address to have approved at least the desired amount
140188
/// of tokens to msg.sender.
141-
function transferFrom(address from, address to, uint160 amount, address token) external;
189+
function transferFrom(address from, address to, uint256 tokenId, uint160 amount, address token) external;
142190

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

149197
/// @notice Enables performing a "lockdown" of the sender's Permit2 identity
150198
/// by batch revoking approvals
151-
/// @param approvals Array of approvals to revoke.
152-
function lockdown(TokenSpenderPair[] calldata approvals) external;
199+
/// @param operatorApprovals Array of approvals to revoke on the operator mapping. Removes operator permissions.
200+
/// @param tokenIdApprovals Array of approvals to revoke on the allowance mapping. Removes spender permissions on certain tokenIds.
201+
function lockdown(TokenSpenderPair[] calldata operatorApprovals, TokenSpenderTokenId[] calldata tokenIdApprovals)
202+
external;
203+
204+
/// @notice Invalidate nonces for a given (token, spender, tokenId) tuple on the allowance mapping.
205+
/// @param token The token to invalidate nonces for
206+
/// @param spender The spender to invalidate nonces for
207+
/// @param tokenId The tokenId to invalidate the nonces for
208+
/// @param newNonce The new nonce to set. Invalidates all nonces less than it.
209+
/// @dev Can't invalidate more than 2**16 nonces per transaction.
210+
function invalidateNonces(address token, address spender, uint256 tokenId, uint48 newNonce) external;
153211

154-
/// @notice Invalidate nonces for a given (token, spender) pair
212+
/// @notice Invalidate nonces for a given (token, spender) pair on the operator mapping.
155213
/// @param token The token to invalidate nonces for
156214
/// @param spender The spender to invalidate nonces for
157215
/// @param newNonce The new nonce to set. Invalidates all nonces less than it.

src/ERC1155/interfaces/ISignatureTransferERC1155.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ interface ISignatureTransferERC1155 {
2222
address token;
2323
// the maximum amount that can be spent
2424
uint256 amount;
25+
// the tokenId that can be spent
26+
uint256 tokenId;
2527
}
2628

2729
/// @notice The signed permit message for a single token transfer

0 commit comments

Comments
 (0)