Skip to content

Commit

Permalink
fix(RoninTransparentProxy): support legacy interface for legacy Proxy…
Browse files Browse the repository at this point in the history
…Admin contract
  • Loading branch information
TuDo1403 committed Oct 18, 2024
1 parent a910ab3 commit b3a3d22
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 15 deletions.
2 changes: 1 addition & 1 deletion script/libraries/LibDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
118 changes: 104 additions & 14 deletions src/RoninTransparentProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Expand All @@ -25,24 +28,18 @@ 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`,
* 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) {
_ADMIN = admin;
// Set the storage value and emit an event for ERC-1967 compatibility
ERC1967Utils.changeAdmin(_proxyAdmin());
ERC1967Utils.changeAdmin(admin);
}

/**
Expand Down Expand Up @@ -79,30 +76,123 @@ contract RoninTransparentProxy is ERC1967Proxy {
* @dev Returns the admin of this proxy.
*/
function _proxyAdmin() internal virtual returns (address admin) {
return _ADMIN;
return ERC1967Utils.getAdmin();
}

/**
* @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior.
*/
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}.
*
* Requirements:
*
* - 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();
}
}
12 changes: 12 additions & 0 deletions src/interfaces/IRoninTransparentProxy.sol
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit b3a3d22

Please sign in to comment.