Skip to content

Commit 638c510

Browse files
committed
Create: DNFactory
1 parent f2d62e4 commit 638c510

5 files changed

Lines changed: 496 additions & 26 deletions

File tree

.gas-snapshot

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ BenchTest:testMintPandora_16() (gas: 1176473)
6767
BenchTest:test__codesize() (gas: 29290)
6868
DN404CustomUnitTest:testInitializeWithZeroUnitReverts() (gas: 85821)
6969
DN404CustomUnitTest:testMint() (gas: 162679)
70-
DN404CustomUnitTest:testMintWithoutNFTs(uint256,uint256,uint256) (runs: 256, μ: 155067, ~: 162175)
70+
DN404CustomUnitTest:testMintWithoutNFTs(uint256,uint256,uint256) (runs: 256, μ: 156145, ~: 162184)
7171
DN404CustomUnitTest:testNFTMint() (gas: 56916373)
72-
DN404CustomUnitTest:testNFTMintAndBurn(uint256,uint256,uint256) (runs: 256, μ: 200350, ~: 161045)
73-
DN404CustomUnitTest:testNFTMintViaTransfer(uint256,uint256,uint256) (runs: 256, μ: 211152, ~: 235934)
74-
DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256,uint256) (runs: 256, μ: 1053, ~: 1147)
72+
DN404CustomUnitTest:testNFTMintAndBurn(uint256,uint256,uint256) (runs: 256, μ: 210189, ~: 161083)
73+
DN404CustomUnitTest:testNFTMintViaTransfer(uint256,uint256,uint256) (runs: 256, μ: 215396, ~: 238870)
74+
DN404CustomUnitTest:testTotalSupplyOverflowsTrick(uint256,uint256,uint256) (runs: 256, μ: 1055, ~: 1147)
7575
DN404CustomUnitTest:test__codesize() (gas: 22937)
7676
DN404MirrorTest:testBaseERC20() (gas: 114632)
7777
DN404MirrorTest:testLinkMirrorContract() (gas: 45829)
@@ -80,56 +80,64 @@ DN404MirrorTest:testNameAndSymbol(string,string) (runs: 256, μ: 207712, ~: 2080
8080
DN404MirrorTest:testNotLinked() (gas: 12767)
8181
DN404MirrorTest:testPullOwner() (gas: 112470)
8282
DN404MirrorTest:testPullOwnerWithOwnable() (gas: 2331186)
83-
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 256, μ: 467433, ~: 467421)
83+
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 256, μ: 467429, ~: 467421)
8484
DN404MirrorTest:testSetAndGetApprovalForAll() (gas: 325041)
8585
DN404MirrorTest:testSetAndGetApproved() (gas: 318317)
8686
DN404MirrorTest:testSupportsInterface() (gas: 7566)
8787
DN404MirrorTest:testTokenURI(string,uint256) (runs: 256, μ: 158201, ~: 135900)
88-
DN404MirrorTest:testTransferFrom(uint32) (runs: 256, μ: 342514, ~: 342506)
88+
DN404MirrorTest:testTransferFrom(uint32) (runs: 256, μ: 342513, ~: 342506)
8989
DN404MirrorTest:test__codesize() (gas: 41739)
9090
DN404OnlyERC20Test:testApprove() (gas: 35902)
9191
DN404OnlyERC20Test:testApprove(address,uint256) (runs: 256, μ: 30209, ~: 31453)
9292
DN404OnlyERC20Test:testBurn() (gas: 49717)
93-
DN404OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 256, μ: 50773, ~: 50918)
94-
DN404OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 43761, ~: 43846)
93+
DN404OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 256, μ: 50959, ~: 50918)
94+
DN404OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 43866, ~: 43855)
9595
DN404OnlyERC20Test:testInfiniteApproveTransferFrom() (gas: 101928)
9696
DN404OnlyERC20Test:testMaxSupplyTrick(uint256) (runs: 256, μ: 541, ~: 541)
9797
DN404OnlyERC20Test:testMetadata() (gas: 10111)
9898
DN404OnlyERC20Test:testMint() (gas: 45292)
9999
DN404OnlyERC20Test:testMintOverMaxLimitReverts() (gas: 40524)
100-
DN404OnlyERC20Test:testMintz(address,uint256) (runs: 256, μ: 45601, ~: 45714)
100+
DN404OnlyERC20Test:testMintz(address,uint256) (runs: 256, μ: 45608, ~: 45714)
101101
DN404OnlyERC20Test:testTransfer() (gas: 74486)
102-
DN404OnlyERC20Test:testTransfer(address,uint256) (runs: 256, μ: 74892, ~: 74943)
102+
DN404OnlyERC20Test:testTransfer(address,uint256) (runs: 256, μ: 74931, ~: 74943)
103103
DN404OnlyERC20Test:testTransferFrom() (gas: 84386)
104-
DN404OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 256, μ: 105342, ~: 107367)
104+
DN404OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 256, μ: 105413, ~: 107367)
105105
DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts() (gas: 68045)
106-
DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 256, μ: 68736, ~: 69150)
106+
DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 256, μ: 68569, ~: 69155)
107107
DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts() (gas: 74812)
108-
DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 75972, ~: 75933)
108+
DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 75872, ~: 75942)
109109
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts() (gas: 66223)
110-
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 67258, ~: 67314)
110+
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 67261, ~: 67319)
111111
DN404OnlyERC20Test:test__codesize() (gas: 29166)
112112
DN404Test:testBatchNFTLog() (gas: 305444)
113113
DN404Test:testBurnOnTransfer(uint32,address) (runs: 256, μ: 264828, ~: 264828)
114-
DN404Test:testInitialize(uint32,address) (runs: 256, μ: 112926, ~: 116582)
114+
DN404Test:testInitialize(uint32,address) (runs: 256, μ: 113846, ~: 116582)
115115
DN404Test:testMintAndBurn() (gas: 336949)
116116
DN404Test:testMintAndBurn2() (gas: 263115)
117-
DN404Test:testMintOnTransfer(uint32,address) (runs: 256, μ: 263589, ~: 263589)
118-
DN404Test:testMixed(uint256) (runs: 256, μ: 597598, ~: 559587)
117+
DN404Test:testMintOnTransfer(uint32,address) (runs: 256, μ: 263569, ~: 263589)
118+
DN404Test:testMixed(uint256) (runs: 256, μ: 614158, ~: 591663)
119119
DN404Test:testNameAndSymbol(string,string) (runs: 256, μ: 207385, ~: 207726)
120-
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 126897, ~: 127063)
121-
DN404Test:testSetAndGetAux(address,uint88) (runs: 256, μ: 21968, ~: 22275)
120+
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 126985, ~: 127063)
121+
DN404Test:testSetAndGetAux(address,uint88) (runs: 256, μ: 22003, ~: 22275)
122122
DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 256, μ: 129929, ~: 120990)
123123
DN404Test:testSetAndGetSkipNFT() (gas: 89209)
124124
DN404Test:testTokenURI(string,uint256) (runs: 256, μ: 158089, ~: 135788)
125125
DN404Test:testTransfersAndBurns() (gas: 448183)
126-
DN404Test:testWrapAround(uint32,uint256) (runs: 256, μ: 351005, ~: 344424)
126+
DN404Test:testWrapAround(uint32,uint256) (runs: 256, μ: 351664, ~: 344424)
127127
DN404Test:test__codesize() (gas: 40228)
128-
MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 256, μ: 59079, ~: 59172)
129-
MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 256, μ: 86042, ~: 86035)
130-
MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 256, μ: 31106, ~: 31169)
131-
MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 256, μ: 83052, ~: 83129)
132-
MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 256, μ: 89595, ~: 89647)
128+
DNFactoryTest:testDeploy() (gas: 5768151)
129+
DNFactoryTest:testRevert_ArrayLengthMismatch() (gas: 545403)
130+
DNFactoryTest:testRevert_InvalidAirdropConfig() (gas: 535403)
131+
DNFactoryTest:testRevert_InvalidAllocations() (gas: 538545)
132+
DNFactoryTest:testRevert_InvalidLiquidityConfig() (gas: 538315)
133+
DNFactoryTest:testRevert_LiquidityLocked() (gas: 5723699)
134+
DNFactoryTest:testWithdrawLP() (gas: 5731761)
135+
DNFactoryTest:test__codesize() (gas: 26742)
136+
MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 256, μ: 59093, ~: 59168)
137+
MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 256, μ: 86020, ~: 86035)
138+
MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 256, μ: 31101, ~: 31169)
139+
MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 256, μ: 83021, ~: 83130)
140+
MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 256, μ: 89597, ~: 89648)
133141
MintTests:test__codesize() (gas: 21844)
134142
NFTMintDN404Test:testAllowlistMint() (gas: 286143)
135143
NFTMintDN404Test:testMint() (gas: 246322)

foundry.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,7 @@ runs = 256
2626
runs = 30
2727
depth = 15
2828
fail_on_revert = true
29-
dictionary_weight = 80
29+
dictionary_weight = 80
30+
31+
[rpc_endpoints]
32+
mainnet = '${ETH_RPC_URL}'

src/DNFactory.sol

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.4;
3+
4+
import {LibClone} from "solady/utils/LibClone.sol";
5+
import {DN404Cloneable} from "./example/DN404Cloneable.sol";
6+
7+
contract DNFactory {
8+
error FailedToInitialize();
9+
error ArrayLengthMismatch();
10+
error InvalidLiquidityConfig();
11+
error InvalidAllocations();
12+
error EtherProvidedForZeroLiquidity();
13+
error InvalidAirdropConfig();
14+
15+
address public immutable implementation;
16+
17+
struct Allocations {
18+
uint80 liquidityAllocation;
19+
uint80 teamAllocation;
20+
uint80 airdropAllocation;
21+
}
22+
23+
constructor() {
24+
DN404Cloneable dn = new DN404Cloneable();
25+
implementation = address(dn);
26+
}
27+
28+
function deployDN(
29+
string calldata name,
30+
string calldata sym,
31+
Allocations calldata allocations,
32+
uint96 totalSupply,
33+
uint256 liquidityLockPeriodInSeconds,
34+
address[] calldata addresses,
35+
uint256[] calldata amounts
36+
) external payable returns (address tokenAddress) {
37+
if (
38+
allocations.liquidityAllocation + allocations.teamAllocation
39+
+ allocations.airdropAllocation != totalSupply
40+
) {
41+
revert InvalidAllocations();
42+
}
43+
if (addresses.length != amounts.length) revert ArrayLengthMismatch();
44+
if (
45+
(addresses.length == 0 && allocations.airdropAllocation > 0)
46+
|| (addresses.length != 0 && allocations.airdropAllocation == 0)
47+
) revert InvalidAirdropConfig();
48+
if (
49+
(allocations.liquidityAllocation != 0 && msg.value == 0)
50+
|| (allocations.liquidityAllocation == 0 && msg.value > 0)
51+
) {
52+
revert InvalidLiquidityConfig();
53+
}
54+
55+
tokenAddress =
56+
LibClone.cloneDeterministic(implementation, keccak256(abi.encodePacked(name)));
57+
(bool success,) = tokenAddress.call{value: msg.value}(
58+
abi.encodeWithSelector(
59+
DN404Cloneable.initialize.selector,
60+
name,
61+
sym,
62+
allocations,
63+
totalSupply,
64+
liquidityLockPeriodInSeconds,
65+
addresses,
66+
amounts
67+
)
68+
);
69+
70+
if (!success) revert FailedToInitialize();
71+
}
72+
}

src/example/DN404Cloneable.sol

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.4;
3+
4+
import "../DN404.sol";
5+
import "../DN404Mirror.sol";
6+
import {Ownable} from "../../lib/solady/src/auth/Ownable.sol";
7+
import {LibString} from "../../lib/solady/src/utils/LibString.sol";
8+
import {SafeTransferLib} from "../../lib/solady/src/utils/SafeTransferLib.sol";
9+
import {Clone} from "../../lib/solady/src/utils/Clone.sol";
10+
import {IERC20} from "../../lib/forge-std/src/interfaces/IERC20.sol";
11+
12+
contract DN404Cloneable is DN404, Ownable, Clone {
13+
error LiquidityLocked();
14+
error UnableToGetPair();
15+
error UnableToWithdraw();
16+
error InvalidAirdropConfig();
17+
error FailedToProvideLiquidity();
18+
19+
LiquidityDetails public liquidityDetails;
20+
21+
address private constant UNISWAP_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
22+
address private constant UNISWAP_FACTORY = 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f;
23+
address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
24+
25+
string private _name;
26+
string private _sym;
27+
string private _baseURI;
28+
bool private initialized = true;
29+
30+
struct Allocations {
31+
uint80 liquidityAllocation;
32+
uint80 teamAllocation;
33+
uint80 airdropAllocation;
34+
}
35+
36+
struct LiquidityDetails {
37+
uint128 liquidityUnlockTimestamp;
38+
uint128 liquidityAllocation;
39+
}
40+
41+
function initialize(
42+
string calldata name_,
43+
string calldata sym_,
44+
Allocations calldata allocations,
45+
uint96 initialTokenSupply,
46+
uint256 liquidityLockInSeconds,
47+
address[] calldata addresses,
48+
uint256[] calldata amounts
49+
) external payable {
50+
if (initialized) revert();
51+
initialized = true;
52+
53+
uint80 teamAllocation = allocations.teamAllocation;
54+
uint80 airdropAllocation = allocations.airdropAllocation;
55+
uint80 liquidityAllocation = allocations.liquidityAllocation;
56+
if (liquidityAllocation + teamAllocation + airdropAllocation != initialTokenSupply) {
57+
revert();
58+
}
59+
60+
_initializeOwner(tx.origin);
61+
_name = name_;
62+
_sym = sym_;
63+
64+
address mirror = address(new DN404Mirror(msg.sender));
65+
66+
_initializeDN404(uint96(teamAllocation), tx.origin, mirror);
67+
68+
uint256 supplyBefore = totalSupply();
69+
for (uint256 i = 0; i < addresses.length; ++i) {
70+
_mint(addresses[i], amounts[i]);
71+
}
72+
uint256 supplyAfter = totalSupply();
73+
if (supplyAfter - supplyBefore != airdropAllocation) revert InvalidAirdropConfig();
74+
75+
if (liquidityAllocation > 0) {
76+
liquidityDetails = LiquidityDetails({
77+
liquidityUnlockTimestamp: uint128(block.timestamp + liquidityLockInSeconds),
78+
liquidityAllocation: liquidityAllocation
79+
});
80+
81+
_mint(address(this), liquidityAllocation);
82+
_approve(address(this), UNISWAP_ROUTER, liquidityAllocation);
83+
84+
address liquidityRecipient = liquidityLockInSeconds == 0 ? tx.origin : address(this);
85+
86+
(bool success,) = UNISWAP_ROUTER.call{value: msg.value}(
87+
abi.encodeWithSelector(
88+
0xf305d719,
89+
address(this),
90+
liquidityAllocation,
91+
0,
92+
0,
93+
liquidityRecipient,
94+
block.timestamp
95+
)
96+
);
97+
98+
if (!success) revert FailedToProvideLiquidity();
99+
}
100+
}
101+
102+
function name() public view override returns (string memory) {
103+
return _name;
104+
}
105+
106+
function symbol() public view override returns (string memory) {
107+
return _sym;
108+
}
109+
110+
function setBaseURI(string calldata baseURI_) public onlyOwner {
111+
_baseURI = baseURI_;
112+
}
113+
114+
function tokenURI(uint256 tokenId) public view override returns (string memory) {
115+
return bytes(_baseURI).length != 0
116+
? string(abi.encodePacked(_baseURI, LibString.toString(tokenId)))
117+
: "";
118+
}
119+
120+
function withdraw() public onlyOwner {
121+
SafeTransferLib.safeTransferAllETH(msg.sender);
122+
}
123+
124+
function withdrawLP() external onlyOwner {
125+
if (block.timestamp < liquidityDetails.liquidityUnlockTimestamp) revert LiquidityLocked();
126+
127+
(bool success, bytes memory result) = UNISWAP_FACTORY.staticcall(
128+
abi.encodeWithSignature("getPair(address,address)", address(this), WETH)
129+
);
130+
if (!success) revert UnableToGetPair();
131+
address pair = abi.decode(result, (address));
132+
uint256 balance = IERC20(pair).balanceOf(address(this));
133+
(success,) = pair.call(abi.encodeWithSelector(0xa9059cbb, owner(), balance));
134+
135+
if (!success) revert UnableToWithdraw();
136+
}
137+
}

0 commit comments

Comments
 (0)