Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol

  • Michael.W
  • 更新于 2024-07-08 13:55
  • 阅读 965

ERC1967Upgrade库实现了基于ERC1967标准(代理合约的slot分布)的slots读写函数,并在对应slot更新时emit出标准中相应的event。对于各种可升级合约和代理合约的实现而言,本库的作用举足轻重。

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")-1
    bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

    function _getImplementation() internal view returns (address) {
        // 读取编号为_IMPLEMENTATION_SLOT的slot中的值,并转为address类型
        // 注:StorageSlot.getAddressSlot()详解参见:https://learnblockchain.cn/article/6104
        return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    }

    // 设置逻辑合约地址为newImplementation
    function _setImplementation(address newImplementation) private {
        // 检验新的逻辑合约地址是合约地址
        // 注:Address.isContract()详解参见:https://learnblockchain.cn/article/6098
        require(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 contract
        vm.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/6098
            Address.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 call
        vm.expectEmit(address(_testing));
        emit IERC1967.Upgraded(newImplementationAddress);

        _testing.upgradeToAndCall(newImplementationAddress, '', false);
        assertEq(_testing.getImplementation(), newImplementationAddress);

        // revert if new implementation address is not a contract
        vm.expectRevert("ERC1967: new implementation is not a contract");
        _testing.upgradeToAndCall(address(1024), '', false);

        // case 2: call with no argument
        newImplementationAddress = address(new Implement());
        vm.expectEmit(address(_testing));
        emit IERC1967.Upgraded(newImplementationAddress);
        vm.expectEmit(address(_testing));
        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 contract
        vm.expectRevert("ERC1967: new implementation is not a contract");
        _testing.upgradeToAndCall(address(1024), data, false);

        // case 3: call with arguments
        newImplementationAddress = address(new Implement());
        uint arg1 = 1024;
        address arg2 = address(1024);
        string memory arg3 = "1024";
        vm.expectEmit(address(_testing));
        emit IERC1967.Upgraded(newImplementationAddress);
        vm.expectEmit(address(_testing));
        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 contract
        vm.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 Implement
        newImplementationAddress = address(new Implement());
        vm.expectEmit(address(_testing));
        emit IERC1967.Upgraded(newImplementationAddress);
        vm.expectEmit(address(_testing));
        emit IImplement.Receive();

        _testing.upgradeToAndCall(newImplementationAddress, '', true);
        assertEq(_testing.getImplementation(), newImplementationAddress);

        // revert if new implementation address is not a contract
        vm.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 data
        newImplementationAddress = address(new Implement());
        data = 'unknown';
        vm.expectEmit(address(_testing));
        emit IERC1967.Upgraded(newImplementationAddress);
        vm.expectEmit(address(_testing));
        emit IImplement.Fallback(data);

        _testing.upgradeToAndCall(newImplementationAddress, data, true);
        assertEq(_testing.getImplementation(), newImplementationAddress);

        // revert if new implementation address is not a contract
        vm.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")-1
    bytes32 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逻辑合约,直接revert
                revert("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 proxiableUUID
        bytes32 proxiableUUID = bytes32(uint(keccak256("eip1967.proxy.implementation")) - 1);

        // case 2.1.1: no call
        address 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 argument
        newImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));
        vm.expectEmit(address(_testing));
        emit IERC1967.Upgraded(newImplementERC1822ProxiableAddress);
        vm.expectEmit(address(_testing));
        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 arguments
        newImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));
        uint arg1 = 1024;
        address arg2 = address(1024);
        string memory arg3 = "1024";
        vm.expectEmit(address(_testing));
        emit IERC1967.Upgraded(newImplementERC1822ProxiableAddress);
        vm.expectEmit(address(_testing));
        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 Implement
        newImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));
        vm.expectEmit(address(_testing));
        emit IERC1967.Upgraded(newImplementERC1822ProxiableAddress);
        vm.expectEmit(address(_testing));
        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 data
        newImplementERC1822ProxiableAddress = payable(address(new ImplementERC1822Proxiable(proxiableUUID)));
        data = 'unknown';
        vm.expectEmit(address(_testing));
        emit IERC1967.Upgraded(newImplementERC1822ProxiableAddress);
        vm.expectEmit(address(_testing));
        emit IImplement.Fallback(data);

        _testing.upgradeToAndCallUUPS(newImplementERC1822ProxiableAddress, data, true);
        assertEq(_testing.getImplementation(), newImplementERC1822ProxiableAddress);

        // case 2.2: revert with unsupported proxiableUUID
        proxiableUUID = 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 compliant
        proxiableUUID = 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 contract
        vm.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")-1
    bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    function _getAdmin() internal view returns (address) {
        // 读取编号为_ADMIN_SLOT的slot中的值,并转为address类型
        // 注:StorageSlot.getAddressSlot()详解参见:https://learnblockchain.cn/article/6104
        return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
    }

    function _changeAdmin(address newAdmin) internal {
        // 按照ERC1967标准,在更换admin地址后抛出事件`AdminChanged(address,address)`
        emit AdminChanged(_getAdmin(), newAdmin);
        // 设置admin地址为newAdmin
        _setAdmin(newAdmin);
    }

    // 设置admin地址为newAdmin
    function _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")-1
    bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

    function _getBeacon() internal view returns (address) {
        // 读取编号为_BEACON_SLOT的slot中的值,并转为address类型
        // 注:StorageSlot.getAddressSlot()详解参见:https://learnblockchain.cn/article/6104
        return 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/6098
            Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
        }
    }

    // 设置信标合约地址为newBeacon
    function _setBeacon(address newBeacon) private {
        // 要求newBeacon是一个合约地址
        // 注:Address.isContract()详解参见:https://learnblockchain.cn/article/6098
        require(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 call
        address 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 argument
        newBeaconAddress = address(new Beacon(address(_implement)));
        vm.expectEmit(address(_testing));
        emit IERC1967.BeaconUpgraded(newBeaconAddress);
        vm.expectEmit(address(_testing));
        emit IImplement.InitialCallWithoutArgs();

        bytes memory data = abi.encodeCall(
            _implement.initialCallWithoutArgs,
            ()
        );
        _testing.upgradeBeaconToAndCall(newBeaconAddress, data, false);
        assertEq(_testing.getBeacon(), newBeaconAddress);

        // case 3: call with arguments
        newBeaconAddress = address(new Beacon(address(_implement)));
        uint arg1 = 1024;
        address arg2 = address(1024);
        string memory arg3 = "1024";
        vm.expectEmit(address(_testing));
        emit IERC1967.BeaconUpgraded(newBeaconAddress);
        vm.expectEmit(address(_testing));
        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 Implement
        newBeaconAddress = address(new Beacon(address(_implement)));
        vm.expectEmit(address(_testing));
        emit IERC1967.BeaconUpgraded(newBeaconAddress);
        vm.expectEmit(address(_testing));
        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 data
        newBeaconAddress = address(new Beacon(address(_implement)));
        data = 'unknown';
        vm.expectEmit(address(_testing));
        emit IERC1967.BeaconUpgraded(newBeaconAddress);
        vm.expectEmit(address(_testing));
        emit IImplement.Fallback(data);

        _testing.upgradeBeaconToAndCall(newBeaconAddress, data, true);
        assertEq(_testing.getBeacon(), newBeaconAddress);

        // revert if new beacon address is not a contract
        vm.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 contract
        newBeaconAddress = address(new Beacon(address(1024)));
        vm.expectRevert("ERC1967: beacon implementation is not a contract");
        _testing.upgradeBeaconToAndCall(newBeaconAddress, '', false);
    }
}

ps: 本人热爱图灵,热爱中本聪,热爱V神。 以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。 同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下! 如果需要转发,麻烦注明作者。十分感谢!

1.jpeg

公众号名称:后现代泼痞浪漫主义奠基人

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Michael.W
Michael.W
0x93E7...0000
狂热的区块链爱好者