github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/universal/Proxy.t.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Test } from "forge-std/Test.sol";
     5  import { Proxy } from "src/universal/Proxy.sol";
     6  import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol";
     7  
     8  contract SimpleStorage {
     9      mapping(uint256 => uint256) internal store;
    10  
    11      function get(uint256 key) external payable returns (uint256) {
    12          return store[key];
    13      }
    14  
    15      function set(uint256 key, uint256 value) external payable {
    16          store[key] = value;
    17      }
    18  }
    19  
    20  contract Clasher {
    21      function upgradeTo(address) external pure {
    22          revert("upgradeTo");
    23      }
    24  }
    25  
    26  contract Proxy_Test is Test {
    27      event Upgraded(address indexed implementation);
    28      event AdminChanged(address previousAdmin, address newAdmin);
    29  
    30      address alice = address(64);
    31  
    32      bytes32 internal constant IMPLEMENTATION_KEY = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1);
    33  
    34      bytes32 internal constant OWNER_KEY = bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1);
    35  
    36      Proxy proxy;
    37      SimpleStorage simpleStorage;
    38  
    39      function setUp() external {
    40          // Deploy a proxy and simple storage contract as
    41          // the implementation
    42          proxy = new Proxy(alice);
    43          simpleStorage = new SimpleStorage();
    44  
    45          vm.prank(alice);
    46          proxy.upgradeTo(address(simpleStorage));
    47      }
    48  
    49      function test_implementationKey_succeeds() external {
    50          // The hardcoded implementation key should be correct
    51          vm.prank(alice);
    52          proxy.upgradeTo(address(6));
    53  
    54          bytes32 key = vm.load(address(proxy), IMPLEMENTATION_KEY);
    55          assertEq(address(6), Bytes32AddressLib.fromLast20Bytes(key));
    56  
    57          vm.prank(alice);
    58          address impl = proxy.implementation();
    59          assertEq(impl, address(6));
    60      }
    61  
    62      function test_ownerKey_succeeds() external {
    63          // The hardcoded owner key should be correct
    64          vm.prank(alice);
    65          proxy.changeAdmin(address(6));
    66  
    67          bytes32 key = vm.load(address(proxy), OWNER_KEY);
    68          assertEq(address(6), Bytes32AddressLib.fromLast20Bytes(key));
    69  
    70          vm.prank(address(6));
    71          address owner = proxy.admin();
    72          assertEq(owner, address(6));
    73      }
    74  
    75      function test_proxyCallToImp_notAdmin_succeeds() external {
    76          // The implementation does not have a `upgradeTo`
    77          // method, calling `upgradeTo` not as the owner
    78          // should revert.
    79          vm.expectRevert(bytes(""));
    80          proxy.upgradeTo(address(64));
    81  
    82          // Call `upgradeTo` as the owner, it should succeed
    83          // and emit the `Upgraded` event.
    84          vm.expectEmit(true, true, true, true);
    85          emit Upgraded(address(64));
    86          vm.prank(alice);
    87          proxy.upgradeTo(address(64));
    88  
    89          // Get the implementation as the owner
    90          vm.prank(alice);
    91          address impl = proxy.implementation();
    92          assertEq(impl, address(64));
    93      }
    94  
    95      function test_ownerProxyCall_notAdmin_succeeds() external {
    96          // Calling `changeAdmin` not as the owner should revert
    97          // as the implementation does not have a `changeAdmin` method.
    98          vm.expectRevert(bytes(""));
    99          proxy.changeAdmin(address(1));
   100  
   101          // Call `changeAdmin` as the owner, it should succeed
   102          // and emit the `AdminChanged` event.
   103          vm.expectEmit(true, true, true, true);
   104          emit AdminChanged(alice, address(1));
   105          vm.prank(alice);
   106          proxy.changeAdmin(address(1));
   107  
   108          // Calling `admin` not as the owner should
   109          // revert as the implementation does not have
   110          // a `admin` method.
   111          vm.expectRevert(bytes(""));
   112          proxy.admin();
   113  
   114          // Calling `admin` as the owner should work.
   115          vm.prank(address(1));
   116          address owner = proxy.admin();
   117          assertEq(owner, address(1));
   118      }
   119  
   120      function test_delegatesToImpl_succeeds() external {
   121          // Call the storage setter on the proxy
   122          SimpleStorage(address(proxy)).set(1, 1);
   123  
   124          // The key should not be set in the implementation
   125          uint256 result = simpleStorage.get(1);
   126          assertEq(result, 0);
   127          {
   128              // The key should be set in the proxy
   129              uint256 expect = SimpleStorage(address(proxy)).get(1);
   130              assertEq(expect, 1);
   131          }
   132  
   133          {
   134              // The owner should be able to call through the proxy
   135              // when there is not a function selector crash
   136              vm.prank(alice);
   137              uint256 expect = SimpleStorage(address(proxy)).get(1);
   138              assertEq(expect, 1);
   139          }
   140      }
   141  
   142      function test_upgradeToAndCall_succeeds() external {
   143          {
   144              // There should be nothing in the current proxy storage
   145              uint256 expect = SimpleStorage(address(proxy)).get(1);
   146              assertEq(expect, 0);
   147          }
   148  
   149          // Deploy a new SimpleStorage
   150          simpleStorage = new SimpleStorage();
   151  
   152          // Set the new SimpleStorage as the implementation
   153          // and call.
   154          vm.expectEmit(true, true, true, true);
   155          emit Upgraded(address(simpleStorage));
   156          vm.prank(alice);
   157          proxy.upgradeToAndCall(address(simpleStorage), abi.encodeWithSelector(simpleStorage.set.selector, 1, 1));
   158  
   159          // The call should have impacted the state
   160          uint256 result = SimpleStorage(address(proxy)).get(1);
   161          assertEq(result, 1);
   162      }
   163  
   164      function test_upgradeToAndCall_functionDoesNotExist_reverts() external {
   165          // Get the current implementation address
   166          vm.prank(alice);
   167          address impl = proxy.implementation();
   168          assertEq(impl, address(simpleStorage));
   169  
   170          // Deploy a new SimpleStorage
   171          simpleStorage = new SimpleStorage();
   172  
   173          // Set the new SimpleStorage as the implementation
   174          // and call. This reverts because the calldata doesn't
   175          // match a function on the implementation.
   176          vm.expectRevert("Proxy: delegatecall to new implementation contract failed");
   177          vm.prank(alice);
   178          proxy.upgradeToAndCall(address(simpleStorage), hex"");
   179  
   180          // The implementation address should have not
   181          // updated because the call to `upgradeToAndCall`
   182          // reverted.
   183          vm.prank(alice);
   184          address postImpl = proxy.implementation();
   185          assertEq(impl, postImpl);
   186  
   187          // The attempt to `upgradeToAndCall`
   188          // should revert when it is not called by the owner.
   189          vm.expectRevert(bytes(""));
   190          proxy.upgradeToAndCall(address(simpleStorage), abi.encodeWithSelector(simpleStorage.set.selector, 1, 1));
   191      }
   192  
   193      function test_upgradeToAndCall_isPayable_succeeds() external {
   194          // Give alice some funds
   195          vm.deal(alice, 1 ether);
   196          // Set the implementation and call and send
   197          // value.
   198          vm.prank(alice);
   199          proxy.upgradeToAndCall{ value: 1 ether }(
   200              address(simpleStorage), abi.encodeWithSelector(simpleStorage.set.selector, 1, 1)
   201          );
   202  
   203          // The implementation address should be correct
   204          vm.prank(alice);
   205          address impl = proxy.implementation();
   206          assertEq(impl, address(simpleStorage));
   207  
   208          // The proxy should have a balance
   209          assertEq(address(proxy).balance, 1 ether);
   210      }
   211  
   212      function test_upgradeTo_clashingFunctionSignatures_succeeds() external {
   213          // Clasher has a clashing function with the proxy.
   214          Clasher clasher = new Clasher();
   215  
   216          // Set the clasher as the implementation.
   217          vm.prank(alice);
   218          proxy.upgradeTo(address(clasher));
   219  
   220          {
   221              // Assert that the implementation was set properly.
   222              vm.prank(alice);
   223              address impl = proxy.implementation();
   224              assertEq(impl, address(clasher));
   225          }
   226  
   227          // Call the clashing function on the proxy
   228          // not as the owner so that the call passes through.
   229          // The implementation will revert so we can be
   230          // sure that the call passed through.
   231          vm.expectRevert(bytes("upgradeTo"));
   232          proxy.upgradeTo(address(0));
   233  
   234          {
   235              // Now call the clashing function as the owner
   236              // and be sure that it doesn't pass through to
   237              // the implementation.
   238              vm.prank(alice);
   239              proxy.upgradeTo(address(0));
   240              vm.prank(alice);
   241              address impl = proxy.implementation();
   242              assertEq(impl, address(0));
   243          }
   244      }
   245  
   246      // Allow for `eth_call` to call proxy methods
   247      // by setting "from" to `address(0)`.
   248      function test_implementation_zeroAddressCaller_succeeds() external {
   249          vm.prank(address(0));
   250          address impl = proxy.implementation();
   251          assertEq(impl, address(simpleStorage));
   252      }
   253  
   254      function test_implementation_isZeroAddress_reverts() external {
   255          // Set `address(0)` as the implementation.
   256          vm.prank(alice);
   257          proxy.upgradeTo(address(0));
   258  
   259          (bool success, bytes memory returndata) = address(proxy).call(hex"");
   260          assertEq(success, false);
   261  
   262          bytes memory err = abi.encodeWithSignature("Error(string)", "Proxy: implementation not initialized");
   263  
   264          assertEq(returndata, err);
   265      }
   266  }