diff --git a/.gas-snapshot b/.gas-snapshot index d1865c7..5e21430 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -65,6 +65,11 @@ BenchTest:testMintPandora_14() (gas: 1037484) BenchTest:testMintPandora_15() (gas: 1106967) BenchTest:testMintPandora_16() (gas: 1176473) BenchTest:test__codesize() (gas: 29290) +DN404CloneableTest:testMint() (gas: 54089) +DN404CloneableTest:testName() (gas: 12348) +DN404CloneableTest:testSetBaseURI() (gas: 41446) +DN404CloneableTest:testSymbol() (gas: 12368) +DN404CloneableTest:test__codesize() (gas: 4926) DN404CustomUnitTest:testInitializeWithZeroUnitReverts() (gas: 85821) DN404CustomUnitTest:testMint() (gas: 162679) DN404CustomUnitTest:testMintWithoutNFTs(uint256,uint256,uint256) (runs: 256, μ: 155067, ~: 162175) diff --git a/src/example/DN404Cloneable.sol b/src/example/DN404Cloneable.sol new file mode 100644 index 0000000..e332fd2 --- /dev/null +++ b/src/example/DN404Cloneable.sol @@ -0,0 +1,81 @@ +// 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 {Initializable} from "../../lib/solady/src/utils/Initializable.sol"; +import {LibClone} from "../../lib/solady/src/utils/LibClone.sol"; +import {IERC20} from "../../lib/forge-std/src/interfaces/IERC20.sol"; + +/** + * @title DN404Cloneable + * @notice Simple DN404 contract that allows clones of the contract to be created. + * Both DN404 Base and DN404Mirror are created as EIP 1167 clones. + */ +contract DN404Cloneable is DN404, Ownable, Initializable { + error MaxTokenSupplyExceeded(); + + string private _name; + string private _sym; + string private _baseURI; + + uint256 private _maxTokenSupply; + + // Immutable so clones don't inherit this and proxy calls still read the right value. + address private immutable dn404mirrorImpl = address(new DN404Mirror(address(this))); + + function initialize( + address owner_, + string calldata name_, + string calldata symbol_, + string calldata baseURI_, + address initialMintTarget_, + uint256 initialTokenSupply_, + uint256 maxTokenSupply_ + ) external payable initializer { + _initializeOwner(owner_); + + _name = name_; + _sym = symbol_; + _baseURI = baseURI_; + _maxTokenSupply = maxTokenSupply_; + + // We don't care about the constructor since address(0) is acceptable. + address mirror = LibClone.clone(dn404mirrorImpl); + + _initializeDN404(initialTokenSupply_, initialMintTarget_, mirror); + } + + 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 mint(address to, uint256 amount) public onlyOwner { + if (amount + totalSupply() > _maxTokenSupply) { + revert MaxTokenSupplyExceeded(); + } + + _mint(to, amount); + } + + 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); + } +} diff --git a/test/DN404Cloneable.t.sol b/test/DN404Cloneable.t.sol new file mode 100644 index 0000000..e1d3c41 --- /dev/null +++ b/test/DN404Cloneable.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "./utils/SoladyTest.sol"; +import {LibClone} from "../lib/solady/src/utils/LibClone.sol"; +import {DN404Cloneable} from "../src/example/DN404Cloneable.sol"; + +contract DN404CloneableTest is SoladyTest { + address immutable dnImpl = address(new DN404Cloneable()); + + DN404Cloneable dn; + address alice = address(111); + + function setUp() public { + dn = DN404Cloneable(payable(LibClone.clone(dnImpl))); + + vm.prank(alice); + dn.initialize(alice, "DN404", "DN", "", address(this), 1000, 2000); + } + + function testMint() public { + vm.prank(dn.owner()); + dn.mint(alice, 100); + assertEq(dn.totalSupply(), 1100); + + vm.prank(dn.owner()); + vm.expectRevert(); + dn.mint(alice, 1000); + } + + function testName() public { + assertEq(dn.name(), "DN404"); + } + + function testSymbol() public { + assertEq(dn.symbol(), "DN"); + } + + function testSetBaseURI() public { + vm.prank(alice); + dn.setBaseURI("https://example.com/"); + assertEq(dn.tokenURI(1), "https://example.com/1"); + } +}