Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚡️ Optimize bytecode size of mirror #150

Merged
merged 2 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Optimize bytecode size of mirror
  • Loading branch information
Vectorized committed Jan 16, 2025
commit 6118a5e76a070dce09a2ac8a6d22175a25c098e7
87 changes: 34 additions & 53 deletions src/DN404Mirror.sol
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,12 @@ contract DN404Mirror {

/// @dev Equivalent to `safeTransferFrom(from, to, id, "")`.
function safeTransferFrom(address from, address to, uint256 id) public payable virtual {
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, "");
bytes calldata emptyData;
/// @solidity memory-safe-assembly
assembly {
emptyData.length := 0
}
safeTransferFrom(from, to, id, emptyData);
}

/// @dev Transfers token `id` from `from` to `to`.
Expand All @@ -302,7 +306,34 @@ contract DN404Mirror {
virtual
{
transferFrom(from, to, id);
if (_hasCode(to)) _checkOnERC721Received(from, to, id, data);
/// @solidity memory-safe-assembly
assembly {
if extcodesize(to) {
// Prepare the calldata.
let m := mload(0x40)
let onERC721ReceivedSelector := 0x150b7a02
mstore(m, onERC721ReceivedSelector)
mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`.
mstore(add(m, 0x40), shr(96, shl(96, from)))
mstore(add(m, 0x60), id)
mstore(add(m, 0x80), 0x80)
mstore(add(m, 0xa0), data.length)
calldatacopy(add(m, 0xc0), data.offset, data.length)
// Revert if the call reverts.
if iszero(call(gas(), to, 0, add(m, 0x1c), add(data.length, 0xa4), m, 0x20)) {
if returndatasize() {
// Bubble up the revert if the call reverts.
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
}
// Load the returndata and compare it.
if iszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) {
mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`.
revert(0x1c, 0x04)
}
}
}
}

/// @dev Returns true if this contract implements the interface defined by `interfaceId`.
Expand Down Expand Up @@ -498,14 +529,6 @@ contract DN404Mirror {
}
}

/// @dev Returns if `a` has bytecode of non-zero length.
function _hasCode(address a) private view returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
result := extcodesize(a) // Can handle dirty upper bits.
}
}

/// @dev More bytecode-efficient way to revert.
function _rv(uint32 s) private pure {
/// @solidity memory-safe-assembly
Expand All @@ -514,46 +537,4 @@ contract DN404Mirror {
revert(0x1c, 0x04)
}
}

/// @dev Perform a call to invoke {IERC721Receiver-onERC721Received} on `to`.
/// Reverts if the target does not support the function correctly.
function _checkOnERC721Received(address from, address to, uint256 id, bytes memory data)
private
{
/// @solidity memory-safe-assembly
assembly {
// Prepare the calldata.
let m := mload(0x40)
let onERC721ReceivedSelector := 0x150b7a02
mstore(m, onERC721ReceivedSelector)
mstore(add(m, 0x20), caller()) // The `operator`, which is always `msg.sender`.
mstore(add(m, 0x40), shr(96, shl(96, from)))
mstore(add(m, 0x60), id)
mstore(add(m, 0x80), 0x80)
let n := mload(data)
mstore(add(m, 0xa0), n)
if n {
let dst := add(m, 0xc0)
let end := add(dst, n)
for { let d := sub(add(data, 0x20), dst) } 1 {} {
mstore(dst, mload(add(dst, d)))
dst := add(dst, 0x20)
if iszero(lt(dst, end)) { break }
}
}
// Revert if the call reverts.
if iszero(call(gas(), to, 0, add(m, 0x1c), add(n, 0xa4), m, 0x20)) {
if returndatasize() {
// Bubble up the revert if the call reverts.
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
}
// Load the returndata and compare it.
if iszero(eq(mload(m), shl(224, onERC721ReceivedSelector))) {
mstore(0x00, 0xd1a57ed6) // `TransferToNonERC721ReceiverImplementer()`.
revert(0x1c, 0x04)
}
}
}
}
51 changes: 41 additions & 10 deletions test/DN404Mirror.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,19 @@ import {MockDN404Ownable} from "./utils/mocks/MockDN404Ownable.sol";
import {DN404Mirror, MockDN404Mirror} from "./utils/mocks/MockDN404Mirror.sol";
import {LibSort} from "solady/utils/LibSort.sol";

contract Invalid721Receiver {}
contract InvalidERC721Receiver {}

contract ERC721Receiver {
bytes32 public lastReceivedHash;

function onERC721Received(address by, address from, uint256 id, bytes calldata data)
public
returns (bytes4)
{
lastReceivedHash = keccak256(abi.encode(by, from, id, keccak256(data)));
return msg.sig;
}
}

contract DN404MirrorTest is SoladyTest {
event Transfer(address indexed from, address indexed to, uint256 indexed id);
Expand Down Expand Up @@ -206,25 +218,44 @@ contract DN404MirrorTest is SoladyTest {
}
}

function testSafeTransferFrom(uint32 totalNFTSupply) public {
function testSafeTransferFrom(uint32 totalNFTSupply, bytes memory randomBytes) public {
totalNFTSupply = uint32(_bound(totalNFTSupply, 5, 1000000));
address alice = address(111);
address bob = address(222);
address invalid = address(new Invalid721Receiver());

dn.initializeDN404(uint96(uint256(totalNFTSupply) * _WAD), address(this), address(mirror));
dn.transfer(alice, _WAD * uint256(5));
assertEq(mirror.balanceOf(alice), 5);
assertEq(mirror.balanceOf(bob), 0);

vm.prank(alice);
vm.expectRevert(DN404Mirror.TransferToNonERC721ReceiverImplementer.selector);
mirror.safeTransferFrom(alice, invalid, 1);
if (_random() % 2 == 0) {
address to = address(new InvalidERC721Receiver());

vm.prank(alice);
vm.expectRevert(DN404Mirror.TransferToNonERC721ReceiverImplementer.selector);
mirror.safeTransferFrom(alice, to, 1);

vm.prank(alice);
mirror.safeTransferFrom(alice, bob, 1);
assertEq(mirror.balanceOf(alice), 4);
assertEq(mirror.balanceOf(bob), 1);
} else {
address to = address(new ERC721Receiver());
address operator = _randomNonZeroAddress();
vm.prank(alice);
mirror.setApprovalForAll(operator, true);

if (randomBytes.length == 0 && _random() % 2 == 0) {
vm.prank(operator);
mirror.safeTransferFrom(alice, to, 1);
} else {
vm.prank(operator);
mirror.safeTransferFrom(alice, to, 1, randomBytes);
}

vm.prank(alice);
mirror.safeTransferFrom(alice, bob, 1);
assertEq(mirror.balanceOf(alice), 4);
assertEq(mirror.balanceOf(bob), 1);
bytes32 h = keccak256(abi.encode(operator, alice, 1, keccak256(randomBytes)));
assertEq(ERC721Receiver(to).lastReceivedHash(), h);
}
}

function testLinkMirrorContract() public {
Expand Down
Loading