Commit 14dd80f3 authored by Mark Tyneway's avatar Mark Tyneway Committed by GitHub

contracts bedrock: implement simple proxy + admin (#2728)

* ci: better check for snapshot diff

* contracts-bedrock: unified solmate import

* contracts-bedrock: fix solmate import

* contracts-bedrock: implement Proxy

* contracts-bedrock: implement ProxyAdmin

* bedrock: snapshot

* contracts: make admin work with legacy proxies

* contracts-bedrock: review fixes

* contracts-bedrock: linting

* contracts-bedrock: lint

* contracts-bedrock: review fixes

* contracts-bedrock: snapshot

* contracts-bedrock: rename

* contracts-bedrock: renames

* contracts-bedrock: renaming
parent da1633a3
---
'@eth-optimism/contracts-bedrock': patch
---
Add proxy contract
...@@ -406,7 +406,9 @@ jobs: ...@@ -406,7 +406,9 @@ jobs:
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run: - run:
name: gas snapshot name: gas snapshot
command: forge snapshot && git diff --exit-code command: |
forge --version
forge snapshot && git diff --exit-code .gas-snapshot
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run: - run:
name: check go bindings name: check go bindings
......
...@@ -114,6 +114,40 @@ OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation() ...@@ -114,6 +114,40 @@ OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation()
OptimismPortal_Test:test_depositTransaction_withEthValueFromContract() (gas: 77478) OptimismPortal_Test:test_depositTransaction_withEthValueFromContract() (gas: 77478)
OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA() (gas: 78049) OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA() (gas: 78049)
OptimismPortal_Test:test_invalidWithdrawalProof() (gas: 28541) OptimismPortal_Test:test_invalidWithdrawalProof() (gas: 28541)
Proxy_Test:test_clashingFunctionSignatures() (gas: 101427)
Proxy_Test:test_implementationKey() (gas: 20942)
Proxy_Test:test_implementationProxyCallIfNotAdmin() (gas: 30021)
Proxy_Test:test_implementationZeroAddress() (gas: 48006)
Proxy_Test:test_itDelegatesToTheImplementation() (gas: 45173)
Proxy_Test:test_ownerKey() (gas: 19113)
Proxy_Test:test_ownerProxyCallIfNotAdmin() (gas: 34733)
Proxy_Test:test_payableUpgradeToAndCall() (gas: 53887)
Proxy_Test:test_revertUpgradeToAndCall() (gas: 104501)
Proxy_Test:test_upgradeToAndCall() (gas: 125238)
Proxy_Test:test_zeroAddressCaller() (gas: 14758)
ProxyAdmin_Test:test_chugsplashChangeProxyAdmin() (gas: 35994)
ProxyAdmin_Test:test_chugsplashGetProxyAdmin() (gas: 16107)
ProxyAdmin_Test:test_chugsplashGetProxyImplementation() (gas: 51947)
ProxyAdmin_Test:test_chugsplashUpgrade() (gas: 49443)
ProxyAdmin_Test:test_chugsplashUpgradeAndCall() (gas: 82729)
ProxyAdmin_Test:test_delegateResolvedChangeProxyAdmin() (gas: 33846)
ProxyAdmin_Test:test_delegateResolvedGetProxyAdmin() (gas: 18102)
ProxyAdmin_Test:test_delegateResolvedGetProxyImplementation() (gas: 63364)
ProxyAdmin_Test:test_delegateResolvedUpgrade() (gas: 59156)
ProxyAdmin_Test:test_delegateResolvedUpgradeAndCall() (gas: 98627)
ProxyAdmin_Test:test_isUpgrading() (gas: 19531)
ProxyAdmin_Test:test_onlyOwner() (gas: 22672)
ProxyAdmin_Test:test_onlyOwnerSetAddressManager() (gas: 10622)
ProxyAdmin_Test:test_onlyOwnerSetImplementationName() (gas: 11113)
ProxyAdmin_Test:test_onlyOwnerSetProxyType() (gas: 10774)
ProxyAdmin_Test:test_openZeppelinChangeProxyAdmin() (gas: 34248)
ProxyAdmin_Test:test_openZeppelinGetProxyAdmin() (gas: 16090)
ProxyAdmin_Test:test_openZeppelinGetProxyImplementation() (gas: 52950)
ProxyAdmin_Test:test_openZeppelinUpgrade() (gas: 50507)
ProxyAdmin_Test:test_openZeppelinUpgradeAndCall() (gas: 79435)
ProxyAdmin_Test:test_owner() (gas: 9796)
ProxyAdmin_Test:test_proxyType() (gas: 20644)
ProxyAdmin_Test:test_setImplementationName() (gas: 38957)
ResourceMetering_Test:test_initialResourceParams() (gas: 8986) ResourceMetering_Test:test_initialResourceParams() (gas: 8986)
ResourceMetering_Test:test_updateNoGasDelta() (gas: 2008269) ResourceMetering_Test:test_updateNoGasDelta() (gas: 2008269)
ResourceMetering_Test:test_updateOneEmptyBlock() (gas: 18078) ResourceMetering_Test:test_updateOneEmptyBlock() (gas: 18078)
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
/* External Imports */
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title Lib_AddressManager
*/
contract Lib_AddressManager is Ownable {
/**********
* Events *
**********/
event AddressSet(string indexed _name, address _newAddress, address _oldAddress);
/*************
* Variables *
*************/
mapping(bytes32 => address) private addresses;
/********************
* Public Functions *
********************/
/**
* Changes the address associated with a particular name.
* @param _name String name to associate an address with.
* @param _address Address to associate with the name.
*/
function setAddress(string memory _name, address _address) external onlyOwner {
bytes32 nameHash = _getNameHash(_name);
address oldAddress = addresses[nameHash];
addresses[nameHash] = _address;
emit AddressSet(_name, _address, oldAddress);
}
/**
* Retrieves the address associated with a given name.
* @param _name Name to retrieve an address for.
* @return Address associated with the given name.
*/
function getAddress(string memory _name) external view returns (address) {
return addresses[_getNameHash(_name)];
}
/**********************
* Internal Functions *
**********************/
/**
* Computes the hash of a name.
* @param _name Name to compute a hash for.
* @return Hash of the given name.
*/
function _getNameHash(string memory _name) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(_name));
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
/* Library Imports */
import { Lib_AddressManager } from "./Lib_AddressManager.sol";
/**
* @title Lib_AddressResolver
*/
abstract contract Lib_AddressResolver {
/*************
* Variables *
*************/
Lib_AddressManager public libAddressManager;
/***************
* Constructor *
***************/
/**
* @param _libAddressManager Address of the Lib_AddressManager.
*/
constructor(address _libAddressManager) {
libAddressManager = Lib_AddressManager(_libAddressManager);
}
/********************
* Public Functions *
********************/
/**
* Resolves the address associated with a given name.
* @param _name Name to resolve an address for.
* @return Address associated with the given name.
*/
function resolve(string memory _name) public view returns (address) {
return libAddressManager.getAddress(_name);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { Lib_AddressManager } from "./Lib_AddressManager.sol";
/**
* @title Lib_ResolvedDelegateProxy
*/
contract Lib_ResolvedDelegateProxy {
/*************
* Variables *
*************/
// Using mappings to store fields to avoid overwriting storage slots in the
// implementation contract. For example, instead of storing these fields at
// storage slot `0` & `1`, they are stored at `keccak256(key + slot)`.
// See: https://solidity.readthedocs.io/en/v0.7.0/internals/layout_in_storage.html
// NOTE: Do not use this code in your own contract system.
// There is a known flaw in this contract, and we will remove it from the repository
// in the near future. Due to the very limited way that we are using it, this flaw is
// not an issue in our system.
mapping(address => string) private implementationName;
mapping(address => Lib_AddressManager) private addressManager;
/***************
* Constructor *
***************/
/**
* @param _libAddressManager Address of the Lib_AddressManager.
* @param _implementationName implementationName of the contract to proxy to.
*/
constructor(address _libAddressManager, string memory _implementationName) {
addressManager[address(this)] = Lib_AddressManager(_libAddressManager);
implementationName[address(this)] = _implementationName;
}
/*********************
* Fallback Function *
*********************/
fallback() external payable {
address target = addressManager[address(this)].getAddress(
(implementationName[address(this)])
);
require(target != address(0), "Target address must be initialized.");
// slither-disable-next-line controlled-delegatecall
(bool success, bytes memory returndata) = target.delegatecall(msg.data);
if (success == true) {
assembly {
return(add(returndata, 0x20), mload(returndata))
}
} else {
assembly {
revert(add(returndata, 0x20), mload(returndata))
}
}
}
}
// SPDX-License-Identifier: Unlicense // SPDX-License-Identifier: Unlicense
pragma solidity >=0.8.0; pragma solidity >=0.8.0;
import {Bytes32AddressLib} from "solmate/utils/Bytes32AddressLib.sol"; import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol";
// prettier-ignore // prettier-ignore
library LibRLP { library LibRLP {
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { Test } from "forge-std/Test.sol";
import { Proxy } from "../universal/Proxy.sol";
import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol";
contract SimpleStorage {
mapping(uint256 => uint256) internal store;
function get(uint256 key) external payable returns (uint256) {
return store[key];
}
function set(uint256 key, uint256 value) external payable {
store[key] = value;
}
}
contract Clasher {
function upgradeTo(address _implementation) external view {
revert("upgradeTo");
}
}
contract Proxy_Test is Test {
event Upgraded(address indexed implementation);
event AdminChanged(address previousAdmin, address newAdmin);
address alice = address(64);
bytes32 internal constant IMPLEMENTATION_KEY =
bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1);
bytes32 internal constant OWNER_KEY =
bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1);
Proxy proxy;
SimpleStorage simpleStorage;
function setUp() external {
// Deploy a proxy and simple storage contract as
// the implementation
proxy = new Proxy(alice);
simpleStorage = new SimpleStorage();
vm.prank(alice);
proxy.upgradeTo(address(simpleStorage));
}
function test_implementationKey() external {
// The hardcoded implementation key should be correct
vm.prank(alice);
proxy.upgradeTo(address(6));
bytes32 key = vm.load(address(proxy), IMPLEMENTATION_KEY);
assertEq(
address(6),
Bytes32AddressLib.fromLast20Bytes(key)
);
vm.prank(alice);
address impl = proxy.implementation();
assertEq(impl, address(6));
}
function test_ownerKey() external {
// The hardcoded owner key should be correct
vm.prank(alice);
proxy.changeAdmin(address(6));
bytes32 key = vm.load(address(proxy), OWNER_KEY);
assertEq(
address(6),
Bytes32AddressLib.fromLast20Bytes(key)
);
vm.prank(address(6));
address owner = proxy.admin();
assertEq(owner, address(6));
}
function test_implementationProxyCallIfNotAdmin() external {
// The implementation does not have a `upgradeTo`
// method, calling `upgradeTo` not as the owner
// should revert.
vm.expectRevert();
proxy.upgradeTo(address(64));
// Call `upgradeTo` as the owner, it should succeed
// and emit the `Upgraded` event.
vm.expectEmit(true, true, true, true);
emit Upgraded(address(64));
vm.prank(alice);
proxy.upgradeTo(address(64));
// Get the implementation as the owner
vm.prank(alice);
address impl = proxy.implementation();
assertEq(impl, address(64));
}
function test_ownerProxyCallIfNotAdmin() external {
// Calling `changeAdmin` not as the owner should revert
// as the implementation does not have a `changeAdmin` method.
vm.expectRevert();
proxy.changeAdmin(address(1));
// Call `changeAdmin` as the owner, it should succeed
// and emit the `AdminChanged` event.
vm.expectEmit(true, true, true, true);
emit AdminChanged(alice, address(1));
vm.prank(alice);
proxy.changeAdmin(address(1));
// Calling `admin` not as the owner should
// revert as the implementation does not have
// a `admin` method.
vm.expectRevert();
proxy.admin();
// Calling `admin` as the owner should work.
vm.prank(address(1));
address owner = proxy.admin();
assertEq(owner, address(1));
}
function test_itDelegatesToTheImplementation() external {
// Call the storage setter on the proxy
SimpleStorage(address(proxy)).set(1, 1);
// The key should not be set in the implementation
uint256 result = simpleStorage.get(1);
assertEq(result, 0);
{
// The key should be set in the proxy
uint256 expect = SimpleStorage(address(proxy)).get(1);
assertEq(expect, 1);
}
{
// The owner should be able to call through the proxy
// when there is not a function selector crash
vm.prank(alice);
uint256 expect = SimpleStorage(address(proxy)).get(1);
assertEq(expect, 1);
}
}
function test_upgradeToAndCall() external {
{
// There should be nothing in the current proxy storage
uint256 result = SimpleStorage(address(proxy)).get(1);
assertEq(result, 0);
}
// Deploy a new SimpleStorage
simpleStorage = new SimpleStorage();
// Set the new SimpleStorage as the implementation
// and call.
vm.expectEmit(true, true, true, true);
emit Upgraded(address(simpleStorage));
vm.prank(alice);
proxy.upgradeToAndCall(
address(simpleStorage),
abi.encodeWithSelector(simpleStorage.set.selector, 1, 1)
);
// The call should have impacted the state
uint256 result = SimpleStorage(address(proxy)).get(1);
assertEq(result, 1);
}
function test_revertUpgradeToAndCall() external {
// Get the current implementation address
vm.prank(alice);
address impl = proxy.implementation();
assertEq(impl, address(simpleStorage));
// Deploy a new SimpleStorage
simpleStorage = new SimpleStorage();
// Set the new SimpleStorage as the implementation
// and call. This reverts because the calldata doesn't
// match a function on the implementation.
vm.expectRevert();
vm.prank(alice);
proxy.upgradeToAndCall(
address(simpleStorage),
hex""
);
// The implementation address should have not
// updated because the call to `upgradeToAndCall`
// reverted.
vm.prank(alice);
address postImpl = proxy.implementation();
assertEq(impl, postImpl);
// The attempt to `upgradeToAndCall`
// should revert when it is not called by the owner.
vm.expectRevert();
proxy.upgradeToAndCall(
address(simpleStorage),
abi.encodeWithSelector(simpleStorage.set.selector, 1, 1)
);
}
function test_payableUpgradeToAndCall() external {
// Give alice some funds
vm.deal(alice, 1 ether);
// Set the implementation and call and send
// value.
vm.prank(alice);
proxy.upgradeToAndCall{ value: 1 ether }(
address(simpleStorage),
abi.encodeWithSelector(simpleStorage.set.selector, 1, 1)
);
// The implementation address should be correct
vm.prank(alice);
address impl = proxy.implementation();
assertEq(impl, address(simpleStorage));
// The proxy should have a balance
assertEq(address(proxy).balance, 1 ether);
}
function test_clashingFunctionSignatures() external {
// Clasher has a clashing function with the proxy.
Clasher clasher = new Clasher();
// Set the clasher as the implementation.
vm.prank(alice);
proxy.upgradeTo(address(clasher));
{
// Assert that the implementation was set properly.
vm.prank(alice);
address impl = proxy.implementation();
assertEq(impl, address(clasher));
}
// Call the clashing function on the proxy
// not as the owner so that the call passes through.
// The implementation will revert so we can be
// sure that the call passed through.
vm.expectRevert(bytes("upgradeTo"));
proxy.upgradeTo(address(0));
{
// Now call the clashing function as the owner
// and be sure that it doesn't pass through to
// the implementation.
vm.prank(alice);
proxy.upgradeTo(address(0));
vm.prank(alice);
address impl = proxy.implementation();
assertEq(impl, address(0));
}
}
// Allow for `eth_call` to call proxy methods
// by setting "from" to `address(0)`.
function test_zeroAddressCaller() external {
vm.prank(address(0));
address impl = proxy.implementation();
assertEq(impl, address(simpleStorage));
}
function test_implementationZeroAddress() external {
// Set `address(0)` as the implementation.
vm.prank(alice);
proxy.upgradeTo(address(0));
(bool success, bytes memory returndata) = address(proxy).call(hex"");
assertEq(success, false);
bytes memory err = abi.encodeWithSignature(
"Error(string)",
"Proxy: implementation not initialized"
);
assertEq(returndata, err);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { Test } from "forge-std/Test.sol";
import { Proxy } from "../universal/Proxy.sol";
import { ProxyAdmin } from "../universal/ProxyAdmin.sol";
import { SimpleStorage } from "./Proxy.t.sol";
import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol";
import { Lib_ResolvedDelegateProxy } from "../legacy/Lib_ResolvedDelegateProxy.sol";
import { Lib_AddressManager } from "../legacy/Lib_AddressManager.sol";
contract ProxyAdmin_Test is Test {
address alice = address(64);
Proxy proxy;
L1ChugSplashProxy chugsplash;
Lib_ResolvedDelegateProxy resolved;
Lib_AddressManager addressManager;
ProxyAdmin admin;
SimpleStorage implementation;
function setUp() external {
// Deploy the proxy admin
admin = new ProxyAdmin(alice);
// Deploy the standard proxy
proxy = new Proxy(address(admin));
// Deploy the legacy L1ChugSplashProxy with the admin as the owner
chugsplash = new L1ChugSplashProxy(address(admin));
// Deploy the legacy Lib_AddressManager
addressManager = new Lib_AddressManager();
// The proxy admin must be the new owner of the address manager
addressManager.transferOwnership(address(admin));
// Deploy a legacy Lib_ResolvedDelegateProxy with the name `a`.
// Whatever `a` is set to in Lib_AddressManager will be the address
// that is used for the implementation.
resolved = new Lib_ResolvedDelegateProxy(address(addressManager), "a");
// Set the address of the address manager in the admin so that it
// can resolve the implementation address of legacy
// Lib_ResolvedDelegateProxy based proxies.
vm.prank(alice);
admin.setAddressManager(address(addressManager));
// Set the reverse lookup of the Lib_ResolvedDelegateProxy
// proxy
vm.prank(alice);
admin.setImplementationName(address(resolved), "a");
// Set the proxy types
vm.prank(alice);
admin.setProxyType(address(chugsplash), ProxyAdmin.ProxyType.Chugsplash);
vm.prank(alice);
admin.setProxyType(address(resolved), ProxyAdmin.ProxyType.ResolvedDelegate);
implementation = new SimpleStorage();
}
function test_setImplementationName() external {
vm.prank(alice);
admin.setImplementationName(address(1), "foo");
assertEq(
admin.implementationName(address(1)),
"foo"
);
}
function test_onlyOwnerSetAddressManager() external {
vm.expectRevert("UNAUTHORIZED");
admin.setAddressManager(address(0));
}
function test_onlyOwnerSetImplementationName() external {
vm.expectRevert("UNAUTHORIZED");
admin.setImplementationName(address(0), "foo");
}
function test_onlyOwnerSetProxyType() external {
vm.expectRevert("UNAUTHORIZED");
admin.setProxyType(address(0), ProxyAdmin.ProxyType.Chugsplash);
}
function test_owner() external {
assertEq(admin.owner(), alice);
}
function test_proxyType() external {
assertEq(
uint256(admin.proxyType(address(proxy))),
uint256(ProxyAdmin.ProxyType.OpenZeppelin)
);
assertEq(
uint256(admin.proxyType(address(chugsplash))),
uint256(ProxyAdmin.ProxyType.Chugsplash)
);
assertEq(
uint256(admin.proxyType(address(resolved))),
uint256(ProxyAdmin.ProxyType.ResolvedDelegate)
);
}
function test_openZeppelinGetProxyImplementation() external {
getProxyImplementation(proxy);
}
function test_chugsplashGetProxyImplementation() external {
getProxyImplementation(Proxy(payable(chugsplash)));
}
function test_delegateResolvedGetProxyImplementation() external {
getProxyImplementation(Proxy(payable(resolved)));
}
function getProxyImplementation(Proxy _proxy) internal {
{
address impl = admin.getProxyImplementation(_proxy);
assertEq(impl, address(0));
}
vm.prank(alice);
admin.upgrade(_proxy, address(implementation));
{
address impl = admin.getProxyImplementation(_proxy);
assertEq(impl, address(implementation));
}
}
function test_openZeppelinGetProxyAdmin() external {
getProxyAdmin(proxy);
}
function test_chugsplashGetProxyAdmin() external {
getProxyAdmin(Proxy(payable(chugsplash)));
}
function test_delegateResolvedGetProxyAdmin() external {
getProxyAdmin(Proxy(payable(resolved)));
}
function getProxyAdmin(Proxy _proxy) internal {
address owner = admin.getProxyAdmin(_proxy);
assertEq(owner, address(admin));
}
function test_openZeppelinChangeProxyAdmin() external {
changeProxyAdmin(proxy);
}
function test_chugsplashChangeProxyAdmin() external {
changeProxyAdmin(Proxy(payable(chugsplash)));
}
function test_delegateResolvedChangeProxyAdmin() external {
changeProxyAdmin(Proxy(payable(resolved)));
}
function changeProxyAdmin(Proxy _proxy) internal {
ProxyAdmin.ProxyType proxyType = admin.proxyType(address(_proxy));
vm.prank(alice);
admin.changeProxyAdmin(_proxy, address(128));
// The proxy is not longer the admin and can
// no longer call the proxy interface except for
// the ResolvedDelegate type which anybody can call
// the admin interface
if (proxyType != ProxyAdmin.ProxyType.ResolvedDelegate) {
vm.expectRevert();
admin.getProxyAdmin(_proxy);
}
// Call the proxy contract directly to get the admin.
// Different proxy types have different interfaces.
vm.prank(address(128));
if (proxyType == ProxyAdmin.ProxyType.OpenZeppelin) {
assertEq(_proxy.admin(), address(128));
} else if (proxyType == ProxyAdmin.ProxyType.Chugsplash) {
assertEq(
L1ChugSplashProxy(payable(_proxy)).getOwner(),
address(128)
);
} else if (proxyType == ProxyAdmin.ProxyType.ResolvedDelegate) {
assertEq(
addressManager.owner(),
address(128)
);
} else {
assert(false);
}
}
function test_openZeppelinUpgrade() external {
upgrade(proxy);
}
function test_chugsplashUpgrade() external {
upgrade(Proxy(payable(chugsplash)));
}
function test_delegateResolvedUpgrade() external {
upgrade(Proxy(payable(resolved)));
}
function upgrade(Proxy _proxy) internal {
vm.prank(alice);
admin.upgrade(_proxy, address(implementation));
address impl = admin.getProxyImplementation(_proxy);
assertEq(impl, address(implementation));
}
function test_openZeppelinUpgradeAndCall() external {
upgradeAndCall(proxy);
}
function test_chugsplashUpgradeAndCall() external {
upgradeAndCall(Proxy(payable(chugsplash)));
}
function test_delegateResolvedUpgradeAndCall() external {
upgradeAndCall(Proxy(payable(resolved)));
}
function upgradeAndCall(Proxy _proxy) internal {
vm.prank(alice);
admin.upgradeAndCall(
_proxy,
address(implementation),
abi.encodeWithSelector(SimpleStorage.set.selector, 1, 1)
);
address impl = admin.getProxyImplementation(_proxy);
assertEq(impl, address(implementation));
uint256 got = SimpleStorage(address(_proxy)).get(1);
assertEq(got, 1);
}
function test_onlyOwner() external {
vm.expectRevert("UNAUTHORIZED");
admin.changeProxyAdmin(proxy, address(0));
vm.expectRevert("UNAUTHORIZED");
admin.upgrade(proxy, address(implementation));
vm.expectRevert("UNAUTHORIZED");
admin.upgradeAndCall(proxy, address(implementation), hex"");
}
function test_isUpgrading() external {
assertEq(false, admin.isUpgrading());
vm.prank(alice);
admin.setUpgrading(true);
assertEq(true, admin.isUpgrading());
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
/**
* @title Proxy
* @notice Proxy is a transparent proxy that passes through the call
* if the caller is the owner or if the caller is `address(0)`,
* meaning that the call originated from an offchain simulation.
*/
contract Proxy {
/**
* @notice An event that is emitted each time the implementation is changed.
* This event is part of the EIP 1967 spec.
*
* @param implementation The address of the implementation contract
*/
event Upgraded(address indexed implementation);
/**
* @notice An event that is emitted each time the owner is upgraded.
* This event is part of the EIP 1967 spec.
*
* @param previousAdmin The previous owner of the contract
* @param newAdmin The new owner of the contract
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @notice The storage slot that holds the address of the implementation.
* bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)
*/
bytes32 internal constant IMPLEMENTATION_KEY =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @notice The storage slot that holds the address of the owner.
* bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)
*/
bytes32 internal constant OWNER_KEY =
0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @notice set the initial owner during contract deployment. The
* owner is stored at the eip1967 owner storage slot so that
* storage collision with the implementation is not possible.
*
* @param _admin Address of the initial contract owner. The owner has
* the ability to access the transparent proxy interface.
*/
constructor(address _admin) {
_changeAdmin(_admin);
}
// slither-disable-next-line locked-ether
fallback() external payable {
// Proxy call by default.
_doProxyCall();
}
/**
* @notice A modifier that reverts if not called by the owner
* or by `address(0)` to allow `eth_call` to interact
* with the proxy without needing to use low level storage
* inspection. It is assumed that nobody controls the private
* key for `address(0)`.
*/
modifier proxyCallIfNotAdmin() {
if (msg.sender == _getAdmin() || msg.sender == address(0)) {
_;
} else {
// This WILL halt the call frame on completion.
_doProxyCall();
}
}
/**
* @notice Set the implementation contract address. The code at this
* address will execute when this contract is called.
*
* @param _implementation The address of the implementation contract
*/
function upgradeTo(address _implementation) external proxyCallIfNotAdmin {
_setImplementation(_implementation);
}
/**
* @notice Set the implementation and call a function in a single
* transaction. This is useful to ensure atomic `initialize()`
* based upgrades.
*
* @param _implementation The address of the implementation contract
* @param _data The calldata to delegatecall the new
* implementation with
*/
function upgradeToAndCall(address _implementation, bytes calldata _data)
external
payable
proxyCallIfNotAdmin
returns (bytes memory)
{
_setImplementation(_implementation);
(bool success, bytes memory returndata) = _implementation.delegatecall(_data);
require(success);
return returndata;
}
/**
* @notice Changes the owner of the proxy contract. Only callable by the owner.
*
* @param _admin New owner of the proxy contract.
*/
function changeAdmin(address _admin) external proxyCallIfNotAdmin {
_changeAdmin(_admin);
}
/**
* @notice Gets the owner of the proxy contract.
*
* @return Owner address.
*/
function admin() external proxyCallIfNotAdmin returns (address) {
return _getAdmin();
}
/**
* @notice Queries the implementation address.
*
* @return Implementation address.
*/
function implementation() external proxyCallIfNotAdmin returns (address) {
return _getImplementation();
}
/**
* @notice Sets the implementation address.
*
* @param _implementation New implementation address.
*/
function _setImplementation(address _implementation) internal {
assembly {
sstore(IMPLEMENTATION_KEY, _implementation)
}
emit Upgraded(_implementation);
}
/**
* @notice Queries the implementation address.
*
* @return implementation address.
*/
function _getImplementation() internal view returns (address) {
address implementation;
assembly {
implementation := sload(IMPLEMENTATION_KEY)
}
return implementation;
}
/**
* @notice Changes the owner of the proxy contract.
*
* @param _admin New owner of the proxy contract.
*/
function _changeAdmin(address _admin) internal {
address previous = _getAdmin();
assembly {
sstore(OWNER_KEY, _admin)
}
emit AdminChanged(previous, _admin);
}
/**
* @notice Queries the owner of the proxy contract.
*
* @return owner address.
*/
function _getAdmin() internal view returns (address) {
address owner;
assembly {
owner := sload(OWNER_KEY)
}
return owner;
}
/**
* @notice Performs the proxy call via a delegatecall.
*/
function _doProxyCall() internal {
address implementation = _getImplementation();
require(implementation != address(0), "Proxy: implementation not initialized");
assembly {
// Copy calldata into memory at 0x0....calldatasize.
calldatacopy(0x0, 0x0, calldatasize())
// Perform the delegatecall, make sure to pass all available gas.
let success := delegatecall(gas(), implementation, 0x0, calldatasize(), 0x0, 0x0)
// Copy returndata into memory at 0x0....returndatasize. Note that this *will*
// overwrite the calldata that we just copied into memory but that doesn't really
// matter because we'll be returning in a second anyway.
returndatacopy(0x0, 0x0, returndatasize())
// Success == 0 means a revert. We'll revert too and pass the data up.
if iszero(success) {
revert(0x0, returndatasize())
}
// Otherwise we'll just return and pass the data up.
return(0x0, returndatasize())
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { Proxy } from "./Proxy.sol";
import { Owned } from "@rari-capital/solmate/src/auth/Owned.sol";
import { Lib_AddressManager } from "../legacy/Lib_AddressManager.sol";
import { L1ChugSplashProxy } from "../legacy/L1ChugSplashProxy.sol";
/**
* @title ProxyAdmin
* @dev This is an auxiliary contract meant to be assigned as the admin of a Proxy, based on
* the OpenZeppelin implementation. It has backwards compatibility logic to work with the
* various types of proxies that have been deployed by Optimism.
*/
contract ProxyAdmin is Owned {
/**
* @notice The proxy types that the ProxyAdmin can manage.
*
* @custom:value OpenZeppelin Represents the OpenZeppelin style transparent proxy
* interface, this is the standard.
* @custom:value Chugsplash Represents the Chugsplash proxy interface,
* this is legacy.
* @custom:value ResolvedDelegate Represents the ResolvedDelegate proxy
* interface, this is legacy.
*/
enum ProxyType {
OpenZeppelin,
Chugsplash,
ResolvedDelegate
}
/**
* @custom:legacy
* @notice A mapping of proxy types, used for backwards compatibility.
*/
mapping(address => ProxyType) public proxyType;
/**
* @custom:legacy
* @notice A reverse mapping of addresses to names held in the AddressManager. This must be
* manually kept up to date with changes in the AddressManager for this contract
* to be able to work as an admin for the Lib_ResolvedDelegateProxy type.
*/
mapping(address => string) public implementationName;
/**
* @custom:legacy
* @notice The address of the address manager, this is required to manage the
* Lib_ResolvedDelegateProxy type.
*/
Lib_AddressManager public addressManager;
/**
* @custom:legacy
* @notice A legacy upgrading indicator used by the old Chugsplash Proxy.
*/
bool internal upgrading = false;
/**
* @notice Set the owner of the ProxyAdmin via constructor argument.
*/
constructor(address owner) Owned(owner) {}
/**
* @notice
*
* @param _address The address of the proxy.
* @param _type The type of the proxy.
*/
function setProxyType(address _address, ProxyType _type) external onlyOwner {
proxyType[_address] = _type;
}
/**
* @notice Set the proxy type in the mapping. This needs to be kept up to date by the owner of
* the contract.
*
* @param _address The address to be named.
* @param _name The name of the address.
*/
function setImplementationName(address _address, string memory _name) external onlyOwner {
implementationName[_address] = _name;
}
/**
* @notice Set the address of the address manager. This is required to manage the legacy
* `Lib_ResolvedDelegateProxy`.
*
* @param _address The address of the address manager.
*/
function setAddressManager(address _address) external onlyOwner {
addressManager = Lib_AddressManager(_address);
}
/**
* @custom:legacy
* @notice Set an address in the address manager. This is required because only the owner of
* the AddressManager can set the addresses in it.
*
* @param _name The name of the address to set in the address manager.
* @param _address The address to set in the address manager.
*/
function setAddress(string memory _name, address _address) external onlyOwner {
addressManager.setAddress(_name, _address);
}
/**
* @custom:legacy
* @notice Legacy function used by the old Chugsplash proxy to determine if an upgrade is
* happening.
*
* @return Whether or not there is an upgrade going on
*/
function isUpgrading() external view returns (bool) {
return upgrading;
}
/**
* @custom:legacy
* @notice Set the upgrading status for the Chugsplash proxy type.
*
* @param _upgrading Whether or not the system is upgrading.
*/
function setUpgrading(bool _upgrading) external onlyOwner {
upgrading = _upgrading;
}
/**
* @dev Returns the current implementation of `proxy`.
* This contract must be the admin of `proxy`.
*
* @param proxy The Proxy to return the implementation of.
* @return The address of the implementation.
*/
function getProxyImplementation(Proxy proxy) external view returns (address) {
ProxyType proxyType = proxyType[address(proxy)];
// We need to manually run the static call since the getter cannot be flagged as view
address target;
bytes memory data;
if (proxyType == ProxyType.OpenZeppelin) {
target = address(proxy);
data = abi.encodeWithSelector(Proxy.implementation.selector);
} else if (proxyType == ProxyType.Chugsplash) {
target = address(proxy);
data = abi.encodeWithSelector(L1ChugSplashProxy.getImplementation.selector);
} else if (proxyType == ProxyType.ResolvedDelegate) {
target = address(addressManager);
data = abi.encodeWithSelector(
Lib_AddressManager.getAddress.selector,
implementationName[address(proxy)]
);
} else {
revert("ProxyAdmin: unknown proxy type");
}
(bool success, bytes memory returndata) = target.staticcall(data);
require(success);
return abi.decode(returndata, (address));
}
/**
* @dev Returns the current admin of `proxy`.
* This contract must be the admin of `proxy`.
*
* @param proxy The Proxy to return the admin of.
* @return The address of the admin.
*/
function getProxyAdmin(Proxy proxy) external view returns (address) {
ProxyType proxyType = proxyType[address(proxy)];
// We need to manually run the static call since the getter cannot be flagged as view
address target;
bytes memory data;
if (proxyType == ProxyType.OpenZeppelin) {
target = address(proxy);
data = abi.encodeWithSelector(Proxy.admin.selector);
} else if (proxyType == ProxyType.Chugsplash) {
target = address(proxy);
data = abi.encodeWithSelector(L1ChugSplashProxy.getOwner.selector);
} else if (proxyType == ProxyType.ResolvedDelegate) {
target = address(addressManager);
data = abi.encodeWithSignature("owner()");
} else {
revert("ProxyAdmin: unknown proxy type");
}
(bool success, bytes memory returndata) = target.staticcall(data);
require(success);
return abi.decode(returndata, (address));
}
/**
* @dev Changes the admin of `proxy` to `newAdmin`. This contract must be the current admin
* of `proxy`.
*
* @param proxy The proxy that will have its admin updated.
* @param newAdmin The address of the admin to update to.
*/
function changeProxyAdmin(Proxy proxy, address newAdmin) external onlyOwner {
ProxyType proxyType = proxyType[address(proxy)];
if (proxyType == ProxyType.OpenZeppelin) {
proxy.changeAdmin(newAdmin);
} else if (proxyType == ProxyType.Chugsplash) {
L1ChugSplashProxy(payable(proxy)).setOwner(newAdmin);
} else if (proxyType == ProxyType.ResolvedDelegate) {
Lib_AddressManager(addressManager).transferOwnership(newAdmin);
}
}
/**
* @dev Upgrades `proxy` to `implementation`. This contract must be the admin of `proxy`.
*
* @param proxy The address of the proxy.
* @param implementation The address of the implementation.
*/
function upgrade(Proxy proxy, address implementation) public onlyOwner {
ProxyType proxyType = proxyType[address(proxy)];
if (proxyType == ProxyType.OpenZeppelin) {
proxy.upgradeTo(implementation);
} else if (proxyType == ProxyType.Chugsplash) {
L1ChugSplashProxy(payable(proxy)).setStorage(
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc,
bytes32(uint256(uint160(implementation)))
);
} else if (proxyType == ProxyType.ResolvedDelegate) {
string memory name = implementationName[address(proxy)];
Lib_AddressManager(addressManager).setAddress(name, implementation);
}
}
/**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation.
* This contract must be the admin of `proxy`.
*
* @param proxy The proxy to call.
* @param implementation The implementation to upgrade the proxy to.
* @param data The calldata to pass to the implementation.
*/
function upgradeAndCall(
Proxy proxy,
address implementation,
bytes memory data
) external payable onlyOwner {
ProxyType proxyType = proxyType[address(proxy)];
if (proxyType == ProxyType.OpenZeppelin) {
proxy.upgradeToAndCall{ value: msg.value }(implementation, data);
} else {
upgrade(proxy, implementation);
(bool success, ) = address(proxy).call{ value: msg.value }(data);
require(success);
}
}
}
...@@ -8,7 +8,7 @@ remappings = [ ...@@ -8,7 +8,7 @@ remappings = [
'@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/', '@openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/',
'@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/', '@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/',
'excessively-safe-call/=node_modules/excessively-safe-call/src/', 'excessively-safe-call/=node_modules/excessively-safe-call/src/',
'solmate/=node_modules/@rari-capital/solmate/src', '@rari-capital/solmate/=node_modules/@rari-capital/solmate',
'forge-std/=node_modules/forge-std/src', 'forge-std/=node_modules/forge-std/src',
'ds-test/=node_modules/ds-test/src' 'ds-test/=node_modules/ds-test/src'
] ]
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment