diff --git a/script/BaseMigration.s.sol b/script/BaseMigration.s.sol index fcab641..0cefd60 100644 --- a/script/BaseMigration.s.sol +++ b/script/BaseMigration.s.sol @@ -2,21 +2,24 @@ pragma solidity >=0.6.2 <0.9.0; pragma experimental ABIEncoderV2; -import { TransparentProxyV2 } from "../src/TransparentProxyV2.sol"; -import { TransparentProxyOZv4_9_5 } from "../src/TransparentProxyOZv4_9_5.sol"; -import { LibString } from "../dependencies/@solady-0.0.228/src/utils/LibString.sol"; -import { console } from "../dependencies/@forge-std-1.9.1/src/console.sol"; -import { StdStyle } from "../dependencies/@forge-std-1.9.1/src/StdStyle.sol"; -import { Vm } from "../dependencies/@forge-std-1.9.1/src/Vm.sol"; -import { ScriptExtended, IScriptExtended } from "./extensions/ScriptExtended.s.sol"; -import { OnchainExecutor } from "./OnchainExecutor.s.sol"; // cheat to load artifact to parent `out` directory +import { StdStyle } from "../dependencies/forge-std-1.9.3/src/StdStyle.sol"; +import { Vm } from "../dependencies/forge-std-1.9.3/src/Vm.sol"; +import { console } from "../dependencies/forge-std-1.9.3/src/console.sol"; +import { LibString } from "../dependencies/solady-0.0.228/src/utils/LibString.sol"; +import { RoninTransparentProxy } from "../src/RoninTransparentProxy.sol"; + +import { OnchainExecutor } from "./OnchainExecutor.s.sol"; +import { IScriptExtended, ScriptExtended } from "./extensions/ScriptExtended.s.sol"; // cheat to load artifact to parent + // `out` directory import { IMigrationScript } from "./interfaces/IMigrationScript.sol"; -import { LibProxy } from "./libraries/LibProxy.sol"; + +import { DeployInfo, LibDeploy, ProxyInterface, UpgradeInfo } from "./libraries/LibDeploy.sol"; import { LibInitializeGuard } from "./libraries/LibInitializeGuard.sol"; +import { LibProxy } from "./libraries/LibProxy.sol"; + +import { TContract, TNetwork } from "./types/Types.sol"; import { DefaultContract } from "./utils/DefaultContract.sol"; -import { ProxyInterface, LibDeploy, DeployInfo, UpgradeInfo } from "./libraries/LibDeploy.sol"; import { cheatBroadcast } from "./utils/Helpers.sol"; -import { TContract, TNetwork } from "./types/Types.sol"; abstract contract BaseMigration is ScriptExtended { using StdStyle for *; @@ -58,12 +61,10 @@ abstract contract BaseMigration is ScriptExtended { function _defaultArguments() internal virtual returns (bytes memory) { } - function switchTo(TNetwork networkType, uint256 forkBlockNumber) - public - virtual - override - returns (TNetwork currNetwork, uint256 currForkId) - { + function switchTo( + TNetwork networkType, + uint256 forkBlockNumber + ) public virtual override returns (TNetwork currNetwork, uint256 currForkId) { (currNetwork, currForkId) = super.switchTo(networkType, forkBlockNumber); // Should rebuild the shared arguments since different chain may have different shared arguments _storeRawSharedArguments(); @@ -75,7 +76,9 @@ abstract contract BaseMigration is ScriptExtended { vme.logSenderInfo(); } - function loadContractOrDeploy(TContract contractType) public virtual returns (address payable contractAddr) { + function loadContractOrDeploy( + TContract contractType + ) public virtual returns (address payable contractAddr) { string memory contractName = CONFIG.getContractName(contractType); try this.loadContract(contractType) returns (address payable addr) { contractAddr = addr; @@ -89,7 +92,9 @@ abstract contract BaseMigration is ScriptExtended { vme.setRawSharedArguments(_sharedArguments()); } - function overrideArgs(bytes memory args) public virtual returns (IMigrationScript) { + function overrideArgs( + bytes memory args + ) public virtual returns (IMigrationScript) { _overriddenArgs = args; return IMigrationScript(address(this)); } @@ -106,7 +111,9 @@ abstract contract BaseMigration is ScriptExtended { proxyAdmin = loadContract(DefaultContract.ProxyAdmin.key()); } - function _deployImmutable(TContract contractType) internal virtual returns (address payable deployed) { + function _deployImmutable( + TContract contractType + ) internal virtual returns (address payable deployed) { deployed = _deployImmutable({ contractType: contractType, artifactName: vme.getContractName(contractType), @@ -116,11 +123,10 @@ abstract contract BaseMigration is ScriptExtended { }); } - function _deployImmutable(TContract contractType, bytes memory args) - internal - virtual - returns (address payable deployed) - { + function _deployImmutable( + TContract contractType, + bytes memory args + ) internal virtual returns (address payable deployed) { deployed = _deployImmutable({ contractType: contractType, artifactName: vme.getContractName(contractType), @@ -156,7 +162,9 @@ abstract contract BaseMigration is ScriptExtended { vme.setAddress(network(), contractType, deployed); } - function _deployLogic(TContract contractType) internal virtual returns (address payable logic) { + function _deployLogic( + TContract contractType + ) internal virtual returns (address payable logic) { logic = _deployLogic({ contractType: contractType, artifactName: vme.getContractName(contractType), @@ -174,7 +182,12 @@ abstract contract BaseMigration is ScriptExtended { }); } - function _deployLogic(TContract contractType, string memory artifactName, address by, bytes memory constructorArgs) + function _deployLogic( + TContract contractType, + string memory artifactName, + address by, + bytes memory constructorArgs + ) internal virtual logFn(string.concat("_deployLogic ", TContract.unwrap(contractType).unpackOne())) @@ -192,15 +205,16 @@ abstract contract BaseMigration is ScriptExtended { }).deployImplementation(); } - function _deployProxy(TContract contractType) internal virtual returns (address payable deployed) { + function _deployProxy( + TContract contractType + ) internal virtual returns (address payable deployed) { deployed = _deployProxy(contractType, arguments()); } - function _deployProxy(TContract contractType, bytes memory callData) - internal - virtual - returns (address payable deployed) - { + function _deployProxy( + TContract contractType, + bytes memory callData + ) internal virtual returns (address payable deployed) { deployed = _deployProxy({ contractType: contractType, artifactName: vme.getContractName(contractType), @@ -212,11 +226,11 @@ abstract contract BaseMigration is ScriptExtended { }); } - function _deployProxy(TContract contractType, bytes memory callData, bytes memory logicConstructorArgs) - internal - virtual - returns (address payable deployed) - { + function _deployProxy( + TContract contractType, + bytes memory callData, + bytes memory logicConstructorArgs + ) internal virtual returns (address payable deployed) { deployed = _deployProxy({ contractType: contractType, artifactName: vme.getContractName(contractType), @@ -261,7 +275,9 @@ abstract contract BaseMigration is ScriptExtended { vme.setAddress(network(), contractType, deployed); } - function _upgradeProxy(TContract contractType) internal virtual returns (address payable proxy) { + function _upgradeProxy( + TContract contractType + ) internal virtual returns (address payable proxy) { proxy = _upgradeProxy(contractType, arguments()); } @@ -269,7 +285,11 @@ abstract contract BaseMigration is ScriptExtended { proxy = _upgradeProxy(contractType, args, EMPTY_ARGS); } - function _upgradeProxy(TContract contractType, bytes memory args, bytes memory argsLogicConstructor) + function _upgradeProxy( + TContract contractType, + bytes memory args, + bytes memory argsLogicConstructor + ) internal virtual logFn(string.concat("_upgradeProxy ", TContract.unwrap(contractType).unpackOne())) @@ -308,7 +328,6 @@ abstract contract BaseMigration is ScriptExtended { */ function _precompileProxyContracts() internal pure virtual { bytes memory dummy; - dummy = type(TransparentProxyV2).creationCode; - dummy = type(TransparentProxyOZv4_9_5).creationCode; + dummy = type(RoninTransparentProxy).creationCode; } } diff --git a/src/RoninTransparentProxy.sol b/src/RoninTransparentProxy.sol new file mode 100644 index 0000000..8d29035 --- /dev/null +++ b/src/RoninTransparentProxy.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/TransparentUpgradeableProxy.sol) +pragma solidity ^0.8.20; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ERC1967Utils } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; +import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.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 + */ +contract RoninTransparentProxy is ERC1967Proxy { + /** + * @dev The proxy caller is the current admin, and can't fallback to the proxy target. Admin must call via + * `adminDelegate`. + */ + error ProxyDeniedAdminAccess(); + + /** + * @dev The caller is not the admin. + */ + 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; + + /** + * @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()); + } + + /** + * @dev Calls a function from the current implementation as specified by `data`, which should be an encoded function + * call. + * + * Requirements: + * - Only the admin can call this function. + * + * Note: The proxy admin is not allowed to interact with the proxy logic through the fallback function to avoid + * triggering some unexpected logic. This is to allow the administrator to explicitly call the proxy, please consider + * reviewing the encoded data `data` and the method which is called before using this. + * + */ + function functionDelegateCall( + bytes memory data + ) external payable { + if (msg.sender != _proxyAdmin()) revert OnlyAdmin(); + + address impl = _implementation(); + + assembly ("memory-safe") { + let result := delegatecall(gas(), impl, add(data, 32), mload(data), 0, 0) + + returndatacopy(0, 0, returndatasize()) + + switch result + case 0 { revert(0, returndatasize()) } + default { return(0, returndatasize()) } + } + } + + /** + * @dev Returns the admin of this proxy. + */ + function _proxyAdmin() internal virtual returns (address admin) { + return _ADMIN; + } + + /** + * @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(); + } else { + super._fallback(); + } + } + + /** + * @dev Upgrade the implementation of the proxy. See {ERC1967Utils-upgradeToAndCall}. + * + * Requirements: + * + * - If `data` is empty, `msg.value` must be zero. + */ + function _dispatchUpgradeToAndCall() private { + (address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes)); + ERC1967Utils.upgradeToAndCall(newImplementation, data); + } +} diff --git a/src/TransparentProxyOZv4_9_5.sol b/src/TransparentProxyOZv4_9_5.sol deleted file mode 100644 index a5d2e0e..0000000 --- a/src/TransparentProxyOZv4_9_5.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { - ITransparentUpgradeableProxy, - TransparentUpgradeableProxy -} from "../dependencies/@openzeppelin-4.9.3/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; - -/** - * @title TransparentProxyOZv4_9_5 - * @dev A contract that acts as a proxy for transparent upgrades. - */ -contract TransparentProxyOZv4_9_5 is TransparentUpgradeableProxy { - /** - * @dev Initializes the Proxy contract. - * @param logic The address of the logic contract. - * @param admin The address of the admin contract. - * @param data The initialization data. - */ - constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) { } -} diff --git a/src/TransparentProxyV2.sol b/src/TransparentProxyV2.sol deleted file mode 100644 index 08f802b..0000000 --- a/src/TransparentProxyV2.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { TransparentUpgradeableProxy } from - "../dependencies/@openzeppelin-4.9.3/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; - -contract TransparentProxyV2 is TransparentUpgradeableProxy { - /** - * @dev Initializes the Proxy contract. - * @param logic The address of the logic contract. - * @param admin The address of the admin contract. - * @param data The initialization data. - */ - constructor(address logic, address admin, bytes memory data) payable TransparentUpgradeableProxy(logic, admin, data) { } - - /** - * @dev Calls a function from the current implementation as specified by `data`, which should be an encoded function call. - * - * Requirements: - * - Only the admin can call this function. - * - * Note: The proxy admin is not allowed to interact with the proxy logic through the fallback function to avoid - * triggering some unexpected logic. This is to allow the administrator to explicitly call the proxy, please consider - * reviewing the encoded data `_data` and the method which is called before using this. - * - */ - function functionDelegateCall(bytes memory data) public payable ifAdmin { - address addr = _implementation(); - - assembly ("memory-safe") { - let result := delegatecall(gas(), addr, add(data, 32), mload(data), 0, 0) - returndatacopy(0, 0, returndatasize()) - switch result - case 0 { revert(0, returndatasize()) } - default { return(0, returndatasize()) } - } - } -}