Foundry高级实战:实现一个可升级的工厂合约
挑战以铸币工厂为例, 理解可升级合约的编写及最小代理如何节省 Gas 。
实现⼀个可升级的工厂合约,工厂合约有两个方法:
deployInscription(string symbol, uint totalSupply, uint perMint)
,该方法用来创建 ERC20 token,(模拟铭文的 deploy), symbol 表示 Token 的名称,totalSupply 表示可发行的数量,perMint 用来控制每次发行的数量,用于控制mintInscription
函数每次发行的数量mintInscription(address tokenAddr)
用来发行 ERC20 token,每次调用一次,发行perMint指定的数量。要求:
• 合约的第⼀版本用普通的 new 的方式发行 ERC20 token 。
• 第⼆版本,deployInscription 加入一个价格参数 price deployInscription(string symbol, uint totalSupply, uint perMint, uint price)
, price 表示发行每个 token 需要支付的费用,并且 第⼆版本使用最小代理的方式以更节约 gas 的方式来创建 ERC20 token,需要同时修改 mintInscription 的实现以便收取每次发行的费用。
在某些区块链项目中,token 的创建和发行不需要任何中心化的管理者,这种设计背后体现了区块链技术的一些核心思想,包括去中心化、公平性和透明性。
解释:
没有管理员:
公平发射:
铭文火的背后思想:
智能合约:
去中心化:
公平性:
DeFi 代币:很多去中心化金融(DeFi)项目的 token 发行采用了公平发射的方式,不依赖于传统的中心化机构,所有人可以通过参与特定的去中心化平台获得代币。
NFT 项目:某些 NFT 项目也可能采用这种方式,在没有中心化管理者的情况下,通过智能合约进行公开的铸造(minting)和分发。
token 的创建和发行不需要管理员,这个是铭文火的背后思想,没有管理员,公平发射。
这句话强调了去中心化和公平发射在区块链和加密货币项目中的重要性,突出了技术对传统管理模式的挑战。
在 Solidity 中,虚函数(Virtual Function)指的是一种可以被子合约重写的方法。虚函数是一种合约的函数声明,它允许在派生合约中定义特定的实现,而不是在基合约中提供一个固定的实现。
在 Solidity 中,你可以通过以下步骤定义和使用虚函数:
定义虚函数:
在基合约中使用 virtual
关键字标记函数,表示该函数可以被子合约重写。
pragma solidity ^0.8.0;
contract BaseContract {
// 声明虚函数
function getValue() public virtual pure returns (uint256) {
return 1;
}
}
重写虚函数:
在派生合约中使用 override
关键字实现虚函数的具体逻辑。
pragma solidity ^0.8.0;
contract DerivedContract is BaseContract {
// 重写虚函数
function getValue() public override pure returns (uint256) {
return 2;
}
}
pragma solidity ^0.8.0;
contract BaseContract {
// 声明虚函数
function getValue() public virtual pure returns (uint256) {
return 1;
}
}
contract DerivedContract is BaseContract {
// 重写虚函数
function getValue() public override pure returns (uint256) {
return 2;
}
}
在上面的代码中:
BaseContract
中的 getValue()
函数被标记为 virtual
,这意味着它可以在派生合约中被重写。DerivedContract
中重写了 getValue()
函数,使用 override
关键字表示这个函数重写了基合约中的 getValue()
函数。public
或 external
。override
关键字:派生合约中重写的函数必须使用 override
关键字标记。虚函数是 Solidity 合约开发中实现灵活和可扩展设计的重要工具。
delegatecall
?Proxy Contract
):将智能合约的存储合约和逻辑合约分开:代理合约(Proxy Contract
)存储所有相关的变量,并且保存逻辑合约的地址;所有函数存在逻辑合约(Logic Contract
)里,通过delegatecall
执行。当升级时,只需要将代理合约指向新的逻辑合约即可。https://eips.ethereum.org/EIPS/eip-1967
https://eips.ethereum.org/EIPS/eip-1822
代理和逻辑合约的存储布局需要一致。
delegateCall
返回值
(bool success, bytes memory returnData) = address.delegatecall(payload)
Bytes
需转化为具体的类型
不能有函数冲撞
初始化问题? - 实现合约中构造函数无效
https://eips.ethereum.org/EIPS/eip-1167
https://github.com/optionality/clone-factory
├── script
│ ├── DeployProxy.s.sol
│ ├── ERC20Token.s.sol
│ ├── TokenFactoryV1.s.sol
│ └── TokenFactoryV2.s.sol
├── src
│ ├── ERC20Token.sol
│ ├── TokenFactoryV1.sol
│ └── TokenFactoryV2.sol
├── test
│ ├── ERC20TokenTest.sol
│ ├── TokenFactoryV1Test.sol
│ └── TokenFactoryV2Test.sol
ERC20Token.sol
文件// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Script, console} from "forge-std/Script.sol";
contract ERC20Token is
Initializable,
ERC20Upgradeable,
ERC20BurnableUpgradeable,
ERC20PausableUpgradeable,
OwnableUpgradeable,
ERC20PermitUpgradeable,
ERC20VotesUpgradeable
{
uint public totalSupplyToken;
uint public perMint;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
// _disableInitializers();
}
/**
* initializes the token
* @param initialOwner the initial owner
* @param _symbol symbol 表示 Token 的名称
* @param _totalSupply totalSupply 表示可发行的数量
* @param _perMint perMint 用来控制每次发行的数量
*
*/
function initialize(
address initialOwner,
string memory _symbol,
uint _totalSupply,
uint _perMint
) public initializer {
__ERC20_init("ERC20Token", _symbol);
__ERC20Burnable_init();
__ERC20Pausable_init();
__Ownable_init(initialOwner);
__ERC20Permit_init("ERC20Token");
__ERC20Votes_init();
perMint = _perMint;
totalSupplyToken = _totalSupply;
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function mint(address to) public {
uint currentSupply = totalSupply(); // 获取当前代币供应量
// 确保铸造后总供应量不超过最大供应量
require(
currentSupply + perMint <= totalSupplyToken,
"Exceeds max total supply"
);
_mint(to, perMint);
}
// The following functions are overrides required by Solidity.
function _update(
address from,
address to,
uint256 value
)
internal
override(
ERC20Upgradeable,
ERC20PausableUpgradeable,
ERC20VotesUpgradeable
)
{
super._update(from, to, value);
}
function nonces(
address owner
)
public
view
override(ERC20PermitUpgradeable, NoncesUpgradeable)
returns (uint256)
{
return super.nonces(owner);
}
}
TokenFactoryV1.sol
文件// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import "./ERC20Token.sol";
contract TokenFactoryV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
ERC20Token myToken;
address[] public deployedTokens;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}
/**
* 该方法用来创建 ERC20 token,(模拟铭文的 deploy)
* @param symbol symbol 表示 Token 的名称
* @param totalSupply totalSupply 表示可发行的数量,
* @param perMint perMint 用来控制每次发行的数量,用于控制mintInscription函数每次发行的数量
* @dev Deploys a new ERC20Token contract with the given parameters and adds it to the deployedTokens array.
*
* deployInscription(string symbol, uint totalSupply, uint perMint)
*
*/
function deployInscription(
string memory symbol,
uint totalSupply,
uint perMint
) public {
myToken = new ERC20Token();
myToken.initialize(msg.sender, symbol, totalSupply, perMint);
console.log("deployInscription newToken: ", address(myToken));
deployedTokens.push(address(myToken));
}
/**
* 该方法用来给用户发行 token
* @param tokenAddr tokenAddr 表示要发行 token 的地址
* @dev Mints tokens to the caller address using the ERC20Token contract at the given address.
*
* mintInscription(address tokenAddr) 用来发行 ERC20 token,每次调用一次,发行perMint指定的数量。
*/
function mintInscription(address tokenAddr) public {
ERC20Token token = ERC20Token(tokenAddr); // Correctly cast the address to the ERC20Token type
token.mint(msg.sender); // Assuming ERC20Token has a mint function with (address, uint256) parameters
}
function size() public view returns (uint) {
return deployedTokens.length;
}
}
TokenFactoryV2.sol
文件// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/Clones.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
// import "@optionality.io/clone-factory/contracts/CloneFactory.sol";
import "./ERC20Token.sol";
/// @custom:oz-upgrades-from TokenFactoryV1
contract TokenFactoryV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
ERC20Token myToken;
address[] public deployedTokens;
mapping(address => uint) public tokenPrices;
mapping(address => uint) public tokenperMint;
mapping(address => address) public tokenDeployUser;
event deployInscriptionEvent(
address indexed tokenAddress,
address indexed userAddress,
uint indexed price
);
event mintInscriptionEvent(
address indexed tokenAddress,
address indexed userAddress,
uint indexed amount
);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
function setTokenAddress(address _tokenAddress) public onlyOwner {
myToken = ERC20Token(_tokenAddress);
}
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}
/**
* 部署新的 ERC20 代币合约
* @param symbol symbol 表示 Token 的名称
* @param totalSupply totalSupply 表示可发行的数量
* @param perMint perMint 用来控制每次发行的数量,用于控制mintInscription函数每次发行的数量
* @param price 每个代币的价格 price 表示发行每个 token 需要支付的费用
*/
function deployInscription(
string memory symbol,
uint totalSupply,
uint perMint,
uint price
) public {
require(bytes(symbol).length > 0, "Symbol cannot be empty");
require(totalSupply > 0, "Total supply must be greater than zero");
require(perMint > 0, "Per mint must be greater than zero");
require(price > 0, "Price must be greater than zero");
require(
address(myToken) != address(0),
"Implementation address is not set"
);
console.log("deployInscription msg.sender, address:", msg.sender);
// 使用 Clones 库创建最小代理合约实例
address newToken = Clones.clone(address(myToken));
ERC20Token(newToken).initialize(
msg.sender,
symbol,
totalSupply,
perMint
);
deployedTokens.push(newToken);
tokenPrices[newToken] = price;
tokenperMint[newToken] = perMint;
tokenDeployUser[newToken] = msg.sender;
emit deployInscriptionEvent(newToken, msg.sender, price);
}
/**
* 铸造 ERC20 代币
* @param tokenAddr 代币地址
*/
function mintInscription(address tokenAddr) public payable {
ERC20Token token = ERC20Token(tokenAddr);
uint price = tokenPrices[tokenAddr];
uint perMint = tokenperMint[tokenAddr];
address userAddr = tokenDeployUser[tokenAddr];
require(msg.value >= (price * perMint), "Incorrect payment");
token.mint(msg.sender);
// 使用 call 方法转账,以避免 gas 限制问题 payable(userAddr).transfer(msg.value);
(bool success, ) = userAddr.call{value: msg.value}("");
require(success, "Transfer failed.");
emit mintInscriptionEvent(tokenAddr, userAddr, msg.value);
}
/**
* 提取合约余额
*/
function withdraw() external onlyOwner {
uint balance = address(this).balance;
require(balance > 0, "No funds to withdraw");
payable(owner()).transfer(balance);
}
function size() public view returns (uint) {
return deployedTokens.length;
}
}
TokenFactoryV2
合约是一个功能全面的代币工厂,提供了代币的部署、铸造和管理功能,同时考虑了合约的安全性和升级能力。通过使用 OpenZeppelin 的库和模式,确保了合约的可靠性和可维护性。
ERC20TokenTest.sol
文件// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {ERC20Token} from "../src/ERC20Token.sol";
contract ERC20TokenTest is Test {
ERC20Token myToken;
ERC1967Proxy proxy;
Account public owner = makeAccount("owner");
Account public newOwner = makeAccount("newOwner");
Account public user = makeAccount("user");
string public symbol = "ETK";
uint public totalSupply = 1_000_000 ether;
uint public perMint = 10 ether;
function setUp() public {
// 部署实现
ERC20Token implementation = new ERC20Token();
// Deploy the proxy and initialize the contract through the proxy
proxy = new ERC1967Proxy(
address(implementation),
abi.encodeCall(
implementation.initialize,
(owner.addr, symbol, totalSupply, perMint)
)
);
// 用代理关联 MyToken 接口
myToken = ERC20Token(address(proxy));
// Emit the owner address for debugging purposes
emit log_address(owner.addr);
}
// Test the basic ERC20 functionality of the MyToken contract
function testERC20Functionality() public {
// Impersonate the owner to call mint function
vm.prank(owner.addr);
// Mint tokens to address(2) and assert the balance
myToken.mint(user.addr);
assertEq(myToken.balanceOf(user.addr), 10 ether);
}
}
TokenFactoryV1Test.sol
文件// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {ERC20Token} from "../src/ERC20Token.sol";
import {TokenFactoryV1} from "../src/TokenFactoryV1.sol";
import {TokenFactoryV2} from "../src/TokenFactoryV2.sol";
contract TokenFactoryV1Test is Test {
TokenFactoryV1 public factoryv1;
TokenFactoryV2 public factoryv2;
ERC20Token public myToken;
ERC20Token deployedToken;
ERC1967Proxy proxy;
Account public owner = makeAccount("owner");
Account public newOwner = makeAccount("newOwner");
Account public user = makeAccount("user");
string public symbol = "ETK";
uint public totalSupply = 1_000_000 ether;
uint public perMint = 10 ether;
uint public price = 10 ** 16; // 0.01 ETH in wei
function setUp() public {
myToken = new ERC20Token();
myToken.initialize(msg.sender, symbol, totalSupply, perMint);
// 部署实现
TokenFactoryV1 implementation = new TokenFactoryV1();
// Deploy the proxy and initialize the contract through the proxy
proxy = new ERC1967Proxy(
address(implementation),
abi.encodeCall(implementation.initialize, owner.addr)
);
// 用代理关联 TokenFactoryV1 接口
factoryv1 = TokenFactoryV1(address(proxy));
// Emit the owner address for debugging purposes
emit log_address(owner.addr);
}
function testTokenFactoryV1DeployInscriptionFunctionality() public {
vm.prank(owner.addr);
factoryv1.deployInscription(symbol, totalSupply, perMint);
assertEq(factoryv1.size(), 1);
// Fetch the deployed token address
address deployedTokenAddress = factoryv1.deployedTokens(0);
// Create an instance of the deployed token contract
deployedToken = ERC20Token(deployedTokenAddress);
// Verify token initialization
assertEq(deployedToken.symbol(), symbol);
assertEq(deployedToken.totalSupply(), 0);
assertEq(deployedToken.totalSupplyToken(), totalSupply);
assertEq(deployedToken.perMint(), perMint);
// Optionally verify owner initialization
assertEq(deployedToken.owner(), owner.addr);
}
function testTokenFactoryV1PermissionsDeployInscriptionFunctionality()
public
{
vm.startPrank(user.addr);
factoryv1.deployInscription(symbol, totalSupply, perMint);
assertEq(factoryv1.size(), 1);
// Fetch the deployed token address
address deployedTokenAddress = factoryv1.deployedTokens(0);
// Create an instance of the deployed token contract
deployedToken = ERC20Token(deployedTokenAddress);
// Verify token initialization
assertEq(deployedToken.symbol(), symbol);
assertEq(deployedToken.totalSupply(), 0);
assertEq(deployedToken.totalSupplyToken(), totalSupply);
assertEq(deployedToken.perMint(), perMint);
// Optionally verify owner initialization
assertEq(deployedToken.owner(), user.addr);
vm.stopPrank();
}
function testTokenFactoryV1MintInscriptionFunctionality() public {
vm.prank(owner.addr);
factoryv1.deployInscription(symbol, totalSupply, perMint);
assertEq(factoryv1.size(), 1);
// Fetch the deployed token address
address deployedTokenAddress = factoryv1.deployedTokens(0);
deployedToken = ERC20Token(deployedTokenAddress);
vm.startPrank(user.addr);
factoryv1.mintInscription(deployedTokenAddress);
assertEq(deployedToken.balanceOf(user.addr), 10 ether);
assertEq(deployedToken.totalSupply(), 10 ether);
assertEq(deployedToken.totalSupplyToken(), totalSupply);
vm.stopPrank();
}
function testTokenFactoryV1PermissionsMintInscriptionFunctionality()
public
{
vm.startPrank(user.addr);
factoryv1.deployInscription(symbol, totalSupply, perMint);
assertEq(factoryv1.size(), 1);
// Fetch the deployed token address
address deployedTokenAddress = factoryv1.deployedTokens(0);
deployedToken = ERC20Token(deployedTokenAddress);
factoryv1.mintInscription(deployedTokenAddress);
assertEq(
ERC20Token(deployedTokenAddress).balanceOf(user.addr),
10 ether
);
assertEq(deployedToken.balanceOf(user.addr), 10 ether);
assertEq(deployedToken.totalSupply(), 10 ether);
assertEq(deployedToken.totalSupplyToken(), totalSupply);
vm.stopPrank();
}
// 测试升级
function testUpgradeability() public {
Upgrades.upgradeProxy(
address(proxy),
"TokenFactoryV2.sol:TokenFactoryV2",
"",
owner.addr
);
}
function testERC20Functionality() public {
vm.startPrank(user.addr);
factoryv1.deployInscription(symbol, totalSupply, perMint);
address deployedTokenAddress = factoryv1.deployedTokens(0);
deployedToken = ERC20Token(deployedTokenAddress);
factoryv1.mintInscription(deployedTokenAddress);
vm.stopPrank();
assertEq(deployedToken.balanceOf(user.addr), perMint);
}
function testVerifyUpgradeability() public {
testERC20Functionality();
vm.prank(owner.addr);
// TokenFactoryV2 factoryV2 = new TokenFactoryV2();
assertEq(deployedToken.balanceOf(user.addr), perMint); ///
// 1. 升级代理合约
Upgrades.upgradeProxy(
address(proxy),
"TokenFactoryV2.sol:TokenFactoryV2",
"",
owner.addr
);
// TokenFactoryV2 factoryV2 = TokenFactoryV2(address(proxy));
factoryv2 = TokenFactoryV2(address(proxy));
console.log("Verify upgradeability");
vm.prank(owner.addr);
(bool s, ) = address(proxy).call(
abi.encodeWithSignature(
"setTokenAddress(address)",
address(myToken)
)
);
require(s);
// 验证新的功能
// 2. deployInscription
vm.startPrank(user.addr);
deal(user.addr, price * perMint);
(bool success, ) = address(proxy).call(
abi.encodeWithSelector(
factoryv2.deployInscription.selector,
symbol,
totalSupply,
perMint,
price
)
);
assertEq(success, true);
(bool su, bytes memory deployedTokenAddressBytes) = address(proxy).call(
abi.encodeWithSelector(factoryv2.deployedTokens.selector, 0)
);
assertEq(su, true);
address deployedTokenAddress = abi.decode(
deployedTokenAddressBytes,
(address)
);
console.log("deployedTokenAddress", deployedTokenAddress);
(bool sus, bytes memory deployedTokensLengthBytes) = address(proxy)
.call(abi.encodeWithSelector(factoryv2.size.selector));
assertEq(sus, true);
uint256 deployedTokensLength = abi.decode(
deployedTokensLengthBytes,
(uint256)
);
console.log("deployedTokensLength", deployedTokensLength);
assertEq(deployedTokensLength, 2);
(bool su2, bytes memory deployedTokenAddressBytes2) = address(proxy)
.call(abi.encodeWithSelector(factoryv2.deployedTokens.selector, 1));
assertEq(su2, true);
address deployedTokenAddress2 = abi.decode(
deployedTokenAddressBytes2,
(address)
);
assertNotEq(deployedTokenAddress, deployedTokenAddress2);
// 3. mintInscription
deployedToken = ERC20Token(deployedTokenAddress2);
(bool mintSuccess, ) = address(proxy).call{value: price * perMint}(
abi.encodeWithSignature(
"mintInscription(address)",
deployedTokenAddress2
)
);
require(mintSuccess, "Minting of token failed");
assertEq(factoryv2.tokenPrices(deployedTokenAddress), 0);
assertEq(factoryv2.tokenPrices(deployedTokenAddress2), price);
assertEq(factoryv2.tokenperMint(deployedTokenAddress2), perMint);
assertEq(deployedToken.balanceOf(user.addr), 10 ether);
assertEq(deployedToken.totalSupply(), perMint);
assertEq(deployedToken.totalSupplyToken(), totalSupply);
vm.stopPrank();
}
}
TokenFactoryV2Test.sol
文件// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {TokenFactoryV2} from "../src/TokenFactoryV2.sol";
import {ERC20Token} from "../src/ERC20Token.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
contract CounterTest is Test {
TokenFactoryV2 public factoryv2;
ERC20Token public myToken;
ERC1967Proxy proxy;
ERC1967Proxy proxy2;
Account public owner = makeAccount("owner");
Account public user = makeAccount("user");
string public symbol = "ETK";
uint public totalSupply = 1_000_000 ether;
uint public perMint = 10 ether;
uint public price = 10 ** 16; // 0.01 ETH in wei
address public tokenAddr;
function setUp() public {
myToken = new ERC20Token();
myToken.initialize(msg.sender, symbol, totalSupply, perMint);
TokenFactoryV2 implementationV2 = new TokenFactoryV2();
vm.prank(owner.addr);
proxy = new ERC1967Proxy(
address(implementationV2),
abi.encodeCall(implementationV2.initialize, (owner.addr))
);
// 用代理关联 TokenFactoryV2 接口
factoryv2 = TokenFactoryV2(address(proxy));
vm.prank(owner.addr);
(bool success, ) = address(proxy).call(
abi.encodeWithSelector(
factoryv2.setTokenAddress.selector,
address(myToken)
)
);
require(success);
// Emit the owner address for debugging purposes
emit log_address(owner.addr);
}
function testTokenFactoryV2DeployInscriptionFunctionality() public {
vm.startPrank(owner.addr);
factoryv2.deployInscription(symbol, totalSupply, perMint, price);
assertEq(factoryv2.size(), 1);
// Fetch the deployed token address
address deployedTokenAddress = factoryv2.deployedTokens(0);
assertEq(factoryv2.tokenPrices(deployedTokenAddress), price);
// Create an instance of the deployed token contract
ERC20Token deployedToken = ERC20Token(deployedTokenAddress);
assertEq(address(deployedToken), deployedTokenAddress);
// Verify token initialization
assertEq(deployedToken.symbol(), symbol);
assertEq(deployedToken.balanceOf(owner.addr), 0);
assertEq(deployedToken.totalSupplyToken(), totalSupply);
assertEq(deployedToken.perMint(), perMint);
// Optionally verify owner initialization
assertEq(deployedToken.owner(), owner.addr);
vm.stopPrank();
}
function testTokenFactoryV2PermissionsDeployInscriptionFunctionality()
public
{
vm.startPrank(user.addr);
factoryv2.deployInscription(symbol, totalSupply, perMint, price);
assertEq(factoryv2.size(), 1);
// Fetch the deployed token address
address deployedTokenAddress = factoryv2.deployedTokens(0);
assertEq(factoryv2.tokenPrices(deployedTokenAddress), price);
// Create an instance of the deployed token contract
ERC20Token deployedToken = ERC20Token(deployedTokenAddress);
assertEq(address(deployedToken), deployedTokenAddress);
// Verify token initialization
assertEq(deployedToken.symbol(), symbol);
assertEq(deployedToken.balanceOf(owner.addr), 0);
assertEq(deployedToken.totalSupplyToken(), totalSupply);
assertEq(deployedToken.perMint(), perMint);
// Optionally verify owner initialization
assertEq(deployedToken.owner(), user.addr);
vm.stopPrank();
}
function testTokenFactoryV2MintInscriptionFunctionality() public {
vm.prank(owner.addr);
factoryv2.deployInscription(symbol, totalSupply, perMint, price);
assertEq(factoryv2.size(), 1);
// Fetch the deployed token address
address deployedTokenAddress = factoryv2.deployedTokens(0);
ERC20Token deployedToken = ERC20Token(deployedTokenAddress);
vm.startPrank(user.addr);
vm.deal(user.addr, price * perMint);
factoryv2.mintInscription{value: price * perMint}(deployedTokenAddress);
assertEq(deployedToken.balanceOf(user.addr), 10 ether);
assertEq(deployedToken.totalSupply(), 10 ether);
// Verify the total supply token
assertEq(deployedToken.totalSupplyToken(), totalSupply);
vm.stopPrank();
}
function testTokenFactoryV2PermissionsMintInscriptionFunctionality()
public
{
vm.startPrank(user.addr);
factoryv2.deployInscription(symbol, totalSupply, perMint, price);
assertEq(factoryv2.size(), 1);
// Fetch the deployed token address
address deployedTokenAddress = factoryv2.deployedTokens(0);
ERC20Token deployedToken = ERC20Token(deployedTokenAddress);
vm.deal(user.addr, price * perMint);
factoryv2.mintInscription{value: price * perMint}(deployedTokenAddress);
assertEq(deployedToken.balanceOf(user.addr), 10 ether);
assertEq(deployedToken.totalSupply(), 10 ether);
assertEq(factoryv2.tokenperMint(deployedTokenAddress), perMint);
// Verify the total supply token
assertEq(deployedToken.totalSupplyToken(), totalSupply);
vm.stopPrank();
}
}
第一步:部署 ERC20Token
合约
第二步: 部署 TokenFactoryV1
合约 和 部署 DeployUUPSProxy
代理合约
第三步:部署 并升级 TokenFactoryV2
合约
ERC20Token.s.sol
文件// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {ERC20Token} from "../src/ERC20Token.sol";
contract ERC20TokenScript is Script {
ERC20Token public token;
function setUp() public {}
function run() public {
vm.startBroadcast();
token = new ERC20Token();
console.log("Token address: ", address(token));
vm.stopBroadcast();
}
}
DeployUUPSProxy.s.sol
文件注意:同时部署 代理合约和 TokenFactoryV1
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "../src/ERC20Token.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "forge-std/Script.sol";
import {TokenFactoryV1} from "../src/TokenFactoryV1.sol";
contract DeployUUPSProxy is Script {
function setUp() public {}
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployerAddress = vm.addr(deployerPrivateKey);
console.log("Deploying contracts with the account:", deployerAddress);
vm.startBroadcast(deployerPrivateKey);
// address _implementation = 0xa8672dfDb0d5A672CC599C3E8D77F8E807cEc6d6; // Replace with your token address
TokenFactoryV1 _implementation = new TokenFactoryV1(); // Replace with your token address
console.log("TokenFactoryV1 deployed to:", address(_implementation));
// Encode the initializer function call
bytes memory data = abi.encodeCall(
_implementation.initialize,
deployerAddress
);
// Deploy the proxy contract with the implementation address and initializer
ERC1967Proxy proxy = new ERC1967Proxy(address(_implementation), data);
vm.stopBroadcast();
// Log the proxy address
console.log("UUPS Proxy Address:", address(proxy));
}
}
TokenFactoryV2.s.sol
文件// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {TokenFactoryV2} from "../src/TokenFactoryV2.sol";
contract TokenFactoryV2Script is Script {
address public proxy = 0x90635Ff2Ff7E64872848612ad6B943b04B089Db0;
address public erc20Token = 0x65869BaA9336F8968704F2dd60C40959a7bD202b;
function setUp() public {}
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployerAddress = vm.addr(deployerPrivateKey);
console.log("Deploying contracts with the account:", deployerAddress);
vm.startBroadcast(deployerPrivateKey);
Upgrades.upgradeProxy(
address(proxy),
"TokenFactoryV2.sol:TokenFactoryV2",
"",
deployerAddress
);
(bool successful, ) = address(proxy).call(
abi.encodeWithSelector(
TokenFactoryV2.setTokenAddress.selector,
address(erc20Token)
)
);
console.log("setTokenAddress success:", successful);
// console.log("TokenFactoryV1 deployed to:", address(factoryv2));
vm.stopBroadcast();
}
}
ERC20Token
部署UpgradeableTokenFactory on main [!?] via ⬢ v22.1.0 via 🅒 base took 8.1s
➜ source .env
UpgradeableTokenFactory on main [!] via ⬢ v22.1.0 via 🅒 base
➜ forge script --chain sepolia ERC20TokenScript --rpc-url $SEPOLIA_RPC_URL --account MetaMask --broadcast --verify -vvvv
[⠢] Compiling...
[⠑] Compiling 1 files with Solc 0.8.20
[⠘] Solc 0.8.20 finished in 1.49s
Compiler run successful!
Enter keystore password:
Traces:
[2196112] ERC20TokenScript::run()
├─ [0] VM::startBroadcast()
│ └─ ← [Return]
├─ [2149887] → new ERC20Token@0x65869BaA9336F8968704F2dd60C40959a7bD202b
│ └─ ← [Return] 10738 bytes of code
├─ [0] console::log("Token address: ", ERC20Token: [0x65869BaA9336F8968704F2dd60C40959a7bD202b]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::stopBroadcast()
│ └─ ← [Return]
└─ ← [Stop]
Script ran successfully.
== Logs ==
Token address: 0x65869BaA9336F8968704F2dd60C40959a7bD202b
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
[2149887] → new ERC20Token@0x65869BaA9336F8968704F2dd60C40959a7bD202b
└─ ← [Return] 10738 bytes of code
==========================
Chain 11155111
Estimated gas price: 86.582754516 gwei
Estimated total gas used for script: 3083806
Estimated amount required: 0.267004417872967896 ETH
==========================
##### sepolia
✅ [Success]Hash: 0xa9616c34ca9e776eddd5c9f0ebab2b6d634e25f28f8835c9e37aa5600b5cbb98
Contract Address: 0x65869BaA9336F8968704F2dd60C40959a7bD202b
Block: 6396610
Paid: 0.098080535921111506 ETH (2372833 gas * 41.334782482 gwei)
✅ Sequence #1 on sepolia | Total Paid: 0.098080535921111506 ETH (2372833 gas * avg 41.334782482 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
##
Start verification for (1) contracts
Start verifying contract `0x65869BaA9336F8968704F2dd60C40959a7bD202b` deployed on sepolia
Submitting verification for [src/ERC20Token.sol:ERC20Token] 0x65869BaA9336F8968704F2dd60C40959a7bD202b.
Submitting verification for [src/ERC20Token.sol:ERC20Token] 0x65869BaA9336F8968704F2dd60C40959a7bD202b.
Submitted contract for verification:
Response: `OK`
GUID: `62qbazivmkngcqyckp4smut8hh4wlpt3tw1hkjbhrncngggtiy`
URL: https://sepolia.etherscan.io/address/0x65869baa9336f8968704f2dd60c40959a7bd202b
Contract verification status:
Response: `NOTOK`
Details: `Pending in queue`
Contract verification status:
Response: `OK`
Details: `Pass - Verified`
Contract successfully verified
All (1) contracts were verified!
Transactions saved to: /Users/qiaopengjun/Code/solidity-code/UpgradeableTokenFactory/broadcast/ERC20Token.s.sol/11155111/run-latest.json
Sensitive values saved to: /Users/qiaopengjun/Code/solidity-code/UpgradeableTokenFactory/cache/ERC20Token.s.sol/11155111/run-latest.json
UpgradeableTokenFactory on main [!?] via ⬢ v22.1.0 via 🅒 base took 1m 9.2s
➜
https://sepolia.etherscan.io/address/0x65869baa9336f8968704f2dd60c40959a7bd202b#code
TokenFactoryV1
部署UpgradeableTokenFactory on main [!?] via ⬢ v22.1.0 via 🅒 base took 13.2s
➜ forge script --chain sepolia DeployUUPSProxy --rpc-url $SEPOLIA_RPC_URL --account MetaMask --broadcast --verify -vvvv
[⠒] Compiling...
[⠘] Compiling 1 files with Solc 0.8.20
[⠊] Solc 0.8.20 finished in 1.66s
Compiler run successful!
Traces:
[3075117] DeployUUPSProxy::run()
├─ [0] VM::envUint("PRIVATE_KEY") [staticcall]
│ └─ ← [Return] <env var value>
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] 0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5
├─ [0] console::log("Deploying contracts with the account:", 0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::startBroadcast(<pk>)
│ └─ ← [Return]
├─ [2890311] → new TokenFactoryV1@0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234
│ ├─ emit Initialized(version: 18446744073709551615 [1.844e19])
│ └─ ← [Return] 14318 bytes of code
├─ [0] console::log("TokenFactoryV1 deployed to:", TokenFactoryV1: [0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234]) [staticcall]
│ └─ ← [Stop]
├─ [107802] → new ERC1967Proxy@0x90635Ff2Ff7E64872848612ad6B943b04B089Db0
│ ├─ emit Upgraded(implementation: TokenFactoryV1: [0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234])
│ ├─ [48636] TokenFactoryV1::initialize(0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5) [delegatecall]
│ │ ├─ emit OwnershipTransferred(previousOwner: 0x0000000000000000000000000000000000000000, newOwner: 0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5)
│ │ ├─ emit Initialized(version: 1)
│ │ └─ ← [Stop]
│ └─ ← [Return] 170 bytes of code
├─ [0] VM::stopBroadcast()
│ └─ ← [Return]
├─ [0] console::log("UUPS Proxy Address:", ERC1967Proxy: [0x90635Ff2Ff7E64872848612ad6B943b04B089Db0]) [staticcall]
│ └─ ← [Stop]
└─ ← [Stop]
Script ran successfully.
== Logs ==
Deploying contracts with the account: 0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5
TokenFactoryV1 deployed to: 0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234
UUPS Proxy Address: 0x90635Ff2Ff7E64872848612ad6B943b04B089Db0
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
[2890311] → new TokenFactoryV1@0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234
├─ emit Initialized(version: 18446744073709551615 [1.844e19])
└─ ← [Return] 14318 bytes of code
[110302] → new ERC1967Proxy@0x90635Ff2Ff7E64872848612ad6B943b04B089Db0
├─ emit Upgraded(implementation: TokenFactoryV1: [0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234])
├─ [48636] TokenFactoryV1::initialize(0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5) [delegatecall]
│ ├─ emit OwnershipTransferred(previousOwner: 0x0000000000000000000000000000000000000000, newOwner: 0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5)
│ ├─ emit Initialized(version: 1)
│ └─ ← [Stop]
└─ ← [Return] 170 bytes of code
==========================
Chain 11155111
Estimated gas price: 72.74057709 gwei
Estimated total gas used for script: 4356665
Estimated amount required: 0.31690632628780485 ETH
==========================
Enter keystore password:
##### sepolia
✅ [Success]Hash: 0x0eeb69d7cc59e24da2f681eb30de9d37ea72e2c5dac7e423312391384c546d6e
Contract Address: 0x90635Ff2Ff7E64872848612ad6B943b04B089Db0
Block: 6396821
Paid: 0.006243301675965136 ETH (180646 gas * 34.560973816 gwei)
##### sepolia
✅ [Success]Hash: 0x2681b0452bc1f217c7456df4ce763daa96aaeb3e5acba6cf26cd9a91600f3e02
Contract Address: 0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234
Block: 6396821
Paid: 0.109614379457223368 ETH (3171623 gas * 34.560973816 gwei)
✅ Sequence #1 on sepolia | Total Paid: 0.115857681133188504 ETH (3352269 gas * avg 34.560973816 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
##
Start verification for (2) contracts
Start verifying contract `0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234` deployed on sepolia
Submitting verification for [src/TokenFactoryV1.sol:TokenFactoryV1] 0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234.
Submitting verification for [src/TokenFactoryV1.sol:TokenFactoryV1] 0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234.
Submitting verification for [src/TokenFactoryV1.sol:TokenFactoryV1] 0x67fC7A2D6E5C1eD37Af85397DB083568bf7e0234.
Submitted contract for verification:
Response: `OK`
GUID: `fdivnqljbb7xmbxq62cf6wwqtkp9en5bqbratcuwaz3pgkswxr`
URL: https://sepolia.etherscan.io/address/0x67fc7a2d6e5c1ed37af85397db083568bf7e0234
Contract verification status:
Response: `NOTOK`
Details: `Pending in queue`
Contract verification status:
Response: `OK`
Details: `Pass - Verified`
Contract successfully verified
Start verifying contract `0x90635Ff2Ff7E64872848612ad6B943b04B089Db0` deployed on sepolia
Submitting verification for [lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy] 0x90635Ff2Ff7E64872848612ad6B943b04B089Db0.
Submitted contract for verification:
Response: `OK`
GUID: `awwcjbgju4sc5m6wzkannu8h66sy8hjscndbenhet99q3xvfhj`
URL: https://sepolia.etherscan.io/address/0x90635ff2ff7e64872848612ad6b943b04b089db0
Contract verification status:
Response: `NOTOK`
Details: `Pending in queue`
Contract verification status:
Response: `OK`
Details: `Pass - Verified`
Contract successfully verified
All (2) contracts were verified!
Transactions saved to: /Users/qiaopengjun/Code/solidity-code/UpgradeableTokenFactory/broadcast/DeployProxy.s.sol/11155111/run-latest.json
Sensitive values saved to: /Users/qiaopengjun/Code/solidity-code/UpgradeableTokenFactory/cache/DeployProxy.s.sol/11155111/run-latest.json
UpgradeableTokenFactory on main [!?] via ⬢ v22.1.0 via 🅒 base took 1m 50.9s
➜
https://sepolia.etherscan.io/address/0x67fc7a2d6e5c1ed37af85397db083568bf7e0234#code
DeployUUPSProxy
部署https://sepolia.etherscan.io/address/0x90635ff2ff7e64872848612ad6b943b04b089db0#code
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Is this a proxy
TokenFactoryV2
部署并升级https://sepolia.etherscan.io/address/0xdddc3837f0d3cb104b768208327f3017ba22bb6f#code
UpgradeableTokenFactory on main [⇡] via ⬢ v22.1.0 via 🅒 base
➜ forge script --chain sepolia TokenFactoryV2Script --rpc-url $SEPOLIA_RPC_URL --account MetaMask --broadcast --verify -vvvv
[⠢] Compiling...
No files changed, compilation skipped
Traces:
...
Script ran successfully.
== Logs ==
Deploying contracts with the account: 0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5
setTokenAddress success: true
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
...
==========================
Chain 11155111
Estimated gas price: 18.026614008 gwei
Estimated total gas used for script: 1650333
Estimated amount required: 0.029749915975664664 ETH
==========================
Enter keystore password:
Transactions saved to: /Users/qiaopengjun/Code/solidity-code/UpgradeableTokenFactory/broadcast/TokenFactoryV2.s.sol/11155111/run-latest.json
Sensitive values saved to: /Users/qiaopengjun/Code/solidity-code/UpgradeableTokenFactory/cache/TokenFactoryV2.s.sol/11155111/run-latest.json
Error:
Failed to send transaction
Context:
- server returned an error response: error code -32000: future transaction tries to replace pending
UpgradeableTokenFactory on main [⇡?] via ⬢ v22.1.0 via 🅒 base
➜ forge script --chain sepolia TokenFactoryV2Script --rpc-url $SEPOLIA_RPC_URL --account MetaMask --broadcast --verify -vvvv
[⠢] Compiling...
No files changed, compilation skipped
Traces:
...
Script ran successfully.
== Logs ==
Deploying contracts with the account: 0x750Ea21c1e98CcED0d4557196B6f4a5974CCB6f5
setTokenAddress success: true
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
....
==========================
Chain 11155111
Estimated gas price: 12.340371374 gwei
Estimated total gas used for script: 1651348
Estimated amount required: 0.020378247587712152 ETH
==========================
Enter keystore password:
##### sepolia
✅ [Success]Hash: 0xdff6fc1e1c8c433603316f0ee29645c2697f7c15a526a9c44ccd2e31e55ec612
Block: 6397346
Paid: 0.0001663139166606 ETH (26550 gas * 6.264177652 gwei)
##### sepolia
✅ [Success]Hash: 0xd53ffbda9ecfe0e105e26cab1e014fdef4d91a4d08e9a55ee696404adada1b40
Contract Address: 0xDdDC3837f0d3cb104B768208327F3017Ba22Bb6f
Block: 6397346
Paid: 0.007177450904418036 ETH (1145793 gas * 6.264177652 gwei)
##### sepolia
✅ [Success]Hash: 0x460526792fb760d0a32c67c86a5d95931aa310774d7866d4966a362382b19b12
Block: 6397346
Paid: 0.000235840024420148 ETH (37649 gas * 6.264177652 gwei)
##### sepolia
✅ [Success]Hash: 0xf718a117d7a27f75b40545ed018f7c6d0e424945e7c59f818162274eb7d53b92
Block: 6397346
Paid: 0.000319773740779296 ETH (51048 gas * 6.264177652 gwei)
✅ Sequence #1 on sepolia | Total Paid: 0.00789937858627808 ETH (1261040 gas * avg 6.264177652 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
##
Start verification for (1) contracts
Start verifying contract `0xDdDC3837f0d3cb104B768208327F3017Ba22Bb6f` deployed on sepolia
Submitting verification for [src/TokenFactoryV2.sol:TokenFactoryV2] 0xDdDC3837f0d3cb104B768208327F3017Ba22Bb6f.
Submitted contract for verification:
Response: `OK`
GUID: `xb9cv6f4g7ur7p7xj15um4gbfi7guyfsrvmpvjjgdq6dj3vqwd`
URL: https://sepolia.etherscan.io/address/0xdddc3837f0d3cb104b768208327f3017ba22bb6f
Contract verification status:
Response: `NOTOK`
Details: `Pending in queue`
Contract verification status:
Response: `OK`
Details: `Pass - Verified`
Contract successfully verified
All (1) contracts were verified!
Transactions saved to: /Users/qiaopengjun/Code/solidity-code/UpgradeableTokenFactory/broadcast/TokenFactoryV2.s.sol/11155111/run-latest.json
Sensitive values saved to: /Users/qiaopengjun/Code/solidity-code/UpgradeableTokenFactory/cache/TokenFactoryV2.s.sol/11155111/run-latest.json
UpgradeableTokenFacto
注意:这里部署了两次,因为第一次部署时发生Context:server returned an error response: error code -32000: future transaction tries to replace pending
有可能是网络问题,导致部署上去的合约未验证,故重新再次部署,当然也可以进行验证verify-contract
。
刚部署时因为报错查看没有 Verify,错误 error code -32000: future transaction tries to replace pending
通常发生在以太坊交易过程中,当一个新的交易试图替换一个尚未被矿工处理的待处理交易时。 第二次部署后,再次查看已经Verify了,估计是网络延迟或节点同步问题。
https://sepolia.etherscan.io/address/0x89A14B4b7c9Ec826C1a3C38deF97b90565503992#code
https://sepolia.etherscan.io/address/0xdddc3837f0d3cb104b768208327f3017ba22bb6f
问题二
解决:调用的时候一定要是代理去调用,而不能是factory合约去调用
UpgradeableTokenFactory on main [!?] via 🅒 base took 8.2s
➜ forge test -vv
[⠒] Compiling...
No files changed, compilation skipped
Ran 1 test for test/ERC20TokenTest.sol:ERC20TokenTest
[PASS] testERC20Functionality() (gas: 123304)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.67ms (61.00µs CPU time)
Ran 4 tests for test/TokenFactoryV2Test.sol:CounterTest
[PASS] testTokenFactoryV2DeployInscriptionFunctionality() (gas: 2903155)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
deployInscription msg.sender, address: 0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
[PASS] testTokenFactoryV2MintInscriptionFunctionality() (gas: 3002961)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
deployInscription msg.sender, address: 0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
[PASS] testTokenFactoryV2PermissionsDeployInscriptionFunctionality() (gas: 2905067)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
deployInscription msg.sender, address: 0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D
[PASS] testTokenFactoryV2PermissionsMintInscriptionFunctionality() (gas: 3000572)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
deployInscription msg.sender, address: 0x6CA6d1e2D5347Bfab1d91e883F1915560e09129D
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 2.23ms (1.95ms CPU time)
Ran 2 tests for test/Counter.t.sol:CounterTest
[PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 30977, ~: 31288)
[PASS] test_Increment() (gas: 31303)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.42ms (4.31ms CPU time)
Ran 5 tests for test/TokenFactoryV1Test.sol:TokenFactoryV1Test
[PASS] testTokenFactoryV1DeployInscriptionFunctionality() (gas: 2972857)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
deployInscription newToken: 0x8d2C17FAd02B7bb64139109c6533b7C2b9CADb81
[PASS] testTokenFactoryV1MintInscriptionFunctionality() (gas: 3068046)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
deployInscription newToken: 0x8d2C17FAd02B7bb64139109c6533b7C2b9CADb81
[PASS] testTokenFactoryV1PermissionsDeployInscriptionFunctionality() (gas: 2973252)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
deployInscription newToken: 0x8d2C17FAd02B7bb64139109c6533b7C2b9CADb81
[PASS] testTokenFactoryV1PermissionsMintInscriptionFunctionality() (gas: 3065556)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
deployInscription newToken: 0x8d2C17FAd02B7bb64139109c6533b7C2b9CADb81
[PASS] testUpgradeability() (gas: 6337394)
Logs:
0x7c8999dC9a822c1f0Df42023113EDB4FDd543266
Suite result: ok. 5 passed; 0 failed; 0 skipped; finished in 6.02s (6.02s CPU time)
Ran 4 test suites in 6.02s (6.03s CPU time): 12 tests passed, 0 failed, 0 skipped (12 total tests)
UpgradeableTokenFactory on main [!?] via 🅒 base took 6.7s
forge install foundry-rs/forge-std
forge install OpenZeppelin/openzeppelin-foundry-upgrades
forge install OpenZeppelin/openzeppelin-contracts-upgradeable
remappings.txt
, replacing any previous definitions of these remappings:@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/
foundry.toml
to enable ffi, ast, build info and storage layout:[profile.default]
ffi = true
ast = true
build_info = true
extra_output = ["storageLayout"]
更多请参考:https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!