diff --git a/contracts/proxy/admin.sol b/contracts/proxy/admin.sol new file mode 100644 index 0000000..4f1afae --- /dev/null +++ b/contracts/proxy/admin.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.0.0) (proxy/transparent/ProxyAdmin.sol) + +pragma solidity ^0.8.20; + +import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import { IEcoOwnable, EcoOwnable } from "../access/EcoOwnable.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +interface IEcoProxyAdmin { + function initEcoProxyAdmin(address initialOwner) external ; +} + +/** + * @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an + * explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}. + */ +contract EcoProxyAdmin is IEcoProxyAdmin, EcoOwnable { + /** + * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address)` + * and `upgradeAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, + * while `upgradeAndCall` will invoke the `receive` function if the second argument is the empty byte string. + * If the getter returns `"5.0.0"`, only `upgradeAndCall(address,bytes)` is present, and the second argument must + * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function + * during an upgrade. + */ + string public constant UPGRADE_INTERFACE_VERSION = "5.0.0"; + + /** + * @dev Sets the initial owner who can perform upgrades. + */ + constructor(address initialOwner) { + initEcoProxyAdmin(initialOwner); + } + + function initEcoProxyAdmin(address initialOwner) public override initializer { + initEcoOwnable(initialOwner); + } + + /** + * @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. + * See {TransparentUpgradeableProxy-_dispatchUpgradeToAndCall}. + * + * Requirements: + * + * - This contract must be the admin of `proxy`. + * - If `data` is empty, `msg.value` must be zero. + */ + function upgradeAndCall( + ITransparentUpgradeableProxy proxy, + address implementation, + bytes memory data + ) public payable virtual onlyOwner { + proxy.upgradeToAndCall{value: msg.value}(implementation, data); + } +} + +contract EcoProxyForProxyAdmin is ERC1967Proxy { + constructor(address proxyAdminLogic, address initialOwner) + ERC1967Proxy( + proxyAdminLogic, + abi.encodeWithSelector(IEcoProxyAdmin.initEcoProxyAdmin.selector, initialOwner) + ) + {} +} \ No newline at end of file diff --git a/contracts/proxy/transparent.sol b/contracts/proxy/transparent.sol new file mode 100644 index 0000000..792374b --- /dev/null +++ b/contracts/proxy/transparent.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol) + +pragma solidity ^0.8.0; + +import { ERC1967Utils } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import { EcoProxyForProxyAdmin } from "./admin.sol"; + +// EcoTransparentUpgradeableProxy +contract EcoTUPWithAdmin is ERC1967Proxy { + // 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 The proxy caller is the current admin, and can't fallback to the proxy target. + */ + error ProxyDeniedAdminAccess(); + + /** + * @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 proxyAdmin, address _logic, bytes memory _data) payable + ERC1967Proxy(_logic, _data) { + _admin = proxyAdmin; + // Set the storage value and emit an event for ERC-1967 compatibility + ERC1967Utils.changeAdmin(_proxyAdmin()); + } + + /** + * @dev Returns the admin of this proxy. + */ + function _proxyAdmin() internal virtual returns (address) { + 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); + } +} + +contract EcoTUPWithAdminLogic is EcoTUPWithAdmin { + constructor(address proxyAdminLogic, address _logic, bytes memory _data) payable + EcoTUPWithAdmin( + address(new EcoProxyForProxyAdmin(proxyAdminLogic, msg.sender)), + _logic, + _data + ) {} +} \ No newline at end of file