From b3a3d224f749c4c4711fa23544f2509f5197480f Mon Sep 17 00:00:00 2001 From: TuDo1403 Date: Fri, 18 Oct 2024 15:14:55 +0700 Subject: [PATCH] fix(RoninTransparentProxy): support legacy interface for legacy ProxyAdmin contract --- script/libraries/LibDeploy.sol | 2 +- src/RoninTransparentProxy.sol | 118 +++++++++++++++++++--- src/interfaces/IRoninTransparentProxy.sol | 12 +++ 3 files changed, 117 insertions(+), 15 deletions(-) create mode 100644 src/interfaces/IRoninTransparentProxy.sol diff --git a/script/libraries/LibDeploy.sol b/script/libraries/LibDeploy.sol index 1a528d6..3944965 100644 --- a/script/libraries/LibDeploy.sol +++ b/script/libraries/LibDeploy.sol @@ -205,7 +205,7 @@ library LibDeploy { DeployInfo memory proxyInfo; proxyInfo.callValue = callValue; proxyInfo.by = implInfo.by; - proxyInfo.contractName = "TransparentProxyOZv4_9_5"; + proxyInfo.contractName = "RoninTransparentProxy"; proxyInfo.absolutePath = string.concat(proxyInfo.contractName, ".sol:", proxyInfo.contractName); proxyInfo.artifactName = string.concat(vm.replace(implInfo.artifactName, "Logic", ""), "Proxy"); proxyInfo.constructorArgs = abi.encode(impl, proxyAdmin, callData); diff --git a/src/RoninTransparentProxy.sol b/src/RoninTransparentProxy.sol index a2cafcb..2d6ffc9 100644 --- a/src/RoninTransparentProxy.sol +++ b/src/RoninTransparentProxy.sol @@ -7,11 +7,14 @@ import { ERC1967Utils } from "../dependencies/openzeppelin-5.0.2/contracts/proxy import { ITransparentUpgradeableProxy } from "../dependencies/openzeppelin-5.0.2/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { IRoninTransparentProxy } from "./interfaces/IRoninTransparentProxy.sol"; + /** * @dev Contract TransparentUpgradeableProxy from Openzeppelin v5 with the following modifications: * - Admin is a parameter in the constructor (like previous versions) instead of being deployed * - Let the admin get access to the proxy via `functionDelegateCall` * - Replace _msgSender() with msg.sender + * - Preserve legacy function (`upgradeTo`, `admin`, `implementation`, `changeAdmin`) for legacy `ProxyAdmin` */ contract RoninTransparentProxy is ERC1967Proxy { /** @@ -25,14 +28,9 @@ contract RoninTransparentProxy is ERC1967Proxy { */ error OnlyAdmin(); - /** - * @dev - * An immutable address for the admin to avoid unnecessary SLOADs before each call - * at the expense of removing the ability to change the admin once it's set. - * This is acceptable if the admin is always a ProxyAdmin instance or similar contract - * with its own ability to transfer the permissions to another account. - */ - address private immutable _ADMIN; + receive() external payable { + _fallback(); + } /** * @dev Initializes an upgradeable proxy managed by an instance of a {ProxyAdmin} with an `initialOwner`, @@ -40,9 +38,8 @@ contract RoninTransparentProxy is ERC1967Proxy { * {ERC1967Proxy-constructor}. */ constructor(address logic, address admin, bytes memory data) payable ERC1967Proxy(logic, data) { - _ADMIN = admin; // Set the storage value and emit an event for ERC-1967 compatibility - ERC1967Utils.changeAdmin(_proxyAdmin()); + ERC1967Utils.changeAdmin(admin); } /** @@ -79,7 +76,7 @@ contract RoninTransparentProxy is ERC1967Proxy { * @dev Returns the admin of this proxy. */ function _proxyAdmin() internal virtual returns (address admin) { - return _ADMIN; + return ERC1967Utils.getAdmin(); } /** @@ -87,13 +84,80 @@ contract RoninTransparentProxy is ERC1967Proxy { */ function _fallback() internal virtual override { if (msg.sender == _proxyAdmin()) { - if (msg.sig != ITransparentUpgradeableProxy.upgradeToAndCall.selector) revert ProxyDeniedAdminAccess(); - else _dispatchUpgradeToAndCall(); + bytes memory ret; + + if (msg.sig == IRoninTransparentProxy.changeAdmin.selector) { + // Change the admin of the proxy + ret = _dispatchChangeAdmin(); + } else if (msg.sig == ITransparentUpgradeableProxy.upgradeToAndCall.selector) { + // Upgrade the implementation of the proxy and call a function + ret = _dispatchUpgradeToAndCall(); + } else if (msg.sig == IRoninTransparentProxy.upgradeTo.selector) { + // Upgrade the implementation of the proxy + ret = _dispatchUpgradeTo(); + } else if (msg.sig == IRoninTransparentProxy.admin.selector) { + // Get the admin of the proxy + ret = _dispatchAdmin(); + } else if (msg.sig == IRoninTransparentProxy.implementation.selector) { + // Get the implementation of the proxy + ret = _dispatchImplementation(); + } else { + revert ProxyDeniedAdminAccess(); + } + + assembly ("memory-safe") { + return(add(ret, 0x20), mload(ret)) + } } else { super._fallback(); } } + /** + * @dev Returns the current implementation. + * + * 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 _dispatchImplementation() private returns (bytes memory) { + _requireZeroValue(); + + address implementation = _implementation(); + + return abi.encode(implementation); + } + + /** + * @dev Returns the current admin. + * + * 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 _dispatchAdmin() private returns (bytes memory) { + _requireZeroValue(); + + address admin = ERC1967Utils.getAdmin(); + + return abi.encode(admin); + } + + /** + * @dev Changes the admin of the proxy. + * + * Emits an {AdminChanged} event. + */ + function _dispatchChangeAdmin() private returns (bytes memory ret) { + _requireZeroValue(); + + (address newAdmin) = abi.decode(msg.data[4:], (address)); + + ERC1967Utils.changeAdmin(newAdmin); + + return ""; + } + /** * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}. * @@ -101,8 +165,34 @@ contract RoninTransparentProxy is ERC1967Proxy { * * - If `data` is empty, `msg.value` must be zero. */ - function _dispatchUpgradeToAndCall() private { + function _dispatchUpgradeToAndCall() private returns (bytes memory ret) { (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); + ERC1967Utils.upgradeToAndCall(newImplementation, data); + + return ""; + } + + /** + * @dev Supports legacy upgradeTo without additional data. + * + * Requirements: + * - `msg.value` must be zero. + */ + function _dispatchUpgradeTo() private returns (bytes memory ret) { + (address newImplementation) = abi.decode(msg.data[4:], (address)); + + // Already checks for zero value + ERC1967Utils.upgradeToAndCall(newImplementation, ""); + + return ""; + } + + /** + * @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to + * emulate some proxy functions being non-payable while still allowing value to pass through. + */ + function _requireZeroValue() private { + if (msg.value != 0) revert ERC1967Utils.ERC1967NonPayable(); } } diff --git a/src/interfaces/IRoninTransparentProxy.sol b/src/interfaces/IRoninTransparentProxy.sol new file mode 100644 index 0000000..79aa1b6 --- /dev/null +++ b/src/interfaces/IRoninTransparentProxy.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IRoninTransparentProxy { + function changeAdmin(address newAdmin) external; + + function upgradeTo(address newImplementation) external; + + function implementation() external view returns (address); + + function admin() external view returns (address); +} \ No newline at end of file