Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol
- 0. 版本
- 0.1 ERC1967Upgrade.sol
- 1. 目标合约
- 2. 代码精读
- 2.1 _getImplementation() internal && _upgradeTo(address newImplementation) internal
- 2.2 _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal
- 2.3 _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal
- 2.4 _getAdmin() internal && _changeAdmin(address newAdmin) internal
- 2.5 _getBeacon() internal && _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal
0. 版本
[openzeppelin]:v4.8.3,[forge-std]:v1.5.6
0.1 ERC1967Upgrade.sol
Github: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.3/contracts/proxy/ERC1967/ERC1967Upgrade.sol
ERC1967Upgrade库实现了基于ERC1967标准(代理合约的slot分布)的slots读写函数,并在对应slot更新时emit出标准中相应的event。对于各种可升级合约和代理合约的实现而言,本库的作用举足轻重。
ERC1967详情参见:https://eips.ethereum.org/EIPS/eip-1967
1. 目标合约
继承ERC1967Upgrade合约:
Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/src/proxy/ERC1967/MockERC1967Upgrade.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;import "openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Upgrade.sol";contract MockERC1967Upgrade is ERC1967Upgrade {bytes32 private constant _ROLLBACK_SLOT = bytes32(uint(keccak256("eip1967.proxy.rollback")) - 1);function setRollbackSlot(bool value) external {StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value = value;}function getImplementation() external view returns (address){return _getImplementation();}function upgradeTo(address newImplementation) external {_upgradeTo(newImplementation);}function upgradeToAndCall(address newImplementation,bytes memory data,bool forceCall) external {_upgradeToAndCall(newImplementation, data, forceCall);}function upgradeToAndCallUUPS(address newImplementation,bytes memory data,bool forceCall) external {_upgradeToAndCallUUPS(newImplementation, data, forceCall);}function getAdmin() external view returns (address) {return _getAdmin();}function changeAdmin(address newAdmin) external {_changeAdmin(newAdmin);}function getBeacon() external view returns (address) {return _getBeacon();}function upgradeBeaconToAndCall(address newBeacon,bytes memory data,bool forceCall) external {_upgradeBeaconToAndCall(newBeacon, data, forceCall);}
}
全部foundry测试合约:
Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/proxy/ERC1967/ERC1967Upgrade/ERC1967Upgrade.t.sol
测试使用的物料合约:
Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/proxy/ERC1967/ERC1967Upgrade/Implement.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;import "openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol";interface IImplement {event InitialCallWithoutArgs();event InitialCallWithArgs(uint, address, string);event Receive();event Fallback(bytes);
}contract Implement is IImplement {function initialCallWithoutArgs() external {emit InitialCallWithoutArgs();}function initialCallWithArgs(uint arg1, address arg2, string memory arg3) external {emit InitialCallWithArgs(arg1, arg2, arg3);}receive() external payable {emit Receive();}fallback() external {emit Fallback(msg.data);}
}contract ImplementERC1822Proxiable is Implement, IERC1822Proxiable {bytes32 public proxiableUUID;constructor(bytes32 newProxiableUUID){proxiableUUID = newProxiableUUID;}
}
Github: https://github.com/RevelationOfTuring/foundry-openzeppelin-contracts/blob/master/test/proxy/ERC1967/ERC1967Upgrade/Beacon.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;import "openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol";contract Beacon is IBeacon {address public implementation;constructor(address newImplementation){implementation = newImplementation;}
}
2. 代码精读
2.1 _getImplementation() internal && _upgradeTo(address newImplementation) internal
_getImplementation() internal
:返回当前存储的逻辑合约地址;_upgradeTo(address newImplementation) internal
:将代理合约背后的逻辑合约地址升级更换为newImplementation。
// 用于存储逻辑合约地址的slot号// 计算逻辑:keccak256("eip1967.proxy.implementation")-1bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;function _getImplementation() internal view returns (address) {// 读取编号为_IMPLEMENTATION_SLOT的slot中的值,并转为address类型// 注:StorageSlot.getAddressSlot()详解参见:https://learnblockchain.cn/article/6104return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;}// 设置逻辑合约地址为newImplementationfunction _setImplementation(address newImplementation) private {// 检验新的逻辑合约地址是合约地址// 注:Address.isContract()详解参见:https://learnblockchain.cn/article/6098require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");// 将新的逻辑合约地址写入编号为_IMPLEMENTATION_SLOT的slot中StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;}function _upgradeTo(address newImplementation) internal {// 设置新的逻辑合约地址_setImplementation(newImplementation);// 按照ERC1967标准,在升级逻辑合约地址后抛出事件`Upgraded(address indexed)`emit Upgraded(newImplementation);}
foundry代码验证:
contract ERC1967UpgradeTest is Test, IERC1967 {MockERC1967Upgrade private _testing = new MockERC1967Upgrade();Implement private _implement = new Implement();function test_GetImplementationAndUpgradeTo() external {assertEq(_testing.getImplementation(), address(0));address newImplementationAddress = address(_implement);vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementationAddress);_testing.upgradeTo(newImplementationAddress);assertEq(_testing.getImplementation(), newImplementationAddress);// revert if new implementation address is not a contractvm.expectRevert("ERC1967: new implementation is not a contract");_testing.upgradeTo(address(1024));}
}
2.2 _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal
将代理合约背后的逻辑合约地址升级更换为newImplementation,再执行一个额外的到新逻辑合约的delegatecall。
注:这个delegatecall可以理解为是去调用一个适配合约升级的类似constructor的函数。
function _upgradeToAndCall(address newImplementation,bytes memory data,bool forceCall) internal {// 将代理合约背后的逻辑合约地址升级更换为newImplementation_upgradeTo(newImplementation);if (data.length > 0 || forceCall) {// 如果data不为空或forceCall为true,将delegatecall到新逻辑合约(以data作为calldata)// 注:Address.functionDelegateCall()详解参见:https://learnblockchain.cn/article/6098Address.functionDelegateCall(newImplementation, data);}}
foundry代码验证:
contract ERC1967UpgradeTest is Test, IERC1967, IImplement {MockERC1967Upgrade private _testing = new MockERC1967Upgrade();Implement private _implement = new Implement();function test_UpgradeToAndCall() external {assertEq(_testing.getImplementation(), address(0));address newImplementationAddress = address(_implement);// case 1: no callvm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementationAddress);_testing.upgradeToAndCall(newImplementationAddress, '', false);assertEq(_testing.getImplementation(), newImplementationAddress);// revert if new implementation address is not a contractvm.expectRevert("ERC1967: new implementation is not a contract");_testing.upgradeToAndCall(address(1024), '', false);// case 2: call with no argumentnewImplementationAddress = address(new Implement());vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementationAddress);emit IImplement.InitialCallWithoutArgs();bytes memory data = abi.encodeCall(_implement.initialCallWithoutArgs, ());_testing.upgradeToAndCall(newImplementationAddress, data, false);assertEq(_testing.getImplementation(), newImplementationAddress);// revert if new implementation address is not a contractvm.expectRevert("ERC1967: new implementation is not a contract");_testing.upgradeToAndCall(address(1024), data, false);// case 3: call with argumentsnewImplementationAddress = address(new Implement());uint arg1 = 1024;address arg2 = address(1024);string memory arg3 = "1024";vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementationAddress);emit IImplement.InitialCallWithArgs(arg1, arg2, arg3);data = abi.encodeCall(_implement.initialCallWithArgs,(arg1, arg2, arg3));_testing.upgradeToAndCall(newImplementationAddress, data, false);assertEq(_testing.getImplementation(), newImplementationAddress);// revert if new implementation address is not a contractvm.expectRevert("ERC1967: new implementation is not a contract");_testing.upgradeToAndCall(address(1024), data, false);// case 4: with forceCall and no data// NOTE: force call to the receive function of ImplementnewImplementationAddress = address(new Implement());vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementationAddress);emit IImplement.Receive();_testing.upgradeToAndCall(newImplementationAddress, '', true);assertEq(_testing.getImplementation(), newImplementationAddress);// revert if new implementation address is not a contractvm.expectRevert("ERC1967: new implementation is not a contract");_testing.upgradeToAndCall(address(1024), '', true);// case 5: with forceCall and data// NOTE: it will enter the fallback function of Implement with non-selector datanewImplementationAddress = address(new Implement());data = 'unknown';vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementationAddress);emit IImplement.Fallback(data);_testing.upgradeToAndCall(newImplementationAddress, data, true);assertEq(_testing.getImplementation(), newImplementationAddress);// revert if new implementation address is not a contractvm.expectRevert("ERC1967: new implementation is not a contract");_testing.upgradeToAndCall(address(1024), data, true);}
}
2.3 _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal
将代理合约背后的逻辑合约地址升级更换为newImplementation并对其进行UUPS安全检查,再执行一个额外的到新逻辑合约的delegatecall。
注:这个delegatecall可以理解为是去调用一个适配合约升级的类似constructor的函数。
// 用于存储rollback测试标志的slot号。// 如果当前的升级调用_upgradeToAndCallUUPS()处于rollback测试中,应先将该slot中设置为非0值。反之该slot中设置为0值。// 计算逻辑:keccak256("eip1967.proxy.rollback")-1bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;function _upgradeToAndCallUUPS(address newImplementation,bytes memory data,bool forceCall) internal {// 注:将UUPS proxy背后的逻辑合约从旧升到新时一般需要进行一个rollback测试,来确保逻辑合约的兼容性。// 该测试是将代理背后的新逻辑合约升级回旧逻辑合约,如果可以回滚成功就说明新逻辑合约是有效的UUPS逻辑合约。if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {// 如果当前_upgradeToAndCallUUPS()的调用是处于UUPS的rollback测试中,// 那么将逻辑合约地址直接设置为newImplementation_setImplementation(newImplementation);} else {// 如果当前_upgradeToAndCallUUPS()的调用不是处于UUPS的rollback测试中,// 那么将调用newImplementation合约的proxiableUUID方法,并检查返回值。// 注:这是在检查新逻辑合约的有效兼容性try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {// 如果成功获得newImplementation合约的proxiableUUID方法的返回值,要求该返回值为_IMPLEMENTATION_SLOT// 否则认为newImplementation合约不兼容require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");} catch {// 如果newImplementation合约中没有proxiableUUID方法,说明新逻辑合约不是UUPS逻辑合约,直接revertrevert("ERC1967Upgrade: new implementation is not UUPS");}// 将proxy合约背后的逻辑合约地址升级更换为newImplementation,再执行一个额外的到新逻辑合约的delegatecall_upgradeToAndCall(newImplementation, data, forceCall);}}
foundry代码验证:
contract ERC1967UpgradeTest is Test, IERC1967, IImplement {MockERC1967Upgrade private _testing = new MockERC1967Upgrade();Implement private _implement = new Implement();function test_UpgradeToAndCallUUPS() external {assertEq(_testing.getImplementation(), address(0));address newImplementationAddress = address(_implement);// case 1: in rollback test// NOTE: only change implementation address no matter what the data and forceCall arguments are_testing.setRollbackSlot(true);_testing.upgradeToAndCallUUPS(newImplementationAddress, '', false);assertEq(_testing.getImplementation(), newImplementationAddress);newImplementationAddress = address(new Implement());_testing.upgradeToAndCallUUPS(newImplementationAddress, '1024', false);assertEq(_testing.getImplementation(), newImplementationAddress);newImplementationAddress = address(new Implement());_testing.upgradeToAndCallUUPS(newImplementationAddress, '', true);assertEq(_testing.getImplementation(), newImplementationAddress);newImplementationAddress = address(new Implement());_testing.upgradeToAndCallUUPS(newImplementationAddress, '1024', true);assertEq(_testing.getImplementation(), newImplementationAddress);// case 2: out of rollback test_testing.setRollbackSlot(false);// case 2.1: with supported proxiableUUIDbytes32 proxiableUUID = bytes32(uint(keccak256("eip1967.proxy.implementation")) - 1);// case 2.1.1: no calladdress payable newImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementERC1822ProxiableAddress);_testing.upgradeToAndCallUUPS(newImplementERC1822ProxiableAddress, '', false);assertEq(_testing.getImplementation(), newImplementERC1822ProxiableAddress);// case 2.1.2: call with no argumentnewImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementERC1822ProxiableAddress);emit IImplement.InitialCallWithoutArgs();bytes memory data = abi.encodeCall(ImplementERC1822Proxiable(newImplementERC1822ProxiableAddress).initialCallWithoutArgs,());_testing.upgradeToAndCallUUPS(newImplementERC1822ProxiableAddress, data, false);assertEq(_testing.getImplementation(), newImplementERC1822ProxiableAddress);// case 2.1.3: call with argumentsnewImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));uint arg1 = 1024;address arg2 = address(1024);string memory arg3 = "1024";vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementERC1822ProxiableAddress);emit IImplement.InitialCallWithArgs(arg1, arg2, arg3);data = abi.encodeCall(ImplementERC1822Proxiable(newImplementERC1822ProxiableAddress).initialCallWithArgs,(arg1, arg2, arg3));_testing.upgradeToAndCallUUPS(newImplementERC1822ProxiableAddress, data, false);assertEq(_testing.getImplementation(), newImplementERC1822ProxiableAddress);// case 2.1.4: with forceCall and no data// NOTE: force call to the receive function of ImplementnewImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementERC1822ProxiableAddress);emit IImplement.Receive();_testing.upgradeToAndCallUUPS(newImplementERC1822ProxiableAddress, '', true);assertEq(_testing.getImplementation(), newImplementERC1822ProxiableAddress);// case 2.1.5: with forceCall and data// NOTE: it will enter the fallback function of Implement with non-selector datanewImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));data = 'unknown';vm.expectEmit(address(_testing));emit IERC1967.Upgraded(newImplementERC1822ProxiableAddress);emit IImplement.Fallback(data);_testing.upgradeToAndCallUUPS(newImplementERC1822ProxiableAddress, data, true);assertEq(_testing.getImplementation(), newImplementERC1822ProxiableAddress);// case 2.2: revert with unsupported proxiableUUIDproxiableUUID = bytes32(uint(keccak256("eip1967.proxy.implementation")) - 2);newImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));vm.expectRevert("ERC1967Upgrade: unsupported proxiableUUID");_testing.upgradeToAndCallUUPS(newImplementERC1822ProxiableAddress, '', false);// case 2.3: revert if the new implementation was a non-ERC1822 compliantproxiableUUID = bytes32(uint(keccak256("eip1967.proxy.implementation")) - 1);newImplementERC1822ProxiableAddress = payable(address(new Implement()));vm.expectRevert("ERC1967Upgrade: new implementation is not UUPS");_testing.upgradeToAndCallUUPS(newImplementERC1822ProxiableAddress, '', false);// case 2.4: revert without msg if the new implementation address is not a contractvm.expectRevert();_testing.upgradeToAndCallUUPS(address(1024), '', false);}
}
2.4 _getAdmin() internal && _changeAdmin(address newAdmin) internal
_getAdmin() internal
:返回当前admin地址;_changeAdmin(address newAdmin) internal
:更换admin地址为newAdmin。
// 用于存储admin地址的slot号// 计算逻辑:keccak256("eip1967.proxy.admin")-1bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;function _getAdmin() internal view returns (address) {// 读取编号为_ADMIN_SLOT的slot中的值,并转为address类型// 注:StorageSlot.getAddressSlot()详解参见:https://learnblockchain.cn/article/6104return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;}function _changeAdmin(address newAdmin) internal {// 按照ERC1967标准,在更换admin地址后抛出事件`AdminChanged(address,address)`emit AdminChanged(_getAdmin(), newAdmin);// 设置admin地址为newAdmin_setAdmin(newAdmin);}// 设置admin地址为newAdminfunction _setAdmin(address newAdmin) private {// 要求newAdmin不为0地址require(newAdmin != address(0), "ERC1967: new admin is the zero address");// 将newAdmin写入编号为_ADMIN_SLOT的slot中StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;}
foundry代码验证:
contract ERC1967UpgradeTest is Test, IERC1967 {MockERC1967Upgrade private _testing = new MockERC1967Upgrade();function test_GetAdminAndChangeAdmin() external {address currentAdmin = address(0);assertEq(_testing.getAdmin(), currentAdmin);address newAdminAddress = address(1024);vm.expectEmit(address(_testing));emit IERC1967.AdminChanged(currentAdmin, newAdminAddress);_testing.changeAdmin(newAdminAddress);assertEq(_testing.getAdmin(), newAdminAddress);}
}
2.5 _getBeacon() internal && _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal
_getBeacon() internal
:返回当前存储的信标合约地址;_upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal
:将代理合约背后的信标合约地址升级更换为newBeacon,再执行一个额外的到新信标合约背后的逻辑合约的delegatecall。注:这个delegatecall可以理解为是去调用一个适配合约升级的类似constructor的函数。
// 用于存储信标合约地址的slot号// 计算逻辑:keccak256("eip1967.proxy.beacon")-1bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;function _getBeacon() internal view returns (address) {// 读取编号为_BEACON_SLOT的slot中的值,并转为address类型// 注:StorageSlot.getAddressSlot()详解参见:https://learnblockchain.cn/article/6104return StorageSlot.getAddressSlot(_BEACON_SLOT).value;}function _upgradeBeaconToAndCall(address newBeacon,bytes memory data,bool forceCall) internal {// 设置信标合约地址为newBeacon_setBeacon(newBeacon);// 按照ERC1967标准,在升级信标合约地址后抛出事件`BeaconUpgraded(address indexed)`emit BeaconUpgraded(newBeacon);if (data.length > 0 || forceCall) {// 如果data不为空或forceCall为true,将delegatecall到新信标合约背后的逻辑合约(以data作为calldata)// 注:Address.functionDelegateCall()详解参见:https://learnblockchain.cn/article/6098Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);}}// 设置信标合约地址为newBeaconfunction _setBeacon(address newBeacon) private {// 要求newBeacon是一个合约地址// 注:Address.isContract()详解参见:https://learnblockchain.cn/article/6098require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");// 要求newBeacon合约中存储的逻辑合约地址是一个合约地址require(Address.isContract(IBeacon(newBeacon).implementation()),"ERC1967: beacon implementation is not a contract");// 将newBeacon写入编号为_BEACON_SLOT的slot中StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;}
foundry代码验证:
contract ERC1967UpgradeTest is Test, IERC1967, IImplement {MockERC1967Upgrade private _testing = new MockERC1967Upgrade();Implement private _implement = new Implement();function test_GetBeaconAndUpgradeBeaconToAndCall() external {assertEq(_testing.getBeacon(), address(0));// case 1: no calladdress newBeaconAddress = address(new Beacon(address(_implement)));vm.expectEmit(address(_testing));emit IERC1967.BeaconUpgraded(newBeaconAddress);_testing.upgradeBeaconToAndCall(newBeaconAddress, '', false);assertEq(_testing.getBeacon(), newBeaconAddress);// case 2: call with no argumentnewBeaconAddress = address(new Beacon(address(_implement)));vm.expectEmit(address(_testing));emit IERC1967.BeaconUpgraded(newBeaconAddress);emit IImplement.InitialCallWithoutArgs();bytes memory data = abi.encodeCall(_implement.initialCallWithoutArgs,());_testing.upgradeBeaconToAndCall(newBeaconAddress, data, false);assertEq(_testing.getBeacon(), newBeaconAddress);// case 3: call with argumentsnewBeaconAddress = address(new Beacon(address(_implement)));uint arg1 = 1024;address arg2 = address(1024);string memory arg3 = "1024";vm.expectEmit(address(_testing));emit IERC1967.BeaconUpgraded(newBeaconAddress);emit IImplement.InitialCallWithArgs(arg1, arg2, arg3);data = abi.encodeCall(_implement.initialCallWithArgs,(arg1, arg2, arg3));_testing.upgradeBeaconToAndCall(newBeaconAddress, data, false);assertEq(_testing.getBeacon(), newBeaconAddress);// case 4: with forceCall and no data// NOTE: force call to the receive function of ImplementnewBeaconAddress = address(new Beacon(address(_implement)));vm.expectEmit(address(_testing));emit IERC1967.BeaconUpgraded(newBeaconAddress);emit IImplement.Receive();_testing.upgradeBeaconToAndCall(newBeaconAddress, '', true);assertEq(_testing.getBeacon(), newBeaconAddress);// case 5: with forceCall and data// NOTE: it will enter the fallback function of Implement with non-selector datanewBeaconAddress = address(new Beacon(address(_implement)));data = 'unknown';vm.expectEmit(address(_testing));emit IERC1967.BeaconUpgraded(newBeaconAddress);emit IImplement.Fallback(data);_testing.upgradeBeaconToAndCall(newBeaconAddress, data, true);assertEq(_testing.getBeacon(), newBeaconAddress);// revert if new beacon address is not a contractvm.expectRevert("ERC1967: new beacon is not a contract");_testing.upgradeBeaconToAndCall(address(1024), '', false);// revert if the implementation address in the new beacon is not a contractnewBeaconAddress = address(new Beacon(address(1024)));vm.expectRevert("ERC1967: beacon implementation is not a contract");_testing.upgradeBeaconToAndCall(newBeaconAddress, '', false);}
}
ps:
本人热爱图灵,热爱中本聪,热爱V神。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人