Skip to content

Commit

Permalink
♻️ Invariant Suite Refactor (Vectorized#137)
Browse files Browse the repository at this point in the history
* Add combined Guardian Audit fixes

* Fix stack-too-deep

* Fix stack-too-deep

* Fix stack-too-deep

* Update ArrayOps test

* Optimize

* Optimize

* Update invariant suite

* gas snapshot

* update assertion message

* Fix compiler warnings

* Add invariant intense ci workflow for serious invariant fuzzing

* Fix stack-too-deep

* Change aux type in invariant handler to uint256 for futureproofness

* Try fix stack-too-deep

* Try fix stack-too-deep

---------

Co-authored-by: Vectorized <[email protected]>
Co-authored-by: Danny G <[email protected]>
  • Loading branch information
3 people authored May 13, 2024
1 parent ecdc346 commit c5a20f8
Show file tree
Hide file tree
Showing 10 changed files with 920 additions and 206 deletions.
130 changes: 73 additions & 57 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ ArrayOpsTest:testConcat(uint256) (runs: 258, μ: 333470, ~: 57449)
ArrayOpsTest:testFilled(uint256) (runs: 258, μ: 321295, ~: 39915)
ArrayOpsTest:testZeroAddresses(uint256) (runs: 258, μ: 116711, ~: 28110)
ArrayOpsTest:test__codesize() (gas: 4235)
BaseInvariantTest:invariantDN404BalanceSum() (runs: 30, calls: 450, reverts: 0)
BaseInvariantTest:invariantMirror721BalanceSum() (runs: 30, calls: 450, reverts: 0)
BaseInvariantTest:invariantMirrorAndBaseRemainImmutable() (runs: 30, calls: 450, reverts: 0)
BaseInvariantTest:invariantTotalReflectionIsValid() (runs: 30, calls: 450, reverts: 0)
BaseInvariantTest:invariantUserReflectionIsValid() (runs: 30, calls: 450, reverts: 0)
BenchTest:testMintAndTransferDN404_01() (gas: 208785)
BenchTest:testMintAndTransferDN404_02() (gas: 214871)
BenchTest:testMintAndTransferDN404_03() (gas: 221088)
Expand Down Expand Up @@ -72,35 +67,35 @@ BenchTest:testMintPandora_14() (gas: 1037484)
BenchTest:testMintPandora_15() (gas: 1106989)
BenchTest:testMintPandora_16() (gas: 1176450)
BenchTest:test__codesize() (gas: 26636)
DN404CustomUnitTest:testInitializeCorrectUnitSuccess() (gas: 129452)
DN404CustomUnitTest:testInitializeWithUnitTooLargeReverts() (gas: 33802)
DN404CustomUnitTest:testInitializeWithZeroUnitReverts() (gas: 13875)
DN404CustomUnitTest:testMint() (gas: 162509)
DN404CustomUnitTest:testMintWithoutNFTs(uint256,uint256,uint256) (runs: 258, μ: 159735, ~: 162077)
DN404CustomUnitTest:testNFTMint() (gas: 64397938)
DN404CustomUnitTest:testNFTMintAndBurn(uint256,uint256,uint256) (runs: 258, μ: 202123, ~: 158412)
DN404CustomUnitTest:testNFTMintViaTransfer(uint256,uint256,uint256) (runs: 258, μ: 218180, ~: 245246)
DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256) (runs: 258, μ: 604, ~: 669)
DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256,uint256) (runs: 258, μ: 798, ~: 746)
DN404CustomUnitTest:testInitializeCorrectUnitSuccess() (gas: 129453)
DN404CustomUnitTest:testInitializeWithUnitTooLargeReverts() (gas: 33803)
DN404CustomUnitTest:testInitializeWithZeroUnitReverts() (gas: 13876)
DN404CustomUnitTest:testMint() (gas: 162570)
DN404CustomUnitTest:testMintWithoutNFTs(uint256,uint256,uint256) (runs: 258, μ: 159260, ~: 162101)
DN404CustomUnitTest:testNFTMint() (gas: 64397999)
DN404CustomUnitTest:testNFTMintAndBurn(uint256,uint256,uint256) (runs: 258, μ: 198638, ~: 158436)
DN404CustomUnitTest:testNFTMintViaTransfer(uint256,uint256,uint256) (runs: 258, μ: 215638, ~: 245057)
DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256) (runs: 258, μ: 606, ~: 669)
DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256,uint256) (runs: 258, μ: 801, ~: 746)
DN404CustomUnitTest:testUnitInvalidCheckTrick(uint256) (runs: 258, μ: 526, ~: 527)
DN404CustomUnitTest:test__codesize() (gas: 28791)
DN404CustomUnitTest:test__codesize() (gas: 29396)
DN404MirrorTest:testBaseERC20() (gas: 114133)
DN404MirrorTest:testFnSelectorNotRecognized() (gas: 6236)
DN404MirrorTest:testLinkMirrorContract() (gas: 39444)
DN404MirrorTest:testLogDirectTransfers() (gas: 394754)
DN404MirrorTest:testLogDirectTransfers() (gas: 394953)
DN404MirrorTest:testLogTransfer() (gas: 120210)
DN404MirrorTest:testNameAndSymbol(string,string) (runs: 258, μ: 205968, ~: 206481)
DN404MirrorTest:testNameAndSymbol(string,string) (runs: 258, μ: 205969, ~: 206482)
DN404MirrorTest:testNotLinked() (gas: 12777)
DN404MirrorTest:testPullOwner() (gas: 112113)
DN404MirrorTest:testPullOwnerWithOwnable() (gas: 3608979)
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 258, μ: 477205, ~: 477146)
DN404MirrorTest:testSetAndGetApprovalForAll() (gas: 329641)
DN404MirrorTest:testSetAndGetApproved() (gas: 326862)
DN404MirrorTest:testPullOwnerWithOwnable() (gas: 3720540)
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 258, μ: 477332, ~: 477272)
DN404MirrorTest:testSetAndGetApprovalForAll() (gas: 329705)
DN404MirrorTest:testSetAndGetApproved() (gas: 326724)
DN404MirrorTest:testSupportsInterface() (gas: 7544)
DN404MirrorTest:testTokenURI(string,uint256) (runs: 258, μ: 261965, ~: 270740)
DN404MirrorTest:testTransferFrom(uint32) (runs: 258, μ: 378199, ~: 378140)
DN404MirrorTest:testTransferFromMixed(uint256) (runs: 258, μ: 724455, ~: 657742)
DN404MirrorTest:test__codesize() (gas: 58679)
DN404MirrorTest:testTokenURI(string,uint256) (runs: 258, μ: 261388, ~: 270720)
DN404MirrorTest:testTransferFrom(uint32) (runs: 258, μ: 378530, ~: 378470)
DN404MirrorTest:testTransferFromMixed(uint256) (runs: 258, μ: 732929, ~: 688889)
DN404MirrorTest:test__codesize() (gas: 59865)
DN404OnlyERC20Test:testApprove() (gas: 35803)
DN404OnlyERC20Test:testApprove(address,uint256) (runs: 258, μ: 31276, ~: 31354)
DN404OnlyERC20Test:testBurn() (gas: 48166)
Expand All @@ -123,31 +118,31 @@ DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,ui
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts() (gas: 43963)
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 258, μ: 44752, ~: 45072)
DN404OnlyERC20Test:test__codesize() (gas: 28420)
DN404Test:testBatchNFTLog() (gas: 326430)
DN404Test:testBurnOnTransfer(uint32,address) (runs: 258, μ: 274865, ~: 274875)
DN404Test:testBatchNFTLog() (gas: 326562)
DN404Test:testBurnOnTransfer(uint32,address) (runs: 258, μ: 274776, ~: 274776)
DN404Test:testFnSelectorNotRecognized() (gas: 6268)
DN404Test:testInitialize(uint32,address) (runs: 258, μ: 111562, ~: 113018)
DN404Test:testMintAndBurn() (gas: 346429)
DN404Test:testMintAndBurn2() (gas: 282888)
DN404Test:testMintNext() (gas: 705169)
DN404Test:testMintNextContiguous(uint256) (runs: 258, μ: 575391, ~: 503266)
DN404Test:testMintOnTransfer(uint32,address) (runs: 258, μ: 289653, ~: 289663)
DN404Test:testMixed(bytes32) (runs: 258, μ: 568950, ~: 506676)
DN404Test:testNameAndSymbol(string,string) (runs: 258, μ: 205703, ~: 206216)
DN404Test:testNumAliasesOverflowReverts() (gas: 40961)
DN404Test:testOwnedIds() (gas: 358936)
DN404Test:testOwnedIds(uint256) (runs: 258, μ: 262000, ~: 283468)
DN404Test:testPermit2() (gas: 455048)
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 258, μ: 120102, ~: 120257)
DN404Test:testSetAndGetAux(address,uint88) (runs: 258, μ: 22110, ~: 22377)
DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 258, μ: 130306, ~: 139794)
DN404Test:testSetAndGetSkipNFT() (gas: 706550)
DN404Test:testTokenURI(string,uint256) (runs: 258, μ: 157063, ~: 167276)
DN404Test:testTransferWithMirrorEvent() (gas: 409794)
DN404Test:testTransfersAndBurns() (gas: 487073)
DN404Test:testWrapAround(uint32,uint256) (runs: 258, μ: 365275, ~: 361721)
DN404Test:testZZZ() (gas: 589997)
DN404Test:test__codesize() (gas: 63087)
DN404Test:testInitialize(uint32,address) (runs: 258, μ: 111548, ~: 113120)
DN404Test:testMintAndBurn() (gas: 346232)
DN404Test:testMintAndBurn2() (gas: 282747)
DN404Test:testMintNext() (gas: 704841)
DN404Test:testMintNextContiguous(uint256) (runs: 258, μ: 571188, ~: 503944)
DN404Test:testMintOnTransfer(uint32,address) (runs: 258, μ: 289489, ~: 289489)
DN404Test:testMixed(bytes32) (runs: 258, μ: 556282, ~: 506117)
DN404Test:testNameAndSymbol(string,string) (runs: 258, μ: 205704, ~: 206217)
DN404Test:testNumAliasesOverflowReverts() (gas: 40944)
DN404Test:testOwnedIds() (gas: 360073)
DN404Test:testOwnedIds(uint256) (runs: 258, μ: 259651, ~: 259514)
DN404Test:testPermit2() (gas: 455012)
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 258, μ: 120036, ~: 120191)
DN404Test:testSetAndGetAux(address,uint88) (runs: 258, μ: 21986, ~: 22287)
DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 258, μ: 130305, ~: 139793)
DN404Test:testSetAndGetSkipNFT() (gas: 708390)
DN404Test:testTokenURI(string,uint256) (runs: 258, μ: 157085, ~: 167298)
DN404Test:testTransferWithMirrorEvent() (gas: 409816)
DN404Test:testTransfersAndBurns() (gas: 487010)
DN404Test:testWrapAround(uint32,uint256) (runs: 258, μ: 365959, ~: 361919)
DN404Test:testZZZ() (gas: 593654)
DN404Test:test__codesize() (gas: 63717)
MappingsTest:testAddressPairMapSetAndGet(address[2],address[2],uint256,uint256) (runs: 258, μ: 46744, ~: 47053)
MappingsTest:testBitmapSetAndGet(uint256) (runs: 258, μ: 543990, ~: 578417)
MappingsTest:testBitmapSetAndGet(uint256,uint256,bool,bool) (runs: 258, μ: 26060, ~: 26282)
Expand All @@ -160,20 +155,41 @@ MappingsTest:testUint32MapSetAndGet(uint256) (runs: 258, μ: 1294852, ~: 1191451
MappingsTest:testUint32MapSetAndGet(uint256,uint256,uint32,uint32) (runs: 258, μ: 44498, ~: 46113)
MappingsTest:testWrapNFTIdWithOverflowCheck(uint256,uint256,uint256) (runs: 258, μ: 840, ~: 852)
MappingsTest:test__codesize() (gas: 6840)
MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 258, μ: 60661, ~: 61606)
MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 258, μ: 85754, ~: 85738)
MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 258, μ: 31069, ~: 31139)
MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 258, μ: 82738, ~: 82896)
MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 258, μ: 89257, ~: 89426)
MintTests:test__codesize() (gas: 27326)
MaxUnitInvariant:invariantBurnedPoolLengthIsTailMinusHead() (runs: 10, calls: 150, reverts: 0)
MaxUnitInvariant:invariantDN404BalanceSum() (runs: 10, calls: 150, reverts: 0)
MaxUnitInvariant:invariantMirror721BalanceSum() (runs: 10, calls: 150, reverts: 0)
MaxUnitInvariant:invariantMirrorAndBaseRemainImmutable() (runs: 10, calls: 150, reverts: 0)
MaxUnitInvariant:invariantNoUserOwnsInvalidToken() (runs: 10, calls: 150, reverts: 0)
MaxUnitInvariant:invariantTotalReflectionIsValid() (runs: 10, calls: 150, reverts: 0)
MaxUnitInvariant:invariantUserReflectionIsValid() (runs: 10, calls: 150, reverts: 0)
MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 258, μ: 60924, ~: 61606)
MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 258, μ: 85931, ~: 85935)
MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 258, μ: 31066, ~: 30984)
MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 258, μ: 82938, ~: 83100)
MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 258, μ: 89373, ~: 89630)
MintTests:test__codesize() (gas: 27956)
NFTMintDN404Test:testAllowlistMint() (gas: 258887)
NFTMintDN404Test:testMint() (gas: 231507)
NFTMintDN404Test:testTotalSupplyReached() (gas: 628016918)
NFTMintDN404Test:test__codesize() (gas: 22593)
NonMultipleUnitInvariant:invariantBurnedPoolLengthIsTailMinusHead() (runs: 10, calls: 150, reverts: 0)
NonMultipleUnitInvariant:invariantDN404BalanceSum() (runs: 10, calls: 150, reverts: 0)
NonMultipleUnitInvariant:invariantMirror721BalanceSum() (runs: 10, calls: 150, reverts: 0)
NonMultipleUnitInvariant:invariantMirrorAndBaseRemainImmutable() (runs: 10, calls: 150, reverts: 0)
NonMultipleUnitInvariant:invariantNoUserOwnsInvalidToken() (runs: 10, calls: 150, reverts: 0)
NonMultipleUnitInvariant:invariantTotalReflectionIsValid() (runs: 10, calls: 150, reverts: 0)
NonMultipleUnitInvariant:invariantUserReflectionIsValid() (runs: 10, calls: 150, reverts: 0)
SimpleDN404Test:testMint() (gas: 47073)
SimpleDN404Test:testName() (gas: 9155)
SimpleDN404Test:testSymbol() (gas: 9153)
SimpleDN404Test:testWithdraw() (gas: 18232)
SimpleDN404Test:test__codesize() (gas: 18555)
SoladyTest:test__codesize() (gas: 840)
TestPlus:test__codesize() (gas: 406)
TestPlus:test__codesize() (gas: 406)
WADUnitInvariant:invariantBurnedPoolLengthIsTailMinusHead() (runs: 10, calls: 150, reverts: 0)
WADUnitInvariant:invariantDN404BalanceSum() (runs: 10, calls: 150, reverts: 0)
WADUnitInvariant:invariantMirror721BalanceSum() (runs: 10, calls: 150, reverts: 0)
WADUnitInvariant:invariantMirrorAndBaseRemainImmutable() (runs: 10, calls: 150, reverts: 0)
WADUnitInvariant:invariantNoUserOwnsInvalidToken() (runs: 10, calls: 150, reverts: 0)
WADUnitInvariant:invariantTotalReflectionIsValid() (runs: 10, calls: 150, reverts: 0)
WADUnitInvariant:invariantUserReflectionIsValid() (runs: 10, calls: 150, reverts: 0)
27 changes: 27 additions & 0 deletions .github/workflows/ci-invariant-intense.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: ci-invariant-intense

on:
workflow_dispatch:

jobs:
tests:
name: Forge Testing invariant intense
runs-on: ubuntu-latest

strategy:
matrix:
profile: [invariant-intense-0,invariant-intense-1,invariant-intense-2,invariant-intense-3]

steps:
- uses: actions/checkout@v4

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Install Dependencies
run: forge install

- name: Run Tests with ${{ matrix.profile }}
run: FOUNDRY_INVARIANT_RUNS=300 forge test
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ line_length = 100 # While we allow up to 120, we lint at 100 for readability.
runs = 256

[invariant]
runs = 30
runs = 10
depth = 15
fail_on_revert = true
dictionary_weight = 80
79 changes: 29 additions & 50 deletions test/invariants/BaseInvariant.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,42 @@ import {Test} from "../utils/forge-std/Test.sol";
import {StdInvariant} from "../utils/forge-std/StdInvariant.sol";
import {DN404} from "../../src/DN404.sol";
import {DN404Mirror} from "../../src/DN404Mirror.sol";
import {MockDN404} from "../utils/mocks/MockDN404.sol";
import {MockDN404CustomUnit} from "../utils/mocks/MockDN404CustomUnit.sol";
import {DN404Handler} from "./handlers/DN404Handler.sol";

// forgefmt: disable-start
/**************************************************************************************************************************************/
/*** Invariant Tests ***/
/***************************************************************************************************************************************
* NFT total supply * WAD must always be less than or equal to the ERC20 total supply
* NFT balance of a user * WAD must be less than or equal to the ERC20 balance of that user
* NFT balance of all users summed up must be equal to the NFT total supply
* ERC20 balance of all users summed up must be equal to the ERC20 total supply
* Mirror contract known to the base and the base contract known to the mirror never change after initialization
* Length of burned pool matches distance between head and tail
* No user owns type(uint32).max invalid ID.
/**************************************************************************************************************************************/
/*** Vault Invariants ***/
/*** Common setup and invariants used by all implemented invariant tests. ***/
/**************************************************************************************************************************************/
// forgefmt: disable-end
contract BaseInvariantTest is Test, StdInvariant {
abstract contract BaseInvariantTest is Test, StdInvariant {
address user0 = vm.addr(uint256(keccak256("User0")));
address user1 = vm.addr(uint256(keccak256("User1")));
address user2 = vm.addr(uint256(keccak256("User2")));
address user3 = vm.addr(uint256(keccak256("User3")));
address user4 = vm.addr(uint256(keccak256("User4")));
address user5 = vm.addr(uint256(keccak256("User5")));
uint256 private constant _WAD = 1000000000000000000;
address[] users = [user0, user1, user2, user3, user4, user5];

MockDN404 dn404;
uint256 internal constant _WAD = 1000000000000000000;

MockDN404CustomUnit dn404;
DN404Mirror dn404Mirror;
DN404Handler dn404Handler;

function setUp() external virtual {
dn404 = new MockDN404();
function setUp() public virtual {
dn404 = new MockDN404CustomUnit();
dn404.setUnit(_unit());
dn404Mirror = new DN404Mirror(address(this));
dn404.initializeDN404(0, address(0), address(dn404Mirror));

Expand All @@ -51,47 +54,6 @@ contract BaseInvariantTest is Test, StdInvariant {
targetContract(address(dn404Handler));
}

function invariantTotalReflectionIsValid() external {
assertLe(
dn404Mirror.totalSupply() * _WAD,
dn404.totalSupply(),
"NFT total supply * wad is greater than ERC20 total supply"
);
}

function invariantUserReflectionIsValid() external {
assertLe(
dn404Mirror.balanceOf(user0) * _WAD,
dn404.balanceOf(user0),
"NFT balanceOf user 0 * wad is greater its ERC20 balanceOf"
);
assertLe(
dn404Mirror.balanceOf(user1) * _WAD,
dn404.balanceOf(user1),
"NFT balanceOf user 1 * wad is greater its ERC20 balanceOf"
);
assertLe(
dn404Mirror.balanceOf(user2) * _WAD,
dn404.balanceOf(user2),
"NFT balanceOf user 2 * wad is greater its ERC20 balanceOf"
);
assertLe(
dn404Mirror.balanceOf(user3) * _WAD,
dn404.balanceOf(user3),
"NFT balanceOf user 3 * wad is greater its ERC20 balanceOf"
);
assertLe(
dn404Mirror.balanceOf(user4) * _WAD,
dn404.balanceOf(user4),
"NFT balanceOf user 4 * wad is greater its ERC20 balanceOf"
);
assertLe(
dn404Mirror.balanceOf(user5) * _WAD,
dn404.balanceOf(user5),
"NFT balanceOf user 5 * wad is greater its ERC20 balanceOf"
);
}

function invariantMirror721BalanceSum() external {
uint256 total = dn404Handler.nftsOwned(user0) + dn404Handler.nftsOwned(user1)
+ dn404Handler.nftsOwned(user2) + dn404Handler.nftsOwned(user3)
Expand All @@ -111,4 +73,21 @@ contract BaseInvariantTest is Test, StdInvariant {
);
assertEq(dn404Mirror.baseERC20(), address(dn404), "base erc20 changed after initialization");
}

function invariantBurnedPoolLengthIsTailMinusHead() external {
(uint256 burnedHead, uint256 burnedTail) = dn404.burnedPoolHeadTail();
uint256[] memory burnedIds = dn404.burnedPool();
assertEq(
burnedIds.length,
burnedTail - burnedHead,
"burned ids length != burned tail - burned head"
);
}

function invariantNoUserOwnsInvalidToken() external {
address owner = dn404Mirror.ownerAt(type(uint32).max);
assertEq(owner, address(0), "user owns uint32 max id");
}

function _unit() internal virtual returns (uint256);
}
21 changes: 21 additions & 0 deletions test/invariants/MaxUnitInvariant.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import {Test} from "../utils/forge-std/Test.sol";
import {StdInvariant} from "../utils/forge-std/StdInvariant.sol";
import {DN404} from "../../src/DN404.sol";
import {DN404Mirror} from "../../src/DN404Mirror.sol";
import {MockDN404CustomUnit} from "../utils/mocks/MockDN404CustomUnit.sol";
import {DN404Handler} from "./handlers/DN404Handler.sol";
import {StaticUnitInvariant} from "./StaticUnitInvariant.t.sol";

/// @dev Invariant tests with the max allowed unit.
contract MaxUnitInvariant is StaticUnitInvariant {
function setUp() public virtual override {
StaticUnitInvariant.setUp();
}

function _unit() internal pure override returns (uint256) {
return type(uint96).max;
}
}
Loading

0 comments on commit c5a20f8

Please sign in to comment.