2
2
pragma solidity 0.8.17 ;
3
3
4
4
import {PermitHashERC1155} from "./libraries/PermitHashERC1155.sol " ;
5
+ import {ERC1155 } from "solmate/src/tokens/ERC1155.sol " ;
5
6
import {SignatureVerification} from "../shared/SignatureVerification.sol " ;
6
7
import {EIP712ForERC1155} from "./EIP712ForERC1155.sol " ;
7
8
import {IAllowanceTransferERC1155} from "./interfaces/IAllowanceTransferERC1155.sol " ;
@@ -15,15 +16,26 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
15
16
using AllowanceERC1155 for PackedAllowance;
16
17
17
18
/// @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;
21
27
22
28
/// @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] ;
25
31
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);
27
39
}
28
40
29
41
/// @inheritdoc IAllowanceTransferERC1155
@@ -53,8 +65,8 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
53
65
}
54
66
55
67
/// @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);
58
70
}
59
71
60
72
/// @inheritdoc IAllowanceTransferERC1155
@@ -63,22 +75,37 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
63
75
uint256 length = transferDetails.length ;
64
76
for (uint256 i = 0 ; i < length; ++ i) {
65
77
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
+ );
67
85
}
68
86
}
69
87
}
70
88
71
89
/// @notice Internal function for transferring tokens using stored allowances
72
90
/// @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;
75
96
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
+ }
77
101
78
102
uint256 maxAmount = allowed.amount;
79
103
if (maxAmount != type (uint160 ).max) {
80
104
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);
82
109
} else {
83
110
unchecked {
84
111
allowed.amount = uint160 (maxAmount) - amount;
@@ -87,28 +114,58 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
87
114
}
88
115
89
116
// 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, "" );
91
118
}
92
119
93
120
/// @inheritdoc IAllowanceTransferERC1155
94
- function lockdown (TokenSpenderPair[] calldata approvals ) external {
121
+ function lockdown (TokenSpenderPair[] calldata operatorApprovals , TokenSpenderTokenId[] calldata tokenIdApprovals )
122
+ external
123
+ {
95
124
address owner = msg .sender ;
96
- // Revoke allowances for each pair of spenders and tokens.
125
+
97
126
unchecked {
98
- uint256 length = approvals.length ;
127
+ // Revoke operator allowances for each pair of spenders and tokens.
128
+ uint256 length = operatorApprovals.length ;
99
129
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;
102
132
103
- allowance [owner][token][spender].amount = 0 ;
133
+ operators [owner][token][spender].expiration = 0 ;
104
134
emit Lockdown (owner, token, spender);
105
135
}
106
136
}
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);
107
164
}
108
165
109
166
/// @inheritdoc IAllowanceTransferERC1155
110
167
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;
112
169
113
170
if (newNonce <= oldNonce) revert InvalidNonce ();
114
171
@@ -118,7 +175,7 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
118
175
if (delta > type (uint16 ).max) revert ExcessiveInvalidation ();
119
176
}
120
177
121
- allowance [msg .sender ][token][spender].nonce = newNonce;
178
+ operators [msg .sender ][token][spender].nonce = newNonce;
122
179
emit NonceInvalidation (msg .sender , token, spender, newNonce, oldNonce);
123
180
}
124
181
@@ -129,8 +186,10 @@ contract AllowanceTransferERC1155 is IAllowanceTransferERC1155, EIP712ForERC1155
129
186
uint48 nonce = details.nonce;
130
187
address token = details.token;
131
188
uint160 amount = details.amount;
189
+ uint256 tokenId = details.tokenId;
132
190
uint48 expiration = details.expiration;
133
- PackedAllowance storage allowed = allowance[owner][token][spender];
191
+
192
+ PackedAllowance storage allowed = allowance[owner][token][spender][tokenId];
134
193
135
194
if (allowed.nonce != nonce) revert InvalidNonce ();
136
195
0 commit comments