Skip to content

Commit

Permalink
Fix stack-too-deep
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized committed May 23, 2024
1 parent 21b8e75 commit 44d13b4
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 212 deletions.
48 changes: 24 additions & 24 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ArrayOpsTest:testConcat(uint256) (runs: 270, μ: 309730, ~: 57449)
ArrayOpsTest:testFilled(uint256) (runs: 270, μ: 235215, ~: 37640)
ArrayOpsTest:testZeroAddresses(uint256) (runs: 270, μ: 123888, ~: 27750)
ArrayOpsTest:testConcat(uint256) (runs: 270, μ: 312026, ~: 57449)
ArrayOpsTest:testFilled(uint256) (runs: 270, μ: 233029, ~: 37419)
ArrayOpsTest:testZeroAddresses(uint256) (runs: 270, μ: 123879, ~: 27750)
ArrayOpsTest:test__codesize() (gas: 4235)
BenchTest:testMintAndTransferDN404_01() (gas: 208747)
BenchTest:testMintAndTransferDN404_02() (gas: 214922)
Expand Down Expand Up @@ -165,31 +165,31 @@ DN420OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,ui
DN420OnlyERC20Test:testTransferInsufficientBalanceReverts() (gas: 42411)
DN420OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 270, μ: 43238, ~: 43520)
DN420OnlyERC20Test:test__codesize() (gas: 25149)
DN420Test:testERC1155Methods(uint256) (runs: 270, μ: 4391696, ~: 4132210)
DN420Test:testERC1155MethodsSelfTransfers(uint256) (runs: 270, μ: 2519997, ~: 2729535)
DN420Test:testERC1155Methods(uint256) (runs: 270, μ: 4391584, ~: 4132210)
DN420Test:testERC1155MethodsSelfTransfers(uint256) (runs: 270, μ: 2522288, ~: 2729574)
DN420Test:testFindOwnedIds() (gas: 2703272)
DN420Test:testMintNext() (gas: 2233723)
DN420Test:testMintToNonERC155RecipientReverts(uint256) (runs: 270, μ: 889651, ~: 811831)
DN420Test:testMintToRevertingERC155RecipientReverts(uint256) (runs: 270, μ: 1520669, ~: 1662938)
DN420Test:testMintToZeroReverts(uint256) (runs: 270, μ: 760193, ~: 677060)
DN420Test:testMixed(uint256) (runs: 270, μ: 6033555, ~: 4143544)
DN420Test:testSafeBatchTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 2369190, ~: 2211493)
DN420Test:testSafeTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 1792077, ~: 1505780)
DN420Test:testTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 2915793, ~: 2903854)
DN420Test:testTransferMixedReverts(uint256) (runs: 270, μ: 4050121, ~: 3446804)
DN420Test:test__codesize() (gas: 66645)
MappingsTest:testAddressPairMapSetAndGet(address[2],address[2],uint256,uint256) (runs: 270, μ: 46537, ~: 47053)
MappingsTest:testBitmapSetAndGet(uint256) (runs: 270, μ: 471316, ~: 422631)
MappingsTest:testBitmapSetAndGet(uint256,uint256,bool,bool) (runs: 270, μ: 25034, ~: 26154)
DN420Test:testMintToNonERC155RecipientReverts(uint256) (runs: 270, μ: 889524, ~: 811583)
DN420Test:testMintToRevertingERC155RecipientReverts(uint256) (runs: 270, μ: 1520707, ~: 1662938)
DN420Test:testMintToZeroReverts(uint256) (runs: 270, μ: 760187, ~: 677056)
DN420Test:testMixed(uint256) (runs: 270, μ: 6137792, ~: 4085985)
DN420Test:testSafeBatchTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 2380194, ~: 2211517)
DN420Test:testSafeTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 1792078, ~: 1505780)
DN420Test:testTransferFromToERC1155Recipient(uint256) (runs: 270, μ: 2915790, ~: 2903854)
DN420Test:testTransferMixedReverts(uint256) (runs: 270, μ: 4067325, ~: 3446804)
DN420Test:test__codesize() (gas: 66495)
MappingsTest:testAddressPairMapSetAndGet(address[2],address[2],uint256,uint256) (runs: 270, μ: 47577, ~: 48093)
MappingsTest:testBitmapSetAndGet(uint256) (runs: 270, μ: 484375, ~: 459757)
MappingsTest:testBitmapSetAndGet(uint256,uint256,bool,bool) (runs: 270, μ: 25037, ~: 26154)
MappingsTest:testFindFirstUnset() (gas: 79749)
MappingsTest:testFindFirstUnset(uint256) (runs: 270, μ: 358628, ~: 256916)
MappingsTest:testFindFirstUnset(uint256) (runs: 270, μ: 321303, ~: 241275)
MappingsTest:testRestrictNFTId(uint256) (runs: 270, μ: 340, ~: 341)
MappingsTest:testSetOwnerAliasAndOwnedIndex(uint256,uint32,uint32) (runs: 270, μ: 23503, ~: 23538)
MappingsTest:testStorageSlotsNoCollision(uint256,uint256,uint256,uint256) (runs: 270, μ: 26805, ~: 26735)
MappingsTest:testUint32MapSetAndGet(uint256) (runs: 270, μ: 1338487, ~: 1419935)
MappingsTest:testUint32MapSetAndGet(uint256,uint256,uint32,uint32) (runs: 270, μ: 44554, ~: 46049)
MappingsTest:testWrapNFTIdWithOverflowCheck(uint256,uint256,uint256) (runs: 270, μ: 813, ~: 852)
MappingsTest:test__codesize() (gas: 6840)
MappingsTest:testSetOwnerAliasAndOwnedIndex(uint256,uint32,uint32) (runs: 270, μ: 23504, ~: 23538)
MappingsTest:testStorageSlotsNoCollision(uint256,uint256,uint256,uint256) (runs: 270, μ: 26846, ~: 26750)
MappingsTest:testUint32MapSetAndGet(uint256) (runs: 270, μ: 1323411, ~: 1423759)
MappingsTest:testUint32MapSetAndGet(uint256,uint256,uint32,uint32) (runs: 270, μ: 44629, ~: 46049)
MappingsTest:testWrapNFTIdWithOverflowCheck(uint256,uint256,uint256) (runs: 270, μ: 814, ~: 852)
MappingsTest:test__codesize() (gas: 6849)
MaxUnitInvariant:invariantBurnedPoolLengthIsTailMinusHead() (runs: 10, calls: 150, reverts: 0)
MaxUnitInvariant:invariantDN404BalanceSum() (runs: 10, calls: 150, reverts: 0)
MaxUnitInvariant:invariantMirror721BalanceSum() (runs: 10, calls: 150, reverts: 0)
Expand Down
43 changes: 22 additions & 21 deletions test/DN420.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -520,8 +520,6 @@ contract DN420Test is SoladyTest {
address from;
address to;
uint256 amount;
uint256 balanceSum;
uint256 nftBalanceSum;
uint256 balance;
uint256 nftBalance;
uint256[] fromIds;
Expand Down Expand Up @@ -570,7 +568,7 @@ contract DN420Test is SoladyTest {
allIds = LibSort.union(allIds, ids);
}
for (uint256 i; i < allIds.length; ++i) {
assertEq(dn.exists(allIds[i]), true);
assertTrue(dn.exists(allIds[i]));
}
}
assertLe(allIds.length, dn.totalSupply() / _WAD);
Expand All @@ -586,6 +584,14 @@ contract DN420Test is SoladyTest {
}
}

function _countNumExists(uint256 maxId) internal view returns (uint256 numExists) {
unchecked {
for (uint256 i; i <= maxId; ++i) {
if (dn.exists(i)) ++numExists;
}
}
}

function _maybeCheckInvariants(address[] memory addresses) internal {
if (_random() % 32 == 0) {
_checkBalanceSum(addresses);
Expand All @@ -594,17 +600,13 @@ contract DN420Test is SoladyTest {
_TestMixedTemps memory t;
unchecked {
t.allIds = _findAndCheckAllOwnedIds(addresses);

if (t.allIds.length != 0) {
t.maxId = t.allIds[t.allIds.length - 1];
for (uint256 i; i <= t.maxId; ++i) {
if (dn.exists(i)) ++t.numExists;
}
assertEq(t.maxId, _maxOwnedTokenId(addresses));
assertEq(t.allIds.length, t.numExists);
assertEq(t.allIds.length, _countNumExists(t.maxId));
t.end = t.maxId + (_random() % 32) * (_random() % 32);
for (uint256 i = t.maxId + 1; i <= t.end; ++i) {
assertEq(dn.exists(i), false);
assertFalse(dn.exists(i));
}
}
}
Expand Down Expand Up @@ -636,6 +638,15 @@ contract DN420Test is SoladyTest {
}
}

function _checkAfterNFTTransfer(_TestMixedTemps memory t) internal {
assertEq(dn.ownedCount(t.from), t.nftBalance);
assertEq(dn.balanceOf(t.from), t.balance);
t.idsCopy = dn.findOwnedIds(t.from);
LibSort.sort(t.idsCopy);
LibSort.sort(t.fromIds);
assertEq(t.idsCopy, t.fromIds);
}

function _doDirectNFTTransfer(address[] memory addresses) internal {
_TestMixedTemps memory t;
if (_random() % 4 == 0) {
Expand All @@ -649,12 +660,7 @@ contract DN420Test is SoladyTest {
t.nftBalance = dn.ownedCount(t.from);
vm.prank(t.from);
dn.safeTransferFromNFT(t.from, t.to, id);
assertEq(dn.ownedCount(t.from), t.nftBalance);
assertEq(dn.balanceOf(t.from), t.balance);
uint256[] memory idsAfter = dn.findOwnedIds(t.from);
LibSort.sort(idsAfter);
LibSort.sort(t.fromIds);
assertEq(idsAfter, t.fromIds);
_checkAfterNFTTransfer(t);
return;
}
vm.prank(t.from);
Expand All @@ -678,12 +684,7 @@ contract DN420Test is SoladyTest {
t.nftBalance = dn.ownedCount(t.from);
vm.prank(t.from);
dn.safeBatchTransferFromNFTs(t.from, t.to, t.ids);
assertEq(dn.ownedCount(t.from), t.nftBalance);
assertEq(dn.balanceOf(t.from), t.balance);
t.idsCopy = dn.findOwnedIds(t.from);
LibSort.sort(t.idsCopy);
LibSort.sort(t.fromIds);
assertEq(t.idsCopy, t.fromIds);
_checkAfterNFTTransfer(t);
return;
}
if (_random() % 2 == 0) {
Expand Down
7 changes: 0 additions & 7 deletions test/Mappings.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -425,13 +425,6 @@ contract MappingsTest is SoladyTest {
}
}

function _brutalized(address a) internal pure returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := or(0xf348aeebbad597df99cf9f4f0000000000000000000000000000000000000000, a)
}
}

function testStorageSlotsNoCollision(uint256 slot0, uint256 slot1, uint256 i0, uint256 i1)
public
{
Expand Down
184 changes: 184 additions & 0 deletions test/utils/Brutalizer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @dev WARNING! This mock is strictly intended for testing purposes only.
/// Do NOT copy anything here into production code unless you really know what you are doing.
contract Brutalizer {
/// @dev Fills the memory with junk, for more robust testing of inline assembly
/// which reads/write to the memory.
function _brutalizeMemory() internal view {
// To prevent a solidity 0.8.13 bug.
// See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug
// Basically, we need to access a solidity variable from the assembly to
// tell the compiler that this assembly block is not in isolation.
uint256 zero;
/// @solidity memory-safe-assembly
assembly {
let offset := mload(0x40) // Start the offset at the free memory pointer.
calldatacopy(offset, zero, calldatasize())

// Fill the 64 bytes of scratch space with garbage.
mstore(zero, add(caller(), gas()))
mstore(0x20, keccak256(offset, calldatasize()))
mstore(zero, keccak256(zero, 0x40))

let r0 := mload(zero)
let r1 := mload(0x20)

let cSize := add(codesize(), iszero(codesize()))
if iszero(lt(cSize, 32)) { cSize := sub(cSize, and(mload(0x02), 0x1f)) }
let start := mod(mload(0x10), cSize)
let size := mul(sub(cSize, start), gt(cSize, start))
let times := div(0x7ffff, cSize)
if iszero(lt(times, 128)) { times := 128 }

// Occasionally offset the offset by a pseudorandom large amount.
// Can't be too large, or we will easily get out-of-gas errors.
offset := add(offset, mul(iszero(and(r1, 0xf)), and(r0, 0xfffff)))

// Fill the free memory with garbage.
// prettier-ignore
for { let w := not(0) } 1 {} {
mstore(offset, r0)
mstore(add(offset, 0x20), r1)
offset := add(offset, 0x40)
// We use codecopy instead of the identity precompile
// to avoid polluting the `forge test -vvvv` output with tons of junk.
codecopy(offset, start, size)
codecopy(add(offset, size), 0, start)
offset := add(offset, cSize)
times := add(times, w) // `sub(times, 1)`.
if iszero(times) { break }
}
}
}

/// @dev Fills the scratch space with junk, for more robust testing of inline assembly
/// which reads/write to the memory.
function _brutalizeScratchSpace() internal view {
// To prevent a solidity 0.8.13 bug.
// See: https://blog.soliditylang.org/2022/06/15/inline-assembly-memory-side-effects-bug
// Basically, we need to access a solidity variable from the assembly to
// tell the compiler that this assembly block is not in isolation.
uint256 zero;
/// @solidity memory-safe-assembly
assembly {
let offset := mload(0x40) // Start the offset at the free memory pointer.
calldatacopy(offset, zero, calldatasize())

// Fill the 64 bytes of scratch space with garbage.
mstore(zero, add(caller(), gas()))
mstore(0x20, keccak256(offset, calldatasize()))
mstore(zero, keccak256(zero, 0x40))
}
}

/// @dev Fills the memory with junk, for more robust testing of inline assembly
/// which reads/write to the memory.
modifier brutalizeMemory() {
_brutalizeMemory();
_;
_checkMemory();
}

/// @dev Fills the scratch space with junk, for more robust testing of inline assembly
/// which reads/write to the memory.
modifier brutalizeScratchSpace() {
_brutalizeScratchSpace();
_;
_checkMemory();
}

/// @dev Returns the result with the upper bits dirtied.
function _brutalized(address value) internal pure returns (address result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, xor(add(shl(32, value), calldataload(0x00)), mload(0x10)))
mstore(0x20, calldataload(0x04))
mstore(0x10, keccak256(0x00, 0x60))
result := or(shl(160, mload(0x10)), value)
}
}

/// @dev Returns the result with the upper bits dirtied.
function _brutalized(uint96 value) internal pure returns (uint96 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, xor(add(shl(32, value), calldataload(0x00)), mload(0x10)))
mstore(0x20, calldataload(0x04))
mstore(0x10, keccak256(0x00, 0x60))
result := or(shl(96, mload(0x10)), value)
}
}

/// @dev Returns the result with the upper bits dirtied.
function _brutalized(bool value) internal pure returns (bool result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, xor(add(shl(32, value), calldataload(0x00)), mload(0x10)))
mstore(0x20, calldataload(0x04))
mstore(0x10, keccak256(0x00, 0x60))
result := mul(iszero(iszero(value)), mload(0x10))
}
}

/// @dev Misaligns the free memory pointer.
/// The free memory pointer has a 1/32 chance to be aligned.
function _misalignFreeMemoryPointer() internal pure {
uint256 twoWords = 0x40;
/// @solidity memory-safe-assembly
assembly {
let m := mload(twoWords)
m := add(m, mul(and(keccak256(0x00, twoWords), 0x1f), iszero(and(m, 0x1f))))
mstore(twoWords, m)
}
}

/// @dev Check if the free memory pointer and the zero slot are not contaminated.
/// Useful for cases where these slots are used for temporary storage.
function _checkMemory() internal pure {
bool zeroSlotIsNotZero;
bool freeMemoryPointerOverflowed;
/// @solidity memory-safe-assembly
assembly {
// Write ones to the free memory, to make subsequent checks fail if
// insufficient memory is allocated.
mstore(mload(0x40), not(0))
// Test at a lower, but reasonable limit for more safety room.
if gt(mload(0x40), 0xffffffff) { freeMemoryPointerOverflowed := 1 }
// Check the value of the zero slot.
zeroSlotIsNotZero := mload(0x60)
}
if (freeMemoryPointerOverflowed) revert("`0x40` overflowed!");
if (zeroSlotIsNotZero) revert("`0x60` is not zero!");
}

/// @dev Check if `s`:
/// - Has sufficient memory allocated.
/// - Is zero right padded (cuz some frontends like Etherscan has issues
/// with decoding non-zero-right-padded strings).
function _checkMemory(bytes memory s) internal pure {
bool notZeroRightPadded;
bool insufficientMalloc;
/// @solidity memory-safe-assembly
assembly {
// Write ones to the free memory, to make subsequent checks fail if
// insufficient memory is allocated.
mstore(mload(0x40), not(0))
let length := mload(s)
let lastWord := mload(add(add(s, 0x20), and(length, not(0x1f))))
let remainder := and(length, 0x1f)
if remainder { if shl(mul(8, remainder), lastWord) { notZeroRightPadded := 1 } }
// Check if the memory allocated is sufficient.
if length { if gt(add(add(s, 0x20), length), mload(0x40)) { insufficientMalloc := 1 } }
}
if (notZeroRightPadded) revert("Not zero right padded!");
if (insufficientMalloc) revert("Insufficient memory allocation!");
_checkMemory();
}

/// @dev For checking the memory allocation for string `s`.
function _checkMemory(string memory s) internal pure {
_checkMemory(bytes(s));
}
}
Loading

0 comments on commit 44d13b4

Please sign in to comment.