diff --git a/bun.lockb b/bun.lockb index 86d5c93..78859c8 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 80569b6..a0be56a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "url": "https://github.com/PaulRBerg" }, "dependencies": { - "@openzeppelin/contracts": "^5.0.1" + "@openzeppelin/contracts": "^5.0.1", + "@openzeppelin/contracts-upgradeable": "^5.0.2" }, "devDependencies": { "forge-std": "github:foundry-rs/forge-std#v1.8.1", diff --git a/src/Week3/ERC20Factory.sol b/src/Week3/ERC20Factory.sol new file mode 100644 index 0000000..11764be --- /dev/null +++ b/src/Week3/ERC20Factory.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { MyERC20 } from "./MyERC20.sol"; + +event SendETH(bool, bytes); + +contract ERC20Factory { + address targetAddress; + + uint256 price; + + struct ProjectHolder { + uint256 perMint; + address managerAddress; + uint256 price; + uint256 totalSupply; + } + + mapping(address => ProjectHolder) tokenMappings; + + constructor(address _target) { + targetAddress = _target; + } + + function deployInscription( + string memory symbol, + uint256 totalSupply, + uint256 perMint, + uint256 _price + ) + public + returns (address) + { + address clone = createClone(targetAddress); + MyERC20(clone).initialize("meme", symbol, totalSupply); + tokenMappings[clone] = + ProjectHolder({ perMint: perMint, managerAddress: msg.sender, price: _price, totalSupply: totalSupply }); + + return clone; + } + + function mintInscription(address tokenAddr) public payable { + uint256 received = msg.value; + require(received >= tokenMappings[tokenAddr].price); + ProjectHolder memory projectHolder = tokenMappings[tokenAddr]; + (bool sent, bytes memory data) = projectHolder.managerAddress.call{ value: projectHolder.price / 2 }(""); + emit SendETH(sent, data); + MyERC20(tokenAddr).transfer(msg.sender, projectHolder.perMint); + } + + function init(string memory symbol, uint256 totalSupply) public returns (address) { + address clone = createClone(targetAddress); + MyERC20(clone).initialize("meme", symbol, totalSupply); + return clone; + } + + function createClone(address target) internal returns (address result) { + bytes20 targetBytes = bytes20(target); + assembly { + let clone := mload(0x40) + mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(clone, 0x14), targetBytes) + mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + result := create(0, clone, 0x37) + } + } +} diff --git a/src/Week3/MyERC20.sol b/src/Week3/MyERC20.sol new file mode 100644 index 0000000..ec6f2b9 --- /dev/null +++ b/src/Week3/MyERC20.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +// import { Initializable } from "@openzeppelin/contracts/contracts/Initializable.sol"; + +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +// import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +// import { ERC20Detailed } from "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol"; + +contract MyERC20 is Initializable, ERC20Upgradeable { + function initialize(string memory name, string memory symbol, uint256 initialSupply) public initializer { + __ERC20_init(name, symbol); + _mint(_msgSender(), initialSupply); + } +} diff --git a/test/ERC20Factory.t.sol b/test/ERC20Factory.t.sol new file mode 100644 index 0000000..0151bf6 --- /dev/null +++ b/test/ERC20Factory.t.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: SEE LICENSE IN LICENSE +pragma solidity ^0.8.25; + +import { Test, console2 } from "forge-std/src/Test.sol"; +// import { NFTMarket, BaseERC20, TokenAccessError, ListNFT, InvalidAddressError } from "../src/Week2/NFTMarket.sol"; +// import { MyERC2612 } from "../src/Week3/MyERC2612.sol"; +// import { TokenBank, BaseERC20 } from "../src/Week2/BaseERC20.sol"; +import { MyERC20 } from "../src/Week3/MyERC20.sol"; +import { ERC20Factory } from "../src/Week3/ERC20Factory.sol"; + +// import { SigUtils } from "./SigUtils.sol"; +// import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +// import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + +contract ERC20FactoryTest is Test { + ERC20Factory erc20Factory; + MyERC20 myERC20; + + address alice = makeAddr("alice"); + address bob = makeAddr("bob"); + address address1 = makeAddr("address1"); + address address2 = makeAddr("address2"); + + function setUp() public { + myERC20 = new MyERC20(); + erc20Factory = new ERC20Factory(address(myERC20)); + } + + function test_init() public { + // newErc20.init("hh", "hh", 1e18); + address clone = erc20Factory.init("hh", 1e18); + MyERC20 newErc20 = MyERC20(clone); + + assertEq(newErc20.symbol(), "hh"); + assertEq(newErc20.decimals(), 18); + } + + function test_deployInscription() public { + vm.prank(alice); + + address tokenAddress = erc20Factory.deployInscription("hh", 1e18, 1e5, 1e5); + assertEq(MyERC20(tokenAddress).balanceOf(address(erc20Factory)), 1e18); + uint256 balanceAliceBeforeMint = alice.balance; + uint256 balanceFactoryBeforeMint = address(erc20Factory).balance; + + vm.deal(bob, 1 ether); + vm.prank(bob); + // bob mint once, want to get 1e5 token + erc20Factory.mintInscription{ value: 1e5 }(tokenAddress); + + assertEq(MyERC20(tokenAddress).balanceOf(address(bob)), 1e5); + assertEq(alice.balance, balanceAliceBeforeMint + 1e5 / 2); // token manager alice get 1e5 wei + assertEq(address(erc20Factory).balance, balanceFactoryBeforeMint + 1e5 / 2); // factory get 1e5 wei + + vm.prank(address1); + address anotherTokenAddress = erc20Factory.deployInscription("gg", 1e18, 1e6, 1e6); + assertEq(MyERC20(anotherTokenAddress).balanceOf(address(erc20Factory)), 1e18); + + balanceFactoryBeforeMint = address(erc20Factory).balance; + vm.deal(address2, 1 ether); + vm.prank(address2); + erc20Factory.mintInscription{ value: 1e6 }(anotherTokenAddress); + assertEq(MyERC20(anotherTokenAddress).balanceOf(address(address2)), 1e6); + + assertEq(address1.balance, 1e6 / 2); // token manager alice get 1e5 wei + assertEq(address(erc20Factory).balance, balanceFactoryBeforeMint + 1e6 / 2); // factory get 1e5 wei + } + + function createClone(address target) internal returns (address result) { + bytes20 targetBytes = bytes20(target); + assembly { + let clone := mload(0x40) + mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) + mstore(add(clone, 0x14), targetBytes) + mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) + result := create(0, clone, 0x37) + } + } +}