Skip to content

Commit

Permalink
⚡️ Optimizations (Vectorized#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized authored Feb 11, 2024
1 parent d6ad487 commit 78640d8
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 69 deletions.
35 changes: 18 additions & 17 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
DN404MirrorTest:testLinkMirrorContract() (gas: 46434)
DN404MirrorTest:testLogTransfer() (gas: 121582)
DN404MirrorTest:testNameAndSymbol(string,string) (runs: 256, μ: 208013, ~: 208354)
DN404MirrorTest:testLogTransfer() (gas: 121621)
DN404MirrorTest:testNameAndSymbol(string,string) (runs: 256, μ: 208105, ~: 208446)
DN404MirrorTest:testNotLinked() (gas: 12471)
DN404MirrorTest:testRootERC20() (gas: 115612)
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 256, μ: 469589, ~: 469577)
DN404MirrorTest:testRootERC20() (gas: 115681)
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 256, μ: 469631, ~: 469622)
DN404MirrorTest:testSupportsInterface() (gas: 7534)
DN404MirrorTest:testTokenURI(string,uint256) (runs: 256, μ: 158820, ~: 136518)
DN404MirrorTest:testTransferFrom(uint32) (runs: 256, μ: 342480, ~: 342475)
DN404MirrorTest:test__codesize() (gas: 22784)
DN404Test:testBatchNFTLog() (gas: 363546)
DN404Test:testBurnOnTransfer(uint32,address) (runs: 256, μ: 288522, ~: 288532)
DN404Test:testInitialize(uint32,address) (runs: 256, μ: 113518, ~: 116913)
DN404Test:testMintOnTransfer(uint32,address) (runs: 256, μ: 264294, ~: 264314)
DN404Test:testNameAndSymbol(string,string) (runs: 256, μ: 208137, ~: 208478)
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 126880, ~: 126909)
DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 256, μ: 130786, ~: 121851)
DN404Test:testTokenURI(string,uint256) (runs: 256, μ: 158922, ~: 136620)
DN404Test:testWrapAround(uint32,uint256) (runs: 256, μ: 439627, ~: 431304)
DN404Test:test__codesize() (gas: 24290)
DN404MirrorTest:testTokenURI(string,uint256) (runs: 256, μ: 158889, ~: 136587)
DN404MirrorTest:testTransferFrom(uint32) (runs: 256, μ: 342483, ~: 342478)
DN404MirrorTest:test__codesize() (gas: 24672)
DN404Test:testBatchNFTLog() (gas: 363330)
DN404Test:testBurnOnTransfer(uint32,address) (runs: 256, μ: 288399, ~: 288399)
DN404Test:testInitialize(uint32,address) (runs: 256, μ: 113844, ~: 117018)
DN404Test:testMintAndBurn() (gas: 303440)
DN404Test:testMintOnTransfer(uint32,address) (runs: 256, μ: 264298, ~: 264318)
DN404Test:testNameAndSymbol(string,string) (runs: 256, μ: 208229, ~: 208570)
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 126773, ~: 126978)
DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 256, μ: 130800, ~: 121865)
DN404Test:testTokenURI(string,uint256) (runs: 256, μ: 158991, ~: 136689)
DN404Test:testWrapAround(uint32,uint256) (runs: 256, μ: 437425, ~: 430543)
DN404Test:test__codesize() (gas: 27644)
SoladyTest:test__codesize() (gas: 1075)
TestPlus:test__codesize() (gas: 398)
65 changes: 35 additions & 30 deletions src/DN404.sol
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,6 @@ abstract contract DN404 {
/* SHARED TRANSFER OPERATIONS */
/*-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»-»*/

struct _TransferTemps {
uint256 nftAmountToBurn; // 0x20.
uint256 nftAmountToMint; // 0x40.
uint256 toBalance;
uint256 fromBalance;
}

struct _PackedLogs {
uint256[] logs;
uint256 offset;
Expand All @@ -192,7 +185,7 @@ abstract contract DN404 {
function _packedLogsMalloc(uint256 n) private pure returns (_PackedLogs memory p) {
/// @solidity memory-safe-assembly
assembly {
let logs := add(mload(0x40), 0x40)
let logs := add(mload(0x40), 0x40) // Offset by 2 words for `_packedLogsSend`.
mstore(logs, n)
let offset := add(0x20, logs)
mstore(0x40, add(offset, shl(5, n)))
Expand Down Expand Up @@ -230,6 +223,15 @@ abstract contract DN404 {
}
}

struct _TransferTemps {
uint256 nftAmountToBurn;
uint256 nftAmountToMint;
uint256 fromBalance;
uint256 toBalance;
uint256 fromOwnedLength;
uint256 toOwnedLength;
}

function _transfer(address from, address to, uint256 amount) internal {
if (to == address(0)) revert TransferToZeroAddress();

Expand All @@ -239,24 +241,27 @@ abstract contract DN404 {
AddressData storage toAddressData = _addressData(to);

_TransferTemps memory t;
uint256 numBurned = $.numBurned;
t.fromOwnedLength = fromAddressData.ownedLength;
t.toOwnedLength = toAddressData.ownedLength;

fromAddressData.balance = uint96(t.fromBalance = fromAddressData.balance - amount);

uint256 numBurned = $.numBurned;

unchecked {
toAddressData.balance = uint96(t.toBalance = toAddressData.balance + amount);

t.nftAmountToBurn = _zeroFloorSub(fromAddressData.ownedLength, t.fromBalance / _WAD);
t.nftAmountToBurn = _zeroFloorSub(t.fromOwnedLength, t.fromBalance / _WAD);

if (!toAddressData.skipNFT) {
t.nftAmountToMint = _zeroFloorSub(t.toBalance / _WAD, toAddressData.ownedLength);
t.nftAmountToMint = _zeroFloorSub(t.toBalance / _WAD, t.toOwnedLength);
}

_PackedLogs memory packedLogs = _packedLogsMalloc(t.nftAmountToBurn + t.nftAmountToMint);

if (t.nftAmountToBurn != 0) {
LibMap.Uint32Map storage fromOwned = $.owned[from];
uint256 fromIndex = fromAddressData.ownedLength;
uint256 fromIndex = t.fromOwnedLength;
uint256 fromEnd = fromIndex - t.nftAmountToBurn;
$.totalNFTSupply -= uint32(t.nftAmountToBurn);
// Burn loop.
Expand All @@ -273,7 +278,7 @@ abstract contract DN404 {

if (t.nftAmountToMint != 0) {
LibMap.Uint32Map storage toOwned = $.owned[to];
uint256 toIndex = toAddressData.ownedLength;
uint256 toIndex = t.toOwnedLength;
uint256 toEnd = toIndex + t.nftAmountToMint;
uint256 id = $.nextTokenId;
uint32 toAlias = _registerAndResolveAlias(toAddressData, to);
Expand Down Expand Up @@ -319,32 +324,27 @@ abstract contract DN404 {

AddressData storage toAddressData = _addressData(to);

{
uint256 currentTokenSupply = $.totalTokenSupply;
currentTokenSupply += amount;
unchecked {
uint256 currentTokenSupply = uint256($.totalTokenSupply) + amount;
if (currentTokenSupply / _WAD > _MAX_TOKEN_ID - 1) revert InvalidTotalNFTSupply();
$.totalTokenSupply = uint96(currentTokenSupply);
}

unchecked {
uint256 toBalance = toAddressData.balance + amount;
toAddressData.balance = uint96(toBalance);

if (!toAddressData.skipNFT) {
LibMap.Uint32Map storage toOwned = $.owned[to];
uint256 toIndex = toAddressData.ownedLength;
uint256 toMaxNFTs = toBalance / _WAD;
uint256 nftAmountToMint = _zeroFloorSub(toMaxNFTs, toIndex);
uint256 toEnd = toBalance / _WAD;
_PackedLogs memory packedLogs = _packedLogsMalloc(_zeroFloorSub(toEnd, toIndex));

if (nftAmountToMint != 0) {
if (packedLogs.logs.length != 0) {
uint256 numBurned = $.numBurned;
_PackedLogs memory packedLogs = _packedLogsMalloc(nftAmountToMint);

uint256 totalNFTSupply = $.totalNFTSupply;
totalNFTSupply += nftAmountToMint;
totalNFTSupply += packedLogs.logs.length;
$.totalNFTSupply = uint32(totalNFTSupply);

toAddressData.ownedLength = uint32(toMaxNFTs);
toAddressData.ownedLength = uint32(toEnd);

uint256 id = $.nextTokenId;
uint32 toAlias = _registerAndResolveAlias(toAddressData, to);
Expand All @@ -366,7 +366,7 @@ abstract contract DN404 {
_packedLogsAppend(packedLogs, to, id, 0);

if (++id > totalNFTSupply) id = 1;
} while (toIndex != toMaxNFTs);
} while (toIndex != toEnd);

$.nextTokenId = uint32(id);
$.numBurned = uint32(numBurned);
Expand Down Expand Up @@ -463,13 +463,18 @@ abstract contract DN404 {
emit Transfer(from, to, _WAD);
}

function setSkipNFT(bool skipNFT) external {
function getSkipNFT(address a) public view virtual returns (bool) {
AddressData storage d = _getDN404Storage().addressData[a];
return d.initialized ? d.skipNFT : _hasCode(a);
}

function setSkipNFT(bool skipNFT) public {
_setSkipNFT(msg.sender, skipNFT);
}

function _setSkipNFT(address target, bool state) internal {
_addressData(target).skipNFT = state;
emit SkipNFTSet(target, state);
function _setSkipNFT(address a, bool state) internal {
_addressData(a).skipNFT = state;
emit SkipNFTSet(a, state);
}

function _addressData(address a) internal returns (AddressData storage d) {
Expand Down
51 changes: 29 additions & 22 deletions src/DN404Mirror.sol
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,21 @@ contract DN404Mirror {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0xd10b6e0c) // `approveNFT(address,uint256,address)`.
mstore(add(m, 0x20), shr(96, shl(96, spender)))
mstore(add(m, 0x40), id)
mstore(add(m, 0x60), caller())
mstore(0x00, 0xd10b6e0c) // `approveNFT(address,uint256,address)`.
mstore(0x20, shr(96, shl(96, spender)))
mstore(0x40, id)
mstore(0x60, caller())
if iszero(
and(
gt(returndatasize(), 0x1f),
call(gas(), root, callvalue(), add(m, 0x1c), 0x64, 0x00, 0x20)
call(gas(), root, callvalue(), 0x1c, 0x64, 0x00, 0x20)
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
owner := shr(96, shl(96, mload(0x00)))
}
emit Approval(owner, spender, id);
Expand All @@ -205,19 +207,21 @@ contract DN404Mirror {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x813500fc) // `setApprovalForAll(address,bool,address)`.
mstore(add(m, 0x20), shr(96, shl(96, operator)))
mstore(add(m, 0x40), iszero(iszero(approved)))
mstore(add(m, 0x60), caller())
mstore(0x00, 0x813500fc) // `setApprovalForAll(address,bool,address)`.
mstore(0x20, shr(96, shl(96, operator)))
mstore(0x40, iszero(iszero(approved)))
mstore(0x60, caller())
if iszero(
and(
and(eq(mload(0x00), 1), gt(returndatasize(), 0x1f)),
call(gas(), root, callvalue(), add(m, 0x1c), 0x64, 0x00, 0x20)
call(gas(), root, callvalue(), 0x1c, 0x64, 0x00, 0x20)
)
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
emit ApprovalForAll(msg.sender, operator, approved);
}
Expand All @@ -232,18 +236,16 @@ contract DN404Mirror {
/// @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)))
mstore(0x00, 0xe985e9c5) // `isApprovedForAll(address,address)`.
mstore(0x20, shr(96, shl(96, owner)))
mstore(0x40, shr(96, shl(96, operator)))
if iszero(
and(
gt(returndatasize(), 0x1f),
staticcall(gas(), root, add(m, 0x1c), 0x44, 0x00, 0x20)
)
and(gt(returndatasize(), 0x1f), staticcall(gas(), root, 0x1c, 0x44, 0x00, 0x20))
) {
returndatacopy(m, 0x00, returndatasize())
revert(m, returndatasize())
}
mstore(0x40, m) // Restore the free memory pointer.
result := iszero(iszero(mload(0x00)))
}
}
Expand Down Expand Up @@ -366,13 +368,18 @@ contract DN404Mirror {
let end := add(o, shl(5, calldataload(sub(o, 0x20))))
returndatacopy(0x00, returndatasize(), lt(calldatasize(), end))

let evtSig := _TRANSFER_EVENT_SIGNATURE
for {} iszero(eq(o, end)) { o := add(0x20, o) } {
let d := calldataload(o) // Entry in the packed logs.
switch and(0xff, d)
case 0 { log4(codesize(), 0x00, evtSig, 0, shr(96, d), shr(168, shl(160, d))) }
case 1 { log4(codesize(), 0x00, evtSig, shr(96, d), 0, shr(168, shl(160, d))) }
default { revert(0x00, 0x00) }
let a := shr(96, d) // The address.
let b := and(1, d) // Whether it is a burn.
log4(
codesize(),
0x00,
_TRANSFER_EVENT_SIGNATURE,
mul(a, b),
mul(a, iszero(b)),
shr(168, shl(160, d))
)
}
mstore(0x00, 0x01)
return(0x00, 0x20)
Expand Down
24 changes: 24 additions & 0 deletions test/DN404.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,30 @@ contract DN404Test is SoladyTest {
mirror.ownerOf(1);
}

function testMintAndBurn() public {
address initialSupplyOwner = address(1111);

dn.initializeDN404(0, initialSupplyOwner, address(mirror));
assertEq(dn.getSkipNFT(initialSupplyOwner), false);
assertEq(dn.getSkipNFT(address(this)), true);

vm.prank(initialSupplyOwner);
dn.setSkipNFT(false);

dn.mint(initialSupplyOwner, 4 * _WAD);
assertEq(mirror.balanceOf(initialSupplyOwner), 4);

dn.burn(initialSupplyOwner, 2 * _WAD);
assertEq(mirror.balanceOf(initialSupplyOwner), 2);

dn.mint(initialSupplyOwner, 3 * _WAD);
assertEq(mirror.balanceOf(initialSupplyOwner), 5);

for (uint256 i = 1; i <= 5; ++i) {
assertEq(mirror.ownerOf(i), initialSupplyOwner);
}
}

// for viewing gas
function testBatchNFTLog() external {
uint32 totalNFTSupply = 10;
Expand Down
8 changes: 8 additions & 0 deletions test/utils/mocks/MockDN404.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ contract MockDN404 is DN404 {
return _registerAndResolveAlias(_addressData(target), target);
}

function mint(address to, uint256 amount) public {
_mint(to, amount);
}

function burn(address from, uint256 amount) public {
_burn(from, amount);
}

function initializeDN404(
uint96 initialTokenSupply,
address initialSupplyOwner,
Expand Down

0 comments on commit 78640d8

Please sign in to comment.