Skip to content

Commit 649c138

Browse files
committed
Create: DNFactory
1 parent 6bfb635 commit 649c138

5 files changed

Lines changed: 515 additions & 22 deletions

File tree

.gas-snapshot

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,56 +72,64 @@ DN404MirrorTest:testNameAndSymbol(string,string) (runs: 256, μ: 207680, ~: 2080
7272
DN404MirrorTest:testNotLinked() (gas: 12767)
7373
DN404MirrorTest:testPullOwner() (gas: 112438)
7474
DN404MirrorTest:testPullOwnerWithOwnable() (gas: 2311110)
75-
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 256, μ: 467160, ~: 467149)
75+
DN404MirrorTest:testSafeTransferFrom(uint32) (runs: 256, μ: 467158, ~: 467149)
7676
DN404MirrorTest:testSetAndGetApprovalForAll() (gas: 324805)
7777
DN404MirrorTest:testSetAndGetApproved() (gas: 318081)
7878
DN404MirrorTest:testSupportsInterface() (gas: 7566)
7979
DN404MirrorTest:testTokenURI(string,uint256) (runs: 256, μ: 158169, ~: 135868)
80-
DN404MirrorTest:testTransferFrom(uint32) (runs: 256, μ: 342276, ~: 342270)
80+
DN404MirrorTest:testTransferFrom(uint32) (runs: 256, μ: 342281, ~: 342270)
8181
DN404MirrorTest:test__codesize() (gas: 41539)
8282
DN404OnlyERC20Test:testApprove() (gas: 35902)
8383
DN404OnlyERC20Test:testApprove(address,uint256) (runs: 256, μ: 30209, ~: 31453)
8484
DN404OnlyERC20Test:testBurn() (gas: 49596)
85-
DN404OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 256, μ: 50674, ~: 50797)
86-
DN404OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 43711, ~: 43790)
85+
DN404OnlyERC20Test:testBurn(address,uint256,uint256) (runs: 256, μ: 50675, ~: 50797)
86+
DN404OnlyERC20Test:testBurnInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 43715, ~: 43790)
8787
DN404OnlyERC20Test:testInfiniteApproveTransferFrom() (gas: 101775)
8888
DN404OnlyERC20Test:testMaxSupplyTrick(uint256) (runs: 256, μ: 541, ~: 541)
8989
DN404OnlyERC20Test:testMetadata() (gas: 10111)
9090
DN404OnlyERC20Test:testMint() (gas: 45215)
9191
DN404OnlyERC20Test:testMintOverMaxLimitReverts() (gas: 40430)
92-
DN404OnlyERC20Test:testMintz(address,uint256) (runs: 256, μ: 45617, ~: 45637)
92+
DN404OnlyERC20Test:testMintz(address,uint256) (runs: 256, μ: 45615, ~: 45637)
9393
DN404OnlyERC20Test:testTransfer() (gas: 74333)
94-
DN404OnlyERC20Test:testTransfer(address,uint256) (runs: 256, μ: 74664, ~: 74790)
94+
DN404OnlyERC20Test:testTransfer(address,uint256) (runs: 256, μ: 74667, ~: 74790)
9595
DN404OnlyERC20Test:testTransferFrom() (gas: 84233)
96-
DN404OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 256, μ: 104928, ~: 107202)
96+
DN404OnlyERC20Test:testTransferFrom(address,address,address,uint256,uint256) (runs: 256, μ: 105186, ~: 107202)
9797
DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts() (gas: 67992)
98-
DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 256, μ: 68600, ~: 69097)
98+
DN404OnlyERC20Test:testTransferFromInsufficientAllowanceReverts(address,uint256,uint256) (runs: 256, μ: 68514, ~: 69093)
9999
DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts() (gas: 74747)
100-
DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 75913, ~: 75868)
100+
DN404OnlyERC20Test:testTransferFromInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 76057, ~: 75868)
101101
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts() (gas: 66158)
102-
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 67281, ~: 67309)
102+
DN404OnlyERC20Test:testTransferInsufficientBalanceReverts(address,uint256,uint256) (runs: 256, μ: 67202, ~: 67245)
103103
DN404OnlyERC20Test:test__codesize() (gas: 29126)
104104
DN404Test:testBatchNFTLog() (gas: 305204)
105-
DN404Test:testBurnOnTransfer(uint32,address) (runs: 256, μ: 264588, ~: 264588)
106-
DN404Test:testInitialize(uint32,address) (runs: 256, μ: 112778, ~: 116550)
105+
DN404Test:testBurnOnTransfer(uint32,address) (runs: 256, μ: 264578, ~: 264588)
106+
DN404Test:testInitialize(uint32,address) (runs: 256, μ: 112886, ~: 116550)
107107
DN404Test:testMintAndBurn() (gas: 336791)
108108
DN404Test:testMintAndBurn2() (gas: 262937)
109109
DN404Test:testMintOnTransfer(uint32,address) (runs: 256, μ: 263389, ~: 263389)
110-
DN404Test:testMixed(uint256) (runs: 256, μ: 590658, ~: 556911)
110+
DN404Test:testMixed(uint256) (runs: 256, μ: 597622, ~: 566489)
111111
DN404Test:testNameAndSymbol(string,string) (runs: 256, μ: 207353, ~: 207694)
112-
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 126946, ~: 127063)
113-
DN404Test:testSetAndGetAux(address,uint88) (runs: 256, μ: 21968, ~: 22275)
112+
DN404Test:testRegisterAndResolveAlias(address,address) (runs: 256, μ: 126810, ~: 127063)
113+
DN404Test:testSetAndGetAux(address,uint88) (runs: 256, μ: 21967, ~: 22275)
114114
DN404Test:testSetAndGetOperatorApprovals(address,address,bool) (runs: 256, μ: 129897, ~: 120958)
115115
DN404Test:testSetAndGetSkipNFT() (gas: 89209)
116116
DN404Test:testTokenURI(string,uint256) (runs: 256, μ: 158057, ~: 135756)
117117
DN404Test:testTransfersAndBurns() (gas: 447607)
118-
DN404Test:testWrapAround(uint32,uint256) (runs: 256, μ: 351700, ~: 343816)
118+
DN404Test:testWrapAround(uint32,uint256) (runs: 256, μ: 350776, ~: 343633)
119119
DN404Test:test__codesize() (gas: 40128)
120-
MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 256, μ: 59039, ~: 59136)
121-
MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 256, μ: 86005, ~: 86006)
122-
MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 256, μ: 31109, ~: 31169)
123-
MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 256, μ: 83015, ~: 83084)
124-
MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 256, μ: 89503, ~: 89601)
120+
DNFactoryTest:testDeploy() (gas: 5527267)
121+
DNFactoryTest:testRevert_ArrayLengthMismatch() (gas: 545696)
122+
DNFactoryTest:testRevert_InvalidAirdropConfig() (gas: 535706)
123+
DNFactoryTest:testRevert_InvalidAllocations() (gas: 538848)
124+
DNFactoryTest:testRevert_InvalidLiquidityConfig() (gas: 538618)
125+
DNFactoryTest:testRevert_LiquidityLocked() (gas: 5482815)
126+
DNFactoryTest:testWithdrawLP() (gas: 5490877)
127+
DNFactoryTest:test__codesize() (gas: 26699)
128+
MintTests:test_WhenAmountIsGreaterThan_MAX_SUPPLYOrMintMakesNFTTotalSupplyExceed_MAX_SUPPLY(uint256) (runs: 256, μ: 59074, ~: 59143)
129+
MintTests:test_WhenRecipientAddressHasSkipNFTEnabled(uint256) (runs: 256, μ: 85997, ~: 86006)
130+
MintTests:test_WhenRecipientIsAddress0(uint256) (runs: 256, μ: 31106, ~: 31169)
131+
MintTests:test_WhenRecipientsBalanceDifferenceIsNotUpTo1e18(uint256) (runs: 256, μ: 83002, ~: 83084)
132+
MintTests:test_WhenRecipientsBalanceDifferenceIsUpTo1e18OrAbove(uint256) (runs: 256, μ: 89490, ~: 89602)
125133
MintTests:test__codesize() (gas: 21744)
126134
NFTMintDN404Test:testAllowlistMint() (gas: 286079)
127135
NFTMintDN404Test:testMint() (gas: 246258)

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)