Skip to content

Commit

Permalink
Use assembly to emit transfer events (#23)
Browse files Browse the repository at this point in the history
Co-authored-by: Nidhhoggr <persie.joseph@gmail.com>
  • Loading branch information
nidhhoggr and Nidhhoggr authored Dec 25, 2022
1 parent 4230ef7 commit 91bf2ec
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 10 deletions.
46 changes: 41 additions & 5 deletions contracts/ERC721Psi.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ contract ERC721Psi is Context, ERC165, IERC721, IERC721Metadata {
mapping(uint256 => address) private _tokenApprovals;
mapping(address => mapping(address => bool)) private _operatorApprovals;

// The mask of the lower 160 bits for addresses.
uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1;
// The `Transfer` event signature is given by:
// `keccak256(bytes("Transfer(address,address,uint256)"))`.
bytes32 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
Expand Down Expand Up @@ -379,12 +386,41 @@ contract ERC721Psi is Context, ERC165, IERC721, IERC721Metadata {
_currentIndex += quantity;
_owners[nextTokenId] = to;
_batchHead.set(nextTokenId);

uint256 toMasked;
uint256 end = nextTokenId + quantity;

// Use assembly to loop and emit the `Transfer` event for gas savings.
// The duplicated `log4` removes an extra check and reduces stack juggling.
// The assembly, together with the surrounding Solidity code, have been
// delicately arranged to nudge the compiler into producing optimized opcodes.
assembly {
// Mask `to` to the lower 160 bits, in case the upper bits somehow aren't clean.
toMasked := and(to, _BITMASK_ADDRESS)
// Emit the `Transfer` event.
log4(
0, // Start of data (0, since no data).
0, // End of data (0, since no data).
_TRANSFER_EVENT_SIGNATURE, // Signature.
0, // `address(0)`.
toMasked, // `to`.
nextTokenId // `tokenId`.
)

// The `iszero(eq(,))` check ensures that large values of `quantity`
// that overflows uint256 will make the loop run out of gas.
// The compiler will optimize the `iszero` away for performance.
for {
let tokenId := add(nextTokenId, 1)
} iszero(eq(tokenId, end)) {
tokenId := add(tokenId, 1)
} {
// Emit the `Transfer` event. Similar to above.
log4(0, 0, _TRANSFER_EVENT_SIGNATURE, 0, toMasked, tokenId)
}
}

_afterTokenTransfers(address(0), to, nextTokenId, quantity);

// Emit events
for(uint256 tokenId=nextTokenId; tokenId < nextTokenId + quantity; tokenId++){
emit Transfer(address(0), to, tokenId);
}
}


Expand Down
46 changes: 41 additions & 5 deletions contracts/ERC721PsiUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ contract ERC721PsiUpgradeable is Initializable, ContextUpgradeable,
mapping(uint256 => address) private _tokenApprovals;
mapping(address => mapping(address => bool)) private _operatorApprovals;

// The mask of the lower 160 bits for addresses.
uint256 private constant _BITMASK_ADDRESS = (1 << 160) - 1;
// The `Transfer` event signature is given by:
// `keccak256(bytes("Transfer(address,address,uint256)"))`.
bytes32 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;

/**
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
*/
Expand Down Expand Up @@ -387,12 +394,41 @@ contract ERC721PsiUpgradeable is Initializable, ContextUpgradeable,
_currentIndex += quantity;
_owners[nextTokenId] = to;
_batchHead.set(nextTokenId);
_afterTokenTransfers(address(0), to, nextTokenId, quantity);

uint256 toMasked;
uint256 end = nextTokenId + quantity;

// Use assembly to loop and emit the `Transfer` event for gas savings.
// The duplicated `log4` removes an extra check and reduces stack juggling.
// The assembly, together with the surrounding Solidity code, have been
// delicately arranged to nudge the compiler into producing optimized opcodes.
assembly {
// Mask `to` to the lower 160 bits, in case the upper bits somehow aren't clean.
toMasked := and(to, _BITMASK_ADDRESS)
// Emit the `Transfer` event.
log4(
0, // Start of data (0, since no data).
0, // End of data (0, since no data).
_TRANSFER_EVENT_SIGNATURE, // Signature.
0, // `address(0)`.
toMasked, // `to`.
nextTokenId // `tokenId`.
)

// The `iszero(eq(,))` check ensures that large values of `quantity`
// that overflows uint256 will make the loop run out of gas.
// The compiler will optimize the `iszero` away for performance.
for {
let tokenId := add(nextTokenId, 1)
} iszero(eq(tokenId, end)) {
tokenId := add(tokenId, 1)
} {
// Emit the `Transfer` event. Similar to above.
log4(0, 0, _TRANSFER_EVENT_SIGNATURE, 0, toMasked, tokenId)
}
}

// Emit events
for(uint256 tokenId=nextTokenId; tokenId < nextTokenId + quantity; tokenId++){
emit Transfer(address(0), to, tokenId);
}
_afterTokenTransfers(address(0), to, nextTokenId, quantity);
}


Expand Down

0 comments on commit 91bf2ec

Please sign in to comment.