Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
58 changes: 33 additions & 25 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ BenchTest:testMintPandora_16() (gas: 1176473)
BenchTest:test__codesize() (gas: 29290)
DN404CustomUnitTest:testInitializeWithZeroUnitReverts() (gas: 85821)
DN404CustomUnitTest:testMint() (gas: 162679)
DN404CustomUnitTest:testMintWithoutNFTs(uint256,uint256,uint256) (runs: 256, μ: 155067, ~: 162175)
DN404CustomUnitTest:testMintWithoutNFTs(uint256,uint256,uint256) (runs: 256, μ: 156145, ~: 162184)
DN404CustomUnitTest:testNFTMint() (gas: 56916373)
DN404CustomUnitTest:testNFTMintAndBurn(uint256,uint256,uint256) (runs: 256, μ: 200350, ~: 161045)
DN404CustomUnitTest:testNFTMintViaTransfer(uint256,uint256,uint256) (runs: 256, μ: 211152, ~: 235934)
DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256,uint256) (runs: 256, μ: 1053, ~: 1147)
DN404CustomUnitTest:testNFTMintAndBurn(uint256,uint256,uint256) (runs: 256, μ: 210189, ~: 161083)
DN404CustomUnitTest:testNFTMintViaTransfer(uint256,uint256,uint256) (runs: 256, μ: 215396, ~: 238870)
DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256,uint256) (runs: 256, μ: 1055, ~: 1147)
DN404CustomUnitTest:test__codesize() (gas: 22937)
DN404MirrorTest:testBaseERC20() (gas: 114632)
DN404MirrorTest:testLinkMirrorContract() (gas: 45829)
Expand All @@ -80,56 +80,64 @@ DN404MirrorTest:testNameAndSymbol(string,string) (runs: 256, μ: 207712, ~: 2080
DN404MirrorTest:testNotLinked() (gas: 12767)
DN404MirrorTest:testPullOwner() (gas: 112470)
DN404MirrorTest:testPullOwnerWithOwnable() (gas: 2331186)
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 256, μ: 467433, ~: 467421)
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 256, μ: 467429, ~: 467421)
DN404MirrorTest:testSetAndGetApprovalForAll() (gas: 325041)
DN404MirrorTest:testSetAndGetApproved() (gas: 318317)
DN404MirrorTest:testSupportsInterface() (gas: 7566)
DN404MirrorTest:testTokenURI(string,uint256) (runs: 256, μ: 158201, ~: 135900)
DN404MirrorTest:testTransferFrom(uint32) (runs: 256, μ: 342514, ~: 342506)
DN404MirrorTest:testTransferFrom(uint32) (runs: 256, μ: 342513, ~: 342506)
DN404MirrorTest:test__codesize() (gas: 41739)
DN404OnlyERC20Test:testApprove() (gas: 35902)
DN404OnlyERC20Test:testApprove(address,uint256) (runs: 256, μ: 30209, ~: 31453)
DN404OnlyERC20Test:testBurn() (gas: 49717)
DN404OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 256, μ: 50773, ~: 50918)
DN404OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 43761, ~: 43846)
DN404OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 256, μ: 50959, ~: 50918)
DN404OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 43866, ~: 43855)
DN404OnlyERC20Test:testInfiniteApproveTransferFrom() (gas: 101928)
DN404OnlyERC20Test:testMaxSupplyTrick(uint256) (runs: 256, μ: 541, ~: 541)
DN404OnlyERC20Test:testMetadata() (gas: 10111)
DN404OnlyERC20Test:testMint() (gas: 45292)
DN404OnlyERC20Test:testMintOverMaxLimitReverts() (gas: 40524)
DN404OnlyERC20Test:testMintz(address,uint256) (runs: 256, μ: 45601, ~: 45714)
DN404OnlyERC20Test:testMintz(address,uint256) (runs: 256, μ: 45608, ~: 45714)
DN404OnlyERC20Test:testTransfer() (gas: 74486)
DN404OnlyERC20Test:testTransfer(address,uint256) (runs: 256, μ: 74892, ~: 74943)
DN404OnlyERC20Test:testTransfer(address,uint256) (runs: 256, μ: 74931, ~: 74943)
DN404OnlyERC20Test:testTransferFrom() (gas: 84386)
DN404OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 256, μ: 105342, ~: 107367)
DN404OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 256, μ: 105413, ~: 107367)
DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts() (gas: 68045)
DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 256, μ: 68736, ~: 69150)
DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 256, μ: 68569, ~: 69155)
DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts() (gas: 74812)
DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 75972, ~: 75933)
DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 75872, ~: 75942)
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts() (gas: 66223)
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 67258, ~: 67314)
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 67261, ~: 67319)
DN404OnlyERC20Test:test__codesize() (gas: 29166)
DN404Test:testBatchNFTLog() (gas: 305444)
DN404Test:testBurnOnTransfer(uint32,address) (runs: 256, μ: 264828, ~: 264828)
DN404Test:testInitialize(uint32,address) (runs: 256, μ: 112926, ~: 116582)
DN404Test:testInitialize(uint32,address) (runs: 256, μ: 113846, ~: 116582)
DN404Test:testMintAndBurn() (gas: 336949)
DN404Test:testMintAndBurn2() (gas: 263115)
DN404Test:testMintOnTransfer(uint32,address) (runs: 256, μ: 263589, ~: 263589)
DN404Test:testMixed(uint256) (runs: 256, μ: 597598, ~: 559587)
DN404Test:testMintOnTransfer(uint32,address) (runs: 256, μ: 263569, ~: 263589)
DN404Test:testMixed(uint256) (runs: 256, μ: 614158, ~: 591663)
DN404Test:testNameAndSymbol(string,string) (runs: 256, μ: 207385, ~: 207726)
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 126897, ~: 127063)
DN404Test:testSetAndGetAux(address,uint88) (runs: 256, μ: 21968, ~: 22275)
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 126985, ~: 127063)
DN404Test:testSetAndGetAux(address,uint88) (runs: 256, μ: 22003, ~: 22275)
DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 256, μ: 129929, ~: 120990)
DN404Test:testSetAndGetSkipNFT() (gas: 89209)
DN404Test:testTokenURI(string,uint256) (runs: 256, μ: 158089, ~: 135788)
DN404Test:testTransfersAndBurns() (gas: 448183)
DN404Test:testWrapAround(uint32,uint256) (runs: 256, μ: 351005, ~: 344424)
DN404Test:testWrapAround(uint32,uint256) (runs: 256, μ: 351664, ~: 344424)
DN404Test:test__codesize() (gas: 40228)
MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 256, μ: 59079, ~: 59172)
MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 256, μ: 86042, ~: 86035)
MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 256, μ: 31106, ~: 31169)
MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 256, μ: 83052, ~: 83129)
MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 256, μ: 89595, ~: 89647)
DNFactoryTest:testDeploy() (gas: 5768151)
DNFactoryTest:testRevert_ArrayLengthMismatch() (gas: 545403)
DNFactoryTest:testRevert_InvalidAirdropConfig() (gas: 535403)
DNFactoryTest:testRevert_InvalidAllocations() (gas: 538545)
DNFactoryTest:testRevert_InvalidLiquidityConfig() (gas: 538315)
DNFactoryTest:testRevert_LiquidityLocked() (gas: 5723699)
DNFactoryTest:testWithdrawLP() (gas: 5731761)
DNFactoryTest:test__codesize() (gas: 26742)
MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 256, μ: 59093, ~: 59168)
MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 256, μ: 86020, ~: 86035)
MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 256, μ: 31101, ~: 31169)
MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 256, μ: 83021, ~: 83130)
MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 256, μ: 89597, ~: 89648)
MintTests:test__codesize() (gas: 21844)
NFTMintDN404Test:testAllowlistMint() (gas: 286143)
NFTMintDN404Test:testMint() (gas: 246322)
Expand Down
5 changes: 4 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ runs = 256
runs = 30
depth = 15
fail_on_revert = true
dictionary_weight = 80
dictionary_weight = 80

[rpc_endpoints]
mainnet = '${ETH_RPC_URL}'
72 changes: 72 additions & 0 deletions src/DNFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.4;

import {LibClone} from "solady/utils/LibClone.sol";
import {DN404Cloneable} from "./example/DN404Cloneable.sol";

contract DNFactory {
error FailedToInitialize();
error ArrayLengthMismatch();
error InvalidLiquidityConfig();
error InvalidAllocations();
error EtherProvidedForZeroLiquidity();
error InvalidAirdropConfig();

address public immutable implementation;

struct Allocations {
uint80 liquidityAllocation;
uint80 teamAllocation;
uint80 airdropAllocation;
}

constructor() {
DN404Cloneable dn = new DN404Cloneable();
implementation = address(dn);
}

function deployDN(
string calldata name,
string calldata sym,
Allocations calldata allocations,
uint96 totalSupply,
uint256 liquidityLockPeriodInSeconds,
address[] calldata addresses,
uint256[] calldata amounts
) external payable returns (address tokenAddress) {
if (
allocations.liquidityAllocation + allocations.teamAllocation
+ allocations.airdropAllocation != totalSupply
) {
revert InvalidAllocations();
}
if (addresses.length != amounts.length) revert ArrayLengthMismatch();
if (
(addresses.length == 0 && allocations.airdropAllocation > 0)
|| (addresses.length != 0 && allocations.airdropAllocation == 0)
) revert InvalidAirdropConfig();
if (
(allocations.liquidityAllocation != 0 && msg.value == 0)
|| (allocations.liquidityAllocation == 0 && msg.value > 0)
) {
revert InvalidLiquidityConfig();
}

tokenAddress =
LibClone.cloneDeterministic(implementation, keccak256(abi.encodePacked(name)));
(bool success,) = tokenAddress.call{value: msg.value}(
abi.encodeWithSelector(
DN404Cloneable.initialize.selector,
name,
sym,
allocations,
totalSupply,
liquidityLockPeriodInSeconds,
addresses,
amounts
)
);

if (!success) revert FailedToInitialize();
}
}
137 changes: 137 additions & 0 deletions src/example/DN404Cloneable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "../DN404.sol";
import "../DN404Mirror.sol";
import {Ownable} from "../../lib/solady/src/auth/Ownable.sol";
import {LibString} from "../../lib/solady/src/utils/LibString.sol";
import {SafeTransferLib} from "../../lib/solady/src/utils/SafeTransferLib.sol";
import {Clone} from "../../lib/solady/src/utils/Clone.sol";
import {IERC20} from "../../lib/forge-std/src/interfaces/IERC20.sol";

contract DN404Cloneable is DN404, Ownable, Clone {
error LiquidityLocked();
error UnableToGetPair();
error UnableToWithdraw();
error InvalidAirdropConfig();
error FailedToProvideLiquidity();

LiquidityDetails public liquidityDetails;

address private constant UNISWAP_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
address private constant UNISWAP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

string private _name;
string private _sym;
string private _baseURI;
bool private initialized = true;

struct Allocations {
uint80 liquidityAllocation;
uint80 teamAllocation;
uint80 airdropAllocation;
}

struct LiquidityDetails {
uint128 liquidityUnlockTimestamp;
uint128 liquidityAllocation;
}

function initialize(
string calldata name_,
string calldata sym_,
Allocations calldata allocations,
uint96 initialTokenSupply,
uint256 liquidityLockInSeconds,
address[] calldata addresses,
uint256[] calldata amounts
) external payable {
if (initialized) revert();
initialized = true;

uint80 teamAllocation = allocations.teamAllocation;
uint80 airdropAllocation = allocations.airdropAllocation;
uint80 liquidityAllocation = allocations.liquidityAllocation;
if (liquidityAllocation + teamAllocation + airdropAllocation != initialTokenSupply) {
revert();
}

_initializeOwner(tx.origin);
_name = name_;
_sym = sym_;

address mirror = address(new DN404Mirror(msg.sender));

_initializeDN404(uint96(teamAllocation), tx.origin, mirror);

uint256 supplyBefore = totalSupply();
for (uint256 i = 0; i < addresses.length; ++i) {
_mint(addresses[i], amounts[i]);
}
uint256 supplyAfter = totalSupply();
if (supplyAfter - supplyBefore != airdropAllocation) revert InvalidAirdropConfig();

if (liquidityAllocation > 0) {
liquidityDetails = LiquidityDetails({
liquidityUnlockTimestamp: uint128(block.timestamp + liquidityLockInSeconds),
liquidityAllocation: liquidityAllocation
});

_mint(address(this), liquidityAllocation);
_approve(address(this), UNISWAP_ROUTER, liquidityAllocation);

address liquidityRecipient = liquidityLockInSeconds == 0 ? tx.origin : address(this);

(bool success,) = UNISWAP_ROUTER.call{value: msg.value}(
abi.encodeWithSelector(
0xf305d719,
address(this),
liquidityAllocation,
0,
0,
liquidityRecipient,
block.timestamp
)
);

if (!success) revert FailedToProvideLiquidity();
}
}

function name() public view override returns (string memory) {
return _name;
}

function symbol() public view override returns (string memory) {
return _sym;
}

function setBaseURI(string calldata baseURI_) public onlyOwner {
_baseURI = baseURI_;
}

function tokenURI(uint256 tokenId) public view override returns (string memory) {
return bytes(_baseURI).length != 0
? string(abi.encodePacked(_baseURI, LibString.toString(tokenId)))
: "";
}

function withdraw() public onlyOwner {
SafeTransferLib.safeTransferAllETH(msg.sender);
}

function withdrawLP() external onlyOwner {
if (block.timestamp < liquidityDetails.liquidityUnlockTimestamp) revert LiquidityLocked();

(bool success, bytes memory result) = UNISWAP_FACTORY.staticcall(
abi.encodeWithSignature("getPair(address,address)", address(this), WETH)
);
if (!success) revert UnableToGetPair();
address pair = abi.decode(result, (address));
uint256 balance = IERC20(pair).balanceOf(address(this));
(success,) = pair.call(abi.encodeWithSelector(0xa9059cbb, owner(), balance));

if (!success) revert UnableToWithdraw();
}
}
Loading