Skip to content

Commit

Permalink
feat(protocol): L2/L3 contracts proxied (#13725)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyberhorsey authored May 9, 2023
1 parent ad75cd5 commit 7e6291f
Show file tree
Hide file tree
Showing 6 changed files with 446 additions and 76 deletions.
134 changes: 134 additions & 0 deletions packages/protocol/contracts/thirdparty/TransparentUpgradeableProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/transparent/TransparentUpgradeableProxy.sol)

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

/**
* @dev This contract implements a proxy that is upgradeable by an admin.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches one of the admin functions exposed by the proxy itself.
* 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
* implementation. If the admin tries to call a function on the implementation it will fail with an error that says
* "admin cannot fallback to proxy target".
*
* These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
* the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
* to sudden errors when trying to call a function from the proxy implementation.
*
* Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
* you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
*/
contract TransparentUpgradeableProxy is ERC1967Proxy {
/**
* @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
* optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
*/
constructor(
address _logic,
address admin_,
bytes memory _data
) payable ERC1967Proxy(_logic, _data) {
_changeAdmin(admin_);
}

/**
* @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
*/
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback();
}
}

/**
* @dev Returns the current admin.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function admin() external ifAdmin returns (address admin_) {
admin_ = _getAdmin();
}

/**
* @dev Returns the current implementation.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function implementation()
external
ifAdmin
returns (address implementation_)
{
implementation_ = _implementation();
}

/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
*/
function changeAdmin(address newAdmin) external virtual ifAdmin {
_changeAdmin(newAdmin);
}

/**
* @dev Upgrade the implementation of the proxy.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
*/
function upgradeTo(address newImplementation) external ifAdmin {
_upgradeToAndCall(newImplementation, bytes(""), false);
}

/**
* @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
* by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
* proxied contract.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
*/
function upgradeToAndCall(
address newImplementation,
bytes calldata data
) external payable ifAdmin {
_upgradeToAndCall(newImplementation, data, true);
}

/**
* @dev Returns the current admin.
*/
function _admin() internal view virtual returns (address) {
return _getAdmin();
}

/**
* @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
*/
function _beforeFallback() internal virtual override {
require(
msg.sender != _getAdmin(),
"TransparentUpgradeableProxy: admin cannot fallback to proxy target"
);
super._beforeFallback();
}
}
171 changes: 151 additions & 20 deletions packages/protocol/test/genesis/GenerateGenesis.g.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {SignalService} from "../../contracts/signal/SignalService.sol";
import {LibBridgeStatus} from "../../contracts/bridge/libs/LibBridgeStatus.sol";
import {LibL2Consts} from "../../contracts/L2/LibL2Consts.sol";
import {RegularERC20} from "../../contracts/test/erc20/RegularERC20.sol";
import {
TransparentUpgradeableProxy
} from "../../contracts/thirdparty/TransparentUpgradeableProxy.sol";

contract TestGenerateGenesis is Test, AddressResolver {
using stdJson for string;
Expand All @@ -29,36 +32,76 @@ contract TestGenerateGenesis is Test, AddressResolver {
string.concat(vm.projectRoot(), "/deployments/genesis_alloc.json")
);
address private owner = configJSON.readAddress(".contractOwner");
address private admin = configJSON.readAddress(".contractAdmin");

uint64 public constant BLOCK_GAS_LIMIT = 30000000;

function testContractDeployment() public {
assertEq(block.chainid, 167);

checkDeployedCode("TaikoL2");
checkDeployedCode("TokenVault");
checkDeployedCode("EtherVault");
checkDeployedCode("Bridge");
checkDeployedCode("ProxiedTaikoL2");
checkDeployedCode("ProxiedTokenVault");
checkDeployedCode("ProxiedEtherVault");
checkDeployedCode("ProxiedBridge");
checkDeployedCode("RegularERC20");
checkDeployedCode("AddressManager");
checkDeployedCode("SignalService");
checkDeployedCode("ProxiedAddressManager");
checkDeployedCode("ProxiedSignalService");

// check proxy implementations
checkProxyImplementation("TaikoL2Proxy", "ProxiedTaikoL2");
checkProxyImplementation("TokenVaultProxy", "ProxiedTokenVault");
checkProxyImplementation("EtherVaultProxy", "ProxiedEtherVault");
checkProxyImplementation("BridgeProxy", "ProxiedBridge");
checkProxyImplementation(
"AddressManagerProxy",
"ProxiedAddressManager"
);
checkProxyImplementation("SignalServiceProxy", "ProxiedSignalService");

// check proxies
checkDeployedCode("TaikoL2Proxy");
checkDeployedCode("TokenVaultProxy");
checkDeployedCode("EtherVaultProxy");
checkDeployedCode("BridgeProxy");
checkDeployedCode("AddressManagerProxy");
checkDeployedCode("SignalServiceProxy");
}

function testAddressManager() public {
AddressManager addressManager = AddressManager(
getPredeployedContractAddress("AddressManager")
getPredeployedContractAddress("AddressManagerProxy")
);

assertEq(owner, addressManager.owner());

checkSavedAddress(addressManager, "Bridge", "bridge");
checkSavedAddress(addressManager, "TokenVault", "token_vault");
checkSavedAddress(addressManager, "EtherVault", "ether_vault");
checkSavedAddress(addressManager, "TaikoL2", "taiko");
checkSavedAddress(addressManager, "SignalService", "signal_service");
checkSavedAddress(addressManager, "BridgeProxy", "bridge");
checkSavedAddress(addressManager, "TokenVaultProxy", "token_vault");
checkSavedAddress(addressManager, "EtherVaultProxy", "ether_vault");
checkSavedAddress(addressManager, "TaikoL2Proxy", "taiko");
checkSavedAddress(
addressManager,
"SignalServiceProxy",
"signal_service"
);

TransparentUpgradeableProxy proxy = TransparentUpgradeableProxy(
payable(getPredeployedContractAddress("AddressManagerProxy"))
);

AddressManager newAddressManager = new AddressManager();

vm.startPrank(admin);

proxy.upgradeTo(address(newAddressManager));

assertEq(proxy.implementation(), address(newAddressManager));
vm.stopPrank();
}

function testTaikoL2() public {
TaikoL2 taikoL2 = TaikoL2(getPredeployedContractAddress("TaikoL2"));
TaikoL2 taikoL2 = TaikoL2(
getPredeployedContractAddress("TaikoL2Proxy")
);

vm.startPrank(taikoL2.GOLDEN_TOUCH_ADDRESS());
for (uint64 i = 0; i < 300; i++) {
Expand Down Expand Up @@ -89,11 +132,24 @@ contract TestGenerateGenesis is Test, AddressResolver {
}
}
vm.stopPrank();

vm.startPrank(admin);

TransparentUpgradeableProxy proxy = TransparentUpgradeableProxy(
payable(getPredeployedContractAddress("TaikoL2Proxy"))
);

TaikoL2 newTaikoL2 = new TaikoL2();

proxy.upgradeTo(address(newTaikoL2));

assertEq(proxy.implementation(), address(newTaikoL2));
vm.stopPrank();
}

function testBridge() public {
address payable bridgeAddress = payable(
getPredeployedContractAddress("Bridge")
getPredeployedContractAddress("BridgeProxy")
);
Bridge bridge = Bridge(bridgeAddress);

Expand All @@ -118,30 +174,60 @@ contract TestGenerateGenesis is Test, AddressResolver {
}),
""
);

vm.startPrank(admin);

TransparentUpgradeableProxy proxy = TransparentUpgradeableProxy(
payable(getPredeployedContractAddress("BridgeProxy"))
);

Bridge newBridge = new Bridge();

proxy.upgradeTo(address(newBridge));

assertEq(proxy.implementation(), address(newBridge));
vm.stopPrank();
}

function testEtherVault() public {
address payable etherVaultAddress = payable(
getPredeployedContractAddress("EtherVault")
getPredeployedContractAddress("EtherVaultProxy")
);
EtherVault etherVault = EtherVault(etherVaultAddress);

assertEq(owner, etherVault.owner());

assertEq(
etherVault.isAuthorized(getPredeployedContractAddress("Bridge")),
etherVault.isAuthorized(
getPredeployedContractAddress("BridgeProxy")
),
true
);
assertEq(etherVault.isAuthorized(etherVault.owner()), false);

vm.startPrank(admin);

TransparentUpgradeableProxy proxy = TransparentUpgradeableProxy(
payable(getPredeployedContractAddress("EtherVaultProxy"))
);

EtherVault newEtherVault = new EtherVault();

proxy.upgradeTo(address(newEtherVault));

assertEq(proxy.implementation(), address(newEtherVault));
vm.stopPrank();
}

function testTokenVault() public {
address tokenVaultAddress = getPredeployedContractAddress("TokenVault");
address bridgeAddress = getPredeployedContractAddress("Bridge");
address tokenVaultAddress = getPredeployedContractAddress(
"TokenVaultProxy"
);
address bridgeAddress = getPredeployedContractAddress("BridgeProxy");

TokenVault tokenVault = TokenVault(tokenVaultAddress);
AddressManager addressManager = AddressManager(
getPredeployedContractAddress("AddressManager")
getPredeployedContractAddress("AddressManagerProxy")
);

assertEq(owner, tokenVault.owner());
Expand All @@ -150,16 +236,42 @@ contract TestGenerateGenesis is Test, AddressResolver {
addressManager.setAddress(1, "bridge", bridgeAddress);
addressManager.setAddress(1, "token_vault", tokenVaultAddress);
vm.stopPrank();

vm.startPrank(admin);

TransparentUpgradeableProxy proxy = TransparentUpgradeableProxy(
payable(getPredeployedContractAddress("TokenVaultProxy"))
);

TokenVault newTokenVault = new TokenVault();

proxy.upgradeTo(address(newTokenVault));

assertEq(proxy.implementation(), address(newTokenVault));
vm.stopPrank();
}

function testSignalService() public {
SignalService signalService = SignalService(
getPredeployedContractAddress("SignalService")
getPredeployedContractAddress("SignalServiceProxy")
);

assertEq(owner, signalService.owner());

signalService.sendSignal(keccak256(abi.encodePacked(block.prevrandao)));

vm.startPrank(admin);

TransparentUpgradeableProxy proxy = TransparentUpgradeableProxy(
payable(getPredeployedContractAddress("SignalServiceProxy"))
);

SignalService newSignalService = new SignalService();

proxy.upgradeTo(address(newSignalService));

assertEq(proxy.implementation(), address(newSignalService));
vm.stopPrank();
}

function testERC20() public {
Expand Down Expand Up @@ -189,6 +301,25 @@ contract TestGenerateGenesis is Test, AddressResolver {
assertEq(address(contractAddress).code, vm.parseBytes(deployedCode));
}

function checkProxyImplementation(
string memory proxyName,
string memory contractName
) private {
vm.startPrank(admin);
address contractAddress = getPredeployedContractAddress(contractName);
address proxyAddress = getPredeployedContractAddress(proxyName);

TransparentUpgradeableProxy proxy = TransparentUpgradeableProxy(
payable(proxyAddress)
);

assertEq(proxy.implementation(), address(contractAddress));

assertEq(proxy.admin(), admin);

vm.stopPrank();
}

function checkSavedAddress(
AddressManager addressManager,
string memory contractName,
Expand Down
Loading

0 comments on commit 7e6291f

Please sign in to comment.