From 14704e02564f53d565ca3417b0367bf7bd5ac77e Mon Sep 17 00:00:00 2001 From: Vectorized Date: Sat, 10 Feb 2024 15:39:08 +0000 Subject: [PATCH] T --- .gas-snapshot | 15 ++++---- src/DN404.sol | 45 +++++++++++++++++++--- src/DN404NonFungibleShadow.sol | 69 +++++++++++++++++++++++++++++++--- test/DN404.t.sol | 28 ++++++++++++-- test/utils/mocks/MockDN404.sol | 3 +- 5 files changed, 137 insertions(+), 23 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 5bc40ea..1e54ee4 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,9 +1,10 @@ -DN404Test:testBurnOnTransfer(uint32,address,address) (runs: 256, μ: 257735, ~: 259368) -DN404Test:testInitialize(uint32,address) (runs: 256, μ: 95178, ~: 108931) -DN404Test:testMintOnTransfer(uint32,address,address) (runs: 256, μ: 213209, ~: 214764) -DN404Test:testNameAndSymbol(string,string) (runs: 256, μ: 206354, ~: 206695) -DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 120599, ~: 120599) -DN404Test:testTokenURI(string,uint256) (runs: 256, μ: 162148, ~: 146462) -DN404Test:test__codesize() (gas: 17853) +DN404Test:testBurnOnTransfer(uint32,address,address) (runs: 256, μ: 277323, ~: 279111) +DN404Test:testInitialize(uint32,address) (runs: 256, μ: 97313, ~: 112344) +DN404Test:testMintOnTransfer(uint32,address,address) (runs: 256, μ: 254835, ~: 256235) +DN404Test:testNameAndSymbol(string,string) (runs: 256, μ: 206378, ~: 206719) +DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 120543, ~: 120621) +DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 256, μ: 129145, ~: 120210) +DN404Test:testTokenURI(string,uint256) (runs: 256, μ: 157275, ~: 134976) +DN404Test:test__codesize() (gas: 20274) SoladyTest:test__codesize() (gas: 1075) TestPlus:test__codesize() (gas: 398) \ No newline at end of file diff --git a/src/DN404.sol b/src/DN404.sol index 0cb9ee8..26354a1 100644 --- a/src/DN404.sol +++ b/src/DN404.sol @@ -40,6 +40,14 @@ abstract contract DN404 { error TransferToNonERC721ReceiverImplementer(); + error TokenDoesNotExist(); + + error CannotLink(); + + error AlreadyLinked(); + + error NotLinked(); + uint256 private constant _WAD = 1000000000000000000; uint256 private constant _MAX_TOKEN_ID = 0xffffffff; @@ -121,11 +129,6 @@ abstract contract DN404 { return _getDN404Storage().addressData[owner].balance; } - function ownerOf(uint256 id) public view virtual returns (address owner) { - DN404Storage storage $ = _getDN404Storage(); - owner = $.aliasToAddress[$.ownerships.get(id)]; - } - function approve(address spender, uint256 amount) public virtual returns (bool) { DN404Storage storage $ = _getDN404Storage(); @@ -360,6 +363,27 @@ abstract contract DN404 { uint256 fnSelector = _calldataload(0x00) >> 224; + // `isApprovedForAll(address,address)`. + if (fnSelector == 0xe985e9c5) { + if (msg.sender != $.sisterERC721) revert Unauthorized(); + if (msg.data.length < 0x44) revert(); + + address owner = address(uint160(_calldataload(0x04))); + address operator = address(uint160(_calldataload(0x24))); + + _return($.operatorApprovals[owner][operator] ? 1 : 0); + } + // `ownerOf(uint256)`. + if (fnSelector == 0x6352211e) { + if (msg.sender != $.sisterERC721) revert Unauthorized(); + if (msg.data.length < 0x24) revert(); + + uint256 id = _calldataload(0x04); + address owner = $.aliasToAddress[$.ownerships.get(id)]; + if (owner == address(0)) revert TokenDoesNotExist(); + + _return(uint160(owner)); + } // `_transferFromNFT(address,address,uint256,address)`. if (fnSelector == 0xe5eb36c8) { if (msg.sender != $.sisterERC721) revert Unauthorized(); @@ -396,6 +420,17 @@ abstract contract DN404 { _return(uint160(_approveNFT(spender, id, msgSender))); } + // `getApproved(uint256)`. + if (fnSelector == 0x081812fc) { + if (msg.sender != $.sisterERC721) revert Unauthorized(); + if (msg.data.length < 0x24) revert(); + + uint256 id = _calldataload(0x04); + address owner = $.aliasToAddress[$.ownerships.get(id)]; + if (owner == address(0)) revert TokenDoesNotExist(); + + _return(uint160($.tokenApprovals[id])); + } // `implementsDN404()`. if (fnSelector == 0xb7a94eb8) { _return(1); diff --git a/src/DN404NonFungibleShadow.sol b/src/DN404NonFungibleShadow.sol index 5b8ee6c..47fc0a3 100644 --- a/src/DN404NonFungibleShadow.sol +++ b/src/DN404NonFungibleShadow.sol @@ -16,8 +16,26 @@ contract DN404NonFungibleShadow { /* CUSTOM ERRORS */ /*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/ + error AlreadyInitialized(); + + error InvalidTotalNFTSupply(); + + error FailToLinkToSister(); + error Unauthorized(); + error TransferToZeroAddress(); + + error SisterAddressIsZero(); + + error ApprovalCallerNotOwnerNorApproved(); + + error TransferCallerNotOwnerNorApproved(); + + error TransferFromIncorrectOwner(); + + error TransferToNonERC721ReceiverImplementer(); + error TokenDoesNotExist(); error CannotLink(); @@ -26,8 +44,6 @@ contract DN404NonFungibleShadow { error NotLinked(); - error TransferToNonERC721ReceiverImplementer(); - uint256 private constant _WAD = 1000000000000000000; struct DN404NFTStorage { @@ -97,7 +113,7 @@ contract DN404NonFungibleShadow { address sister = sisterERC20(); /// @solidity memory-safe-assembly assembly { - mstore(0x00, 0x7824407f) // `tokenSupply()`. + mstore(0x00, 0x18160ddd) // `totalSupply()`. if iszero( and(gt(returndatasize(), 0x1f), staticcall(gas(), sister, 0x1c, 0x04, 0x00, 0x20)) ) { @@ -124,7 +140,7 @@ contract DN404NonFungibleShadow { } } - function ownerOf(uint256 id) public view virtual returns (address owner) { + function ownerOf(uint256 id) public view virtual returns (address result) { address sister = sisterERC20(); /// @solidity memory-safe-assembly assembly { @@ -136,9 +152,8 @@ contract DN404NonFungibleShadow { returndatacopy(mload(0x40), 0x00, returndatasize()) revert(mload(0x40), returndatasize()) } - owner := shr(96, shl(96, mload(0x00))) + result := shr(96, shl(96, mload(0x00))) } - if (owner == address(0)) revert TokenDoesNotExist(); } function approve(address spender, uint256 id) public virtual { @@ -165,6 +180,22 @@ contract DN404NonFungibleShadow { emit Approval(owner, spender, id); } + function getApproved(uint256 id) public view virtual returns (address result) { + address sister = sisterERC20(); + /// @solidity memory-safe-assembly + assembly { + mstore(0x00, 0x081812fc) // `getApproved(uint256)`. + mstore(0x20, id) + if iszero( + and(gt(returndatasize(), 0x1f), staticcall(gas(), sister, 0x1c, 0x24, 0x00, 0x20)) + ) { + returndatacopy(mload(0x40), 0x00, returndatasize()) + revert(mload(0x40), returndatasize()) + } + result := shr(96, shl(96, mload(0x00))) + } + } + function setApprovalForAll(address operator, bool approved) public virtual { address sister = sisterERC20(); /// @solidity memory-safe-assembly @@ -187,6 +218,32 @@ contract DN404NonFungibleShadow { emit ApprovalForAll(msg.sender, operator, approved); } + function isApprovedForAll(address owner, address operator) + public + view + virtual + returns (bool result) + { + address sister = sisterERC20(); + /// @solidity memory-safe-assembly + assembly { + let m := mload(0x40) + mstore(m, 0xe985e9c5) // `isApprovedForAll(address,address)`. + mstore(add(m, 0x20), shr(96, shl(96, owner))) + mstore(add(m, 0x40), shr(96, shl(96, operator))) + if iszero( + and( + gt(returndatasize(), 0x1f), + staticcall(gas(), sister, add(m, 0x1c), 0x44, 0x00, 0x20) + ) + ) { + returndatacopy(m, 0x00, returndatasize()) + revert(m, returndatasize()) + } + result := iszero(iszero(mload(0x00))) + } + } + function transferFrom(address from, address to, uint256 id) public virtual { address sister = sisterERC20(); /// @solidity memory-safe-assembly diff --git a/test/DN404.t.sol b/test/DN404.t.sol index a2c241f..0b2a300 100644 --- a/test/DN404.t.sol +++ b/test/DN404.t.sol @@ -4,9 +4,10 @@ pragma solidity ^0.8.24; import "./utils/SoladyTest.sol"; import {DN404, MockDN404} from "./utils/mocks/MockDN404.sol"; import {DN404NonFungibleShadow} from "../src/DN404NonFungibleShadow.sol"; -import "solady/utils/LibString.sol"; contract DN404Test is SoladyTest { + uint256 private constant _WAD = 1000000000000000000; + MockDN404 dn; DN404NonFungibleShadow shadow; @@ -25,7 +26,7 @@ contract DN404Test is SoladyTest { function testTokenURI(string memory baseURI, uint256 id) public { dn.initializeDN404(1000, address(this), address(shadow)); dn.setBaseURI(baseURI); - assertEq(shadow.tokenURI(id), string(abi.encodePacked(baseURI, LibString.toString(id)))); + assertEq(shadow.tokenURI(id), string(abi.encodePacked(baseURI, id))); } function testRegisterAndResolveAlias(address a0, address a1) public { @@ -49,9 +50,21 @@ contract DN404Test is SoladyTest { dn.initializeDN404(totalNFTSupply, initialSupplyOwner, address(shadow)); assertEq(dn.totalSupply(), uint256(totalNFTSupply) * 10 ** 18); assertEq(dn.balanceOf(initialSupplyOwner), uint256(totalNFTSupply) * 10 ** 18); + assertEq(shadow.totalSupply(), totalNFTSupply); + assertEq(shadow.balanceOf(initialSupplyOwner), totalNFTSupply); } } + function testSetAndGetOperatorApprovals(address owner, address operator, bool approved) + public + { + dn.initializeDN404(1000, address(this), address(shadow)); + assertEq(shadow.isApprovedForAll(owner, operator), false); + vm.prank(owner); + shadow.setApprovalForAll(operator, approved); + assertEq(shadow.isApprovedForAll(owner, operator), approved); + } + function testMintOnTransfer( uint32 totalNFTSupply, address initialSupplyOwner, @@ -65,10 +78,19 @@ contract DN404Test is SoladyTest { dn.initializeDN404(totalNFTSupply, initialSupplyOwner, address(shadow)); + vm.expectRevert(DN404NonFungibleShadow.TokenDoesNotExist.selector); + shadow.getApproved(1); + vm.prank(initialSupplyOwner); - dn.transfer(recipient, 1e18); + dn.transfer(recipient, _WAD); + assertEq(shadow.balanceOf(recipient), 1); assertEq(shadow.ownerOf(1), recipient); + + assertEq(shadow.getApproved(1), address(0)); + vm.prank(recipient); + shadow.approve(address(this), 1); + assertEq(shadow.getApproved(1), address(this)); } function testBurnOnTransfer( diff --git a/test/utils/mocks/MockDN404.sol b/test/utils/mocks/MockDN404.sol index a1c7c5f..41580ff 100644 --- a/test/utils/mocks/MockDN404.sol +++ b/test/utils/mocks/MockDN404.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.4; import "../../../src/DN404.sol"; -import "solady/utils/LibString.sol"; contract MockDN404 is DN404 { string private _name; @@ -29,7 +28,7 @@ contract MockDN404 is DN404 { } function tokenURI(uint256 id) public view virtual override returns (string memory) { - return string(abi.encodePacked(_baseURI, LibString.toString(id))); + return string(abi.encodePacked(_baseURI, id)); } function setWhitelist(address target, bool status) public {