// SPDX-License-Identifier: MIT pragma solidity >=0.6.11; // ============ External Imports ============ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; /** * @title UpgradeBeacon * @notice Stores the address of an implementation contract * and allows a controller to upgrade the implementation address * @dev This implementation combines the gas savings of having no function selectors * found in 0age's implementation: * https://github.com/dharma-eng/dharma-smart-wallet/blob/master/contracts/proxies/smart-wallet/UpgradeBeaconProxyV1.sol * With the added niceties of a safety check that each implementation is a contract * and an Upgrade event emitted each time the implementation is changed * found in OpenZeppelin's implementation: * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/beacon/BeaconProxy.sol */ contract UpgradeBeacon { // ============ Immutables ============ // The controller is capable of modifying the implementation address address private immutable controller; // ============ Private Storage Variables ============ // The implementation address is held in storage slot zero. address private implementation; // ============ Events ============ // Upgrade event is emitted each time the implementation address is set // (including deployment) event Upgrade(address indexed implementation); // ============ Constructor ============ /** * @notice Validate the initial implementation and store it. * Store the controller immutably. * @param _initialImplementation Address of the initial implementation contract * @param _controller Address of the controller who can upgrade the implementation */ constructor(address _initialImplementation, address _controller) payable { _setImplementation(_initialImplementation); controller = _controller; } // ============ External Functions ============ /** * @notice For all callers except the controller, return the current implementation address. * If called by the Controller, update the implementation address * to the address passed in the calldata. * Note: this requires inline assembly because Solidity fallback functions * do not natively take arguments or return values. */ fallback() external payable { if (msg.sender != controller) { // if not called by the controller, // load implementation address from storage slot zero // and return it. assembly { mstore(0, sload(0)) return(0, 32) } } else { // if called by the controller, // load new implementation address from the first word of the calldata address _newImplementation; assembly { _newImplementation := calldataload(0) } // set the new implementation _setImplementation(_newImplementation); } } // ============ Private Functions ============ /** * @notice Perform checks on the new implementation address * then upgrade the stored implementation. * @param _newImplementation Address of the new implementation contract which will replace the old one */ function _setImplementation(address _newImplementation) private { // Require that the new implementation is different from the current one require(implementation != _newImplementation, "!upgrade"); // Require that the new implementation is a contract require( Address.isContract(_newImplementation), "implementation !contract" ); // set the new implementation implementation = _newImplementation; emit Upgrade(_newImplementation); } }