本指南演示了如何使用 Foundry 测试 {oz} Upgrades 插件。
前提条件
-
安装了 Foundry。请参阅 Foundry 的安装说明。
-
安装了 {oz} Upgrades 插件。
npm install --save-dev @openzeppelin/hardhat-upgrades
设置你的项目
首先,创建一个新的 Foundry 项目。
forge init --template minimal my-forge-project
cd my-forge-project
接下来,安装 {oz} Contracts。
forge install openzeppelin/openzeppelin-contracts
然后,将以下内容添加到你的 foundry.toml
文件中。这将允许 Foundry 找到 {oz} Contracts 中的导入。
[remappings]
openzeppelin/=lib/openzeppelin-contracts/
编写你的合约
现在,创建一个你想要升级的合约。这是一个简单的计数器合约的例子。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin/contracts/proxy/utils/Initializable.sol";
contract MyContract is Initializable {
uint256 private _myNumber;
function initialize() public initializer {
_myNumber = 0;
}
function myNumber() public view returns (uint256) {
return _myNumber;
}
function increment() public {
_myNumber++;
}
}
将此合约保存在 src/MyContract.sol
中。你可以随意命名这个文件。
编写你的测试
现在,编写你的测试。首先,导入你需要的 Foundry 依赖。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {MyContract} from "../src/MyContract.sol";
import {Deployments} from "./Deployments.t.sol";
接下来,创建一个新的测试合约并从 Test
继承。
contract MyContractTest is Test {
现在,在你的测试合约中声明变量。你需要你的合约的实例,你的代理的地址以及 Deployments
合约的实例。
MyContract public myContract;
address public proxy;
Deployments public deployments;
编写你的 setUp
函数。首先,你需要部署你的 Deployments
合约。这个合约将包含升级逻辑。然后,你可以使用 Deployments
合约来部署你的合约。
function setUp() public {
deployments = new Deployments();
(myContract, proxy) = deployments.deployMyContract();
}
现在,编写你的测试。这是一个简单的测试,它部署了你的合约,调用了 increment
函数,并断言 myNumber
现在是 1。
function testIncrement() public {
myContract.increment();
assertEq(myContract.myNumber(), 1);
}
完整的测试文件如下所示。将其保存在 test/MyContract.t.sol
。你可以随意命名这个文件。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {MyContract} from "../src/MyContract.sol";
import {Deployments} from "./Deployments.t.sol";
contract MyContractTest is Test {
MyContract public myContract;
address public proxy;
Deployments public deployments;
function setUp() public {
deployments = new Deployments();
(myContract, proxy) = deployments.deployMyContract();
}
function testIncrement() public {
myContract.increment();
assertEq(myContract.myNumber(), 1);
}
}
编写你的部署合约
使用 {oz} Upgrades 插件,你需要一个合约来处理你的部署和升级。这是一个将部署你的合约的合约的例子。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {VmSafe} from "forge-std/VmSafe.sol";
import {MyContract} from "../src/MyContract.sol";
import {TransparentUpgradeableProxy} from "openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
contract Deployments {
using VmSafe for vm;
function deployMyContract() public returns (MyContract, address) {
// 1. Deploy the implementation contract
MyContract myContract = new MyContract();
// 2. Deploy the proxy admin
address proxyAdmin = address(this);
// 3. Deploy the transparent proxy
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
address(myContract),
proxyAdmin,
""
);
// 4. Return the proxy as the contract
return (MyContract(address(proxy)), address(proxy));
}
}
将此合约保存在 test/Deployments.t.sol
中。你可以随意命名这个文件。请注意,Deployments
合约也必须是一个测试合约,这就是为什么它被保存在 test
目录中,并且必须以 .t.sol
结尾。
运行你的测试
现在,你可以运行你的测试了。
forge test
你应该看到你的测试通过了。
[⠰] Compiling...
[⠔] Compiling 1 files with 0.8.19
[⠘] Solc: ok
[⠘] Linking: ok
[♮] Optimizing...
[♮] gas estimates: ok
[+] Wrote cache: .forge-cache
====================== test session starts ======================
Found 1 test(s)
Running 1 test(s)
[PASS] test/MyContract.t.sol:MyContractTest::testIncrement() (gas: 29925)
Logs:
Deployments::deployMyContract:
address(myContract) = 0x5FbDB2315678afecb367f032d93F642f64180aa3
address(proxyAdmin) = 0x5FC8D32690cc91D4c39d9d3abcBD1690F877631
address(proxy) = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.47ms
升级合约
现在,让我们升级你的合约。首先,创建一个新的合约版本。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "openzeppelin/contracts/proxy/utils/Initializable.sol";
contract MyContractV2 is Initializable {
uint256 private _myNumber;
uint256 private _myMultiplier;
function initialize(uint256 multiplier) public initializer {
_myNumber = 1;
_myMultiplier = multiplier;
}
function myNumber() public view returns (uint256) {
return _myNumber;
}
function multiply() public {
_myNumber = _myNumber * _myMultiplier;
}
}
将此合约保存在 src/MyContractV2.sol
中。你可以随意命名这个文件。
接下来,更新你的 Deployments
合约以部署新的合约并将其升级到它。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {VmSafe} from "forge-std/VmSafe.sol";
import {MyContract} from "../src/MyContract.sol";
import {MyContractV2} from "../src/MyContractV2.sol";
import {TransparentUpgradeableProxy} from "openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {ProxyAdmin} from "openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
contract Deployments {
using VmSafe for vm;
function deployMyContract() public returns (MyContract, address) {
// 1. Deploy the implementation contract
MyContract myContract = new MyContract();
// 2. Deploy the proxy admin
ProxyAdmin proxyAdmin = new ProxyAdmin();
// 3. Deploy the transparent proxy
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
address(myContract),
address(proxyAdmin),
""
);
// 4. Initialize the proxy
myContract.initialize();
// 5. Return the proxy as the contract
return (MyContract(address(proxy)), address(proxy));
}
function upgradeMyContract(address proxy, address proxyAdmin) public returns (MyContractV2) {
// 1. Deploy the new implementation contract
MyContractV2 myContractV2 = new MyContractV2();
// 2. Upgrade the proxy to the new implementation
ProxyAdmin(proxyAdmin).upgrade(proxy, address(myContractV2));
// 3. Initialize the new implementation
MyContractV2(proxy).initialize(5);
// 4. Return the proxy as the contract
return MyContractV2(proxy);
}
}
最后,更新你的测试以使用新的合约。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Test, console2} from "forge-std/Test.sol";
import {MyContract} from "../src/MyContract.sol";
import {MyContractV2} from "../src/MyContractV2.sol";
import {Deployments} from "./Deployments.t.sol";
contract MyContractTest is Test {
MyContract public myContract;
MyContractV2 public myContractV2;
address public proxy;
Deployments public deployments;
address public proxyAdmin;
function setUp() public {
deployments = new Deployments();
(myContract, proxy) = deployments.deployMyContract();
proxyAdmin = address(deployments);
myContractV2 = deployments.upgradeMyContract(proxy, proxyAdmin);
}
function testUpgrade() public {
myContractV2.multiply();
assertEq(myContractV2.myNumber(), 5);
}
}
现在,再次运行你的测试。
forge test
你应该看到你的测试通过了。
[⠰] Compiling...
[⠘] Compiling 3 files with 0.8.19
[⠘] Solc: ok
[⠘] Linking: ok
[♮] Optimizing...
[♮] gas estimates: ok
[+] Wrote cache: .forge-cache
====================== test session starts ======================
Found 1 test(s)
Running 1 test(s)
[PASS] test/MyContract.t.sol:MyContractTest::testUpgrade() (gas: 43437)
Logs:
Deployments::deployMyContract:
address(myContract) = 0x5FbDB2315678afecb367f032d93F642f64180aa3
address(proxyAdmin) = 0xCf7Ed3AccA5a467e9e704C7036889D546B85233d
address(proxy) = 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
Deployments::upgradeMyContract:
address(myContractV2) = 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.61ms