Skip to content

Commit 70e616d

Browse files
authored
Optimized ERC721 transfers. (#1539)
* Added _transferToken. * _transferFrom is now usable by derived contracts, abstracted away enumerable behavior. * Removed unnecesary check from _clearApprovals
1 parent 70fd243 commit 70e616d

File tree

2 files changed

+87
-29
lines changed

2 files changed

+87
-29
lines changed

contracts/token/ERC721/ERC721.sol

+24-11
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,8 @@ contract ERC721 is ERC165, IERC721 {
130130
*/
131131
function transferFrom(address from, address to, uint256 tokenId) public {
132132
require(_isApprovedOrOwner(msg.sender, tokenId));
133-
require(to != address(0));
134-
135-
_clearApproval(from, tokenId);
136-
_removeTokenFrom(from, tokenId);
137-
_addTokenTo(to, tokenId);
138133

139-
emit Transfer(from, to, tokenId);
134+
_transferFrom(from, to, tokenId);
140135
}
141136

142137
/**
@@ -217,7 +212,7 @@ contract ERC721 is ERC165, IERC721 {
217212
* @param tokenId uint256 ID of the token being burned by the msg.sender
218213
*/
219214
function _burn(address owner, uint256 tokenId) internal {
220-
_clearApproval(owner, tokenId);
215+
_clearApproval(tokenId);
221216
_removeTokenFrom(owner, tokenId);
222217
emit Transfer(owner, address(0), tokenId);
223218
}
@@ -249,6 +244,27 @@ contract ERC721 is ERC165, IERC721 {
249244
_tokenOwner[tokenId] = address(0);
250245
}
251246

247+
/**
248+
* @dev Internal function to transfer ownership of a given token ID to another address.
249+
* As opposed to transferFrom, this imposes no restrictions on msg.sender.
250+
* @param from current owner of the token
251+
* @param to address to receive the ownership of the given token ID
252+
* @param tokenId uint256 ID of the token to be transferred
253+
*/
254+
function _transferFrom(address from, address to, uint256 tokenId) internal {
255+
require(ownerOf(tokenId) == from);
256+
require(to != address(0));
257+
258+
_clearApproval(tokenId);
259+
260+
_ownedTokensCount[from] = _ownedTokensCount[from].sub(1);
261+
_ownedTokensCount[to] = _ownedTokensCount[to].add(1);
262+
263+
_tokenOwner[tokenId] = to;
264+
265+
emit Transfer(from, to, tokenId);
266+
}
267+
252268
/**
253269
* @dev Internal function to invoke `onERC721Received` on a target address
254270
* The call is not executed if the target address is not a contract
@@ -269,12 +285,9 @@ contract ERC721 is ERC165, IERC721 {
269285

270286
/**
271287
* @dev Private function to clear current approval of a given token ID
272-
* Reverts if the given address is not indeed the owner of the token
273-
* @param owner owner of the token
274288
* @param tokenId uint256 ID of the token to be transferred
275289
*/
276-
function _clearApproval(address owner, uint256 tokenId) private {
277-
require(ownerOf(tokenId) == owner);
290+
function _clearApproval(uint256 tokenId) private {
278291
if (_tokenApprovals[tokenId] != address(0)) {
279292
_tokenApprovals[tokenId] = address(0);
280293
}

contracts/token/ERC721/ERC721Enumerable.sol

+63-18
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,8 @@ contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
7676
*/
7777
function _addTokenTo(address to, uint256 tokenId) internal {
7878
super._addTokenTo(to, tokenId);
79-
uint256 length = _ownedTokens[to].length;
80-
_ownedTokens[to].push(tokenId);
81-
_ownedTokensIndex[tokenId] = length;
79+
80+
_addTokenToOwnerEnumeration(to, tokenId);
8281
}
8382

8483
/**
@@ -92,22 +91,26 @@ contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
9291
function _removeTokenFrom(address from, uint256 tokenId) internal {
9392
super._removeTokenFrom(from, tokenId);
9493

95-
// To prevent a gap in the array, we store the last token in the index of the token to delete, and
96-
// then delete the last slot.
97-
uint256 tokenIndex = _ownedTokensIndex[tokenId];
98-
uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
99-
uint256 lastToken = _ownedTokens[from][lastTokenIndex];
100-
101-
_ownedTokens[from][tokenIndex] = lastToken;
102-
// This also deletes the contents at the last position of the array
103-
_ownedTokens[from].length--;
104-
105-
// Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to
106-
// be zero. Then we can make sure that we will remove tokenId from the ownedTokens list since we are first swapping
107-
// the lastToken to the first position, and then dropping the element placed in the last position of the list
94+
_removeTokenFromOwnerEnumeration(from, tokenId);
10895

96+
// Since the token is being destroyed, we also clear its index
97+
// TODO(nventuro): 0 is still a valid index, so arguably this isnt really helpful, remove?
10998
_ownedTokensIndex[tokenId] = 0;
110-
_ownedTokensIndex[lastToken] = tokenIndex;
99+
}
100+
101+
/**
102+
* @dev Internal function to transfer ownership of a given token ID to another address.
103+
* As opposed to transferFrom, this imposes no restrictions on msg.sender.
104+
* @param from current owner of the token
105+
* @param to address to receive the ownership of the given token ID
106+
* @param tokenId uint256 ID of the token to be transferred
107+
*/
108+
function _transferFrom(address from, address to, uint256 tokenId) internal {
109+
super._transferFrom(from, to, tokenId);
110+
111+
_removeTokenFromOwnerEnumeration(from, tokenId);
112+
113+
_addTokenToOwnerEnumeration(to, tokenId);
111114
}
112115

113116
/**
@@ -144,7 +147,7 @@ contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
144147
_allTokensIndex[tokenId] = 0;
145148
_allTokensIndex[lastToken] = tokenIndex;
146149
}
147-
150+
148151
/**
149152
* @dev Gets the list of token IDs of the requested owner
150153
* @param owner address owning the tokens
@@ -153,4 +156,46 @@ contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
153156
function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
154157
return _ownedTokens[owner];
155158
}
159+
160+
/**
161+
* @dev Private function to add a token to this extension's ownership-tracking data structures.
162+
* @param to address representing the new owner of the given token ID
163+
* @param tokenId uint256 ID of the token to be added to the tokens list of the given address
164+
*/
165+
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
166+
uint256 newOwnedTokensLength = _ownedTokens[to].push(tokenId);
167+
// No need to use SafeMath since the length after a push cannot be zero
168+
_ownedTokensIndex[tokenId] = newOwnedTokensLength - 1;
169+
}
170+
171+
/**
172+
* @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
173+
* while the token is not assigned a new owner, the _ownedTokensIndex mapping is _not_ updated: this allows for
174+
* gas optimizations e.g. when performing a transfer operation (avoiding double writes).
175+
* This has O(1) time complexity, but alters the order of the _ownedTokens array.
176+
* @param from address representing the previous owner of the given token ID
177+
* @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
178+
*/
179+
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
180+
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
181+
// then delete the last slot (swap and pop).
182+
183+
uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
184+
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
185+
186+
uint256 tokenIndex = _ownedTokensIndex[tokenId];
187+
188+
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
189+
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
190+
191+
// Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going
192+
// to be zero. The swap operation will therefore have no effect, but the token _will_ be deleted during the
193+
// 'pop' operation.
194+
195+
// This also deletes the contents at the last position of the array
196+
_ownedTokens[from].length--;
197+
198+
// Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occcupied by
199+
// lasTokenId).
200+
}
156201
}

0 commit comments

Comments
 (0)