Contract Name:
InitialProxyImplementationWithOwner
Contract Source Code:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
library AddressUtil {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
/**
* @title Contract for facilitating ownership by a single address, using the Openzeppelin ownable upgradable storage slot.
* @dev Implementation is a modified version of Openzeppelin and Synthetix Ownable implementations.
*/
interface IOwnable {
/*///////////////////////////////////////////////////////////////
ERRORS
///////////////////////////////////////////////////////////////*/
/**
* @notice Thrown when an address tries to accept ownership but has not been nominated.
* @param addr The address that is trying to accept ownership.
*/
error NotNominated(address addr);
/**
* @notice Thrown when an address tries to renounce pending ownership but not owner or pending owner.
* @param addr The address that is trying to renounce pending ownership.
*/
error NotNominatedOrOwner(address addr);
/**
* @notice Thrown when an address is zero.
*/
error ZeroAddress();
/**
* @notice Thrown when no change is made.
*/
error NoChange();
/**
* @notice Thrown when an address is not the owner.
*/
error Unauthorized(address addr);
/*///////////////////////////////////////////////////////////////
EVENTS
///////////////////////////////////////////////////////////////*/
/**
* @notice Emitted when an address has been nominated.
* @param newOwner The address that has been nominated.
*/
event OwnerNominated(address newOwner);
/**
* @notice Emitted when the owner of the contract has changed.
* @param oldOwner The previous owner of the contract.
* @param newOwner The new owner of the contract.
*/
event OwnerChanged(address oldOwner, address newOwner);
/**
* @notice Emitted when the nominated pending owner renounces themselves as nominated.
* @param pendingOwner The pending owner that is renounced.
*/
event PendingOwnerRenounced(address pendingOwner);
/*///////////////////////////////////////////////////////////////
VIEW FUNCTIONS
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the current owner of the contract.
*/
function owner() external view returns (address);
/**
* @notice Returns the current pending owner of the contract.
* @dev Only one address can be pending at a time.
*/
function pendingOwner() external view returns (address);
/*///////////////////////////////////////////////////////////////
MUTATIVE FUNCTIONS
///////////////////////////////////////////////////////////////*/
/**
* @notice Allows a pending owner address to accept ownership of the contract.
* @dev Reverts if the caller has not been nominated.
*/
function acceptOwnership() external;
/**
* @notice Allows the current owner to nominate a new owner.
* @dev The pending owner will have to call `acceptOwnership` in a separate transaction in order to finalize the action and become the new contract owner.
* @param newOwner The address that is to become nominated.
*/
function transferOwnership(address newOwner) external;
/**
* @notice Allows a pending owner to reject the nomination.
*/
function renouncePendingOwnership() external;
/**
* @notice Allows the current owner of the contract to renounce the ownership and pending ownership completely.
*/
function renounceOwnership() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import { IOwnable } from "src/ownership/IOwnable.sol";
import { OwnableStorage } from "src/ownership/OwnableStorage.sol";
/**
* @title Contract for facilitating ownership by a single address, using the Openzeppelin ownable upgradable storage slot.
* @dev Implementation is a modified version of Openzeppelin and Synthetix Ownable implementations.
*/
abstract contract Ownable is IOwnable {
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
///////////////////////////////////////////////////////////////*/
constructor(address initialOwner) {
if (initialOwner == address(0)) revert ZeroAddress();
OwnableStorage._setOwner(initialOwner);
}
/*///////////////////////////////////////////////////////////////
MODIFIERS
///////////////////////////////////////////////////////////////*/
/**
* @notice Reverts if the caller is not the owner.
*/
modifier onlyOwner() {
_onlyOwner();
_;
}
/*///////////////////////////////////////////////////////////////
VIEW FUNCTIONS
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the current owner of the contract.
*/
function owner() external view virtual returns (address) {
return _owner();
}
/**
* @notice Returns the current pending owner of the contract.
* @dev Only one address can be pending at a time.
*/
function pendingOwner() external view virtual returns (address) {
return _pendingOwner();
}
/*///////////////////////////////////////////////////////////////
INTERNAL FUNCTIONS
///////////////////////////////////////////////////////////////*/
/**
* @notice Returns the current owner of the contract.
*/
function _owner() internal view virtual returns (address) {
return OwnableStorage._getOwner();
}
/**
* @notice Returns the current pending owner of the contract.
* @dev Only one address can be nominated at a time.
*/
function _pendingOwner() internal view virtual returns (address) {
return OwnableStorage._getPendingOwner();
}
/**
* @notice Reverts if the caller is not the owner.
*/
function _onlyOwner() internal view virtual {
if (msg.sender != _owner()) {
revert Unauthorized(msg.sender);
}
}
/*///////////////////////////////////////////////////////////////
MUTATIVE FUNCTIONS
///////////////////////////////////////////////////////////////*/
/**
* @notice Allows a pending owner to accept ownership of the contract.
* @dev Reverts if the caller is not pending owner.
*/
function acceptOwnership() public virtual {
address currentPendingOwner = _pendingOwner();
if (msg.sender != currentPendingOwner) {
revert NotNominated(msg.sender);
}
emit OwnerChanged(_owner(), currentPendingOwner);
OwnableStorage._setOwner(currentPendingOwner);
OwnableStorage._setPendingOwner(address(0));
}
/**
* @notice Allows the current owner to nominate a new owner.
* @dev The pending owner will have to call `acceptOwnership` in a separate transaction in order to finalize the action and become the new contract owner.
* @param newOwner The address that is to become nominated.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert ZeroAddress();
}
if (newOwner == _pendingOwner()) {
revert NoChange();
}
emit OwnerNominated(newOwner);
OwnableStorage._setPendingOwner(newOwner);
}
/**
* @notice Allows a pending owner or owner to reject the nomination.
*/
function renouncePendingOwnership() external virtual {
address pendingOwner = _pendingOwner();
if (pendingOwner != msg.sender && msg.sender != _owner()) {
revert NotNominatedOrOwner(msg.sender);
}
emit PendingOwnerRenounced(pendingOwner);
OwnableStorage._setPendingOwner(address(0));
}
/**
* @notice Allows the current owner of the contract to renounce the ownership and pending ownership completely.
*/
function renounceOwnership() public virtual onlyOwner {
emit PendingOwnerRenounced(_pendingOwner());
OwnableStorage._setPendingOwner(address(0));
emit OwnerChanged(_owner(), address(0));
OwnableStorage._setOwner(address(0));
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
/**
* @title Ownable Storage
* @dev This library provides storage and functions for managing the ownership of a contract, using the Openzeppelin ownable upgradable storage slot.
* Implementation is a modified version of Openzeppelin and Synthetix Ownable implementations.
*/
library OwnableStorage {
// Storage slot copied from the Openzeppelin ownable upgradable contract.
// https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/access/OwnableUpgradeable.sol
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant _SLOT_OWNABLE_STORAGE = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300; //#gitleaks:allow
struct Storage {
address _owner;
address _pendingOwner;
}
/**
* @notice Loads the storage data for the Ownable contract
* @return store The storage data for the Ownable contract
*/
function getStorage() internal pure returns (Storage storage store) {
bytes32 s = _SLOT_OWNABLE_STORAGE;
assembly {
store.slot := s
}
}
/**
* @notice Returns the current owner of the contract
* @return The address of the owner
*/
function _getOwner() internal view returns (address) {
return getStorage()._owner;
}
/**
* @notice Returns the pending owner of the contract.
* @return The address of the pending owner.
*/
function _getPendingOwner() internal view returns (address) {
return getStorage()._pendingOwner;
}
/**
* @notice Sets the owner in ownable storage.
* @param _newOwner The new owner of the contract.
*/
function _setOwner(address _newOwner) internal {
getStorage()._owner = _newOwner;
}
/**
* @notice Sets the pending owner.
* @param _newOwner The new pending owner of the contract.
*/
function _setPendingOwner(address _newOwner) internal {
getStorage()._pendingOwner = _newOwner;
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
/**
* @title Contract to be used as the implementation of a Universal Upgradeable Proxy Standard (UUPS) proxy.
* Important: A UUPS proxy requires its upgradeability functions to be in the implementation as opposed to the proxy.
* This means that if the proxy is upgraded to an implementation that does not support this interface, it will no longer be upgradeable.
* Copied from Synthetix
* https://github.com/Synthetixio/synthetix-v3/blob/main/utils/core-contracts/contracts/interfaces/IUUPSImplementation.sol
*/
interface IUUPSImplementation {
/**
* @notice Thrown when an incoming implementation will not be able to receive future upgrades.
*/
error ImplementationIsSterile(address implementation);
/**
* @notice Thrown intentionally when testing future upgradeability of an implementation.
*/
error UpgradeSimulationFailed();
/**
* @notice Thrown when the address is zero.
*/
error NullAddress();
/**
* @notice Thrown when the address is not a contract.
*/
error NotAContract(address implementation);
/**
* @notice Thrown when the implementation is the same as the current implementation.
*/
error SameImplementation();
/**
* @notice Emitted when the implementation of the proxy has been upgraded.
* @param self The address of the proxy whose implementation was upgraded.
* @param implementation The address of the proxy's new implementation.
*/
event Upgraded(address indexed self, address implementation);
/**
* @notice Allows the proxy to be upgraded to a new implementation.
* @param newImplementation The address of the proxy's new implementation.
* @dev Will revert if `newImplementation` is not upgradeable.
* @dev The implementation of this function needs to be protected by some sort of access control such as `onlyOwner`.
*/
function upgradeTo(address newImplementation) external;
/**
* @notice Function used to determine if a new implementation will be able to receive future upgrades in `upgradeTo`.
* @param newImplementation The address of the new implementation being tested for future upgradeability.
* @dev This function will always revert, but will revert with different error messages. The function `upgradeTo` uses this error to determine the future upgradeability of the implementation in question.
*/
function simulateUpgradeTo(address newImplementation) external;
/**
* @notice Retrieves the current implementation of the proxy.
* @return The address of the current implementation.
*/
function getImplementation() external view returns (address);
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import { UUPSImplementation } from "src/proxy/UUPSImplementation.sol";
import { Ownable } from "src/ownership/Ownable.sol";
/**
* @dev The below contract is only used as an intermediary implementation contract
* when initializing the proxy to make sure that, when deployed using CREATE2,
* we always get the same contract address since the bytecode is deterministic.
*/
contract InitialProxyImplementationWithOwner is UUPSImplementation, Ownable {
constructor() Ownable(address(this)) { }
function upgradeTo(address newImplementation) public override {
_onlyOwner();
// If the current implementation is the same as the new one, we return blank to avoid the NoChange Error event.
// This is helpful when trying to build the package for the first time, so that we can smoothly upgrade the proxy impl
if (newImplementation == this.getImplementation()) {
emit Upgraded(address(this), newImplementation);
return;
}
_upgradeTo(newImplementation);
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
/**
* @dev Modified Synthetix proxy storage contract.
* https://github.com/Synthetixio/synthetix-v3/blob/main/utils/core-contracts/contracts/proxy/ProxyStorage.sol
*/
contract ProxyStorage {
// keccak256(abi.encode("io.synthetix.core-contracts.Proxy"));
bytes32 private constant _SLOT_PROXY_STORAGE = 0x5a648c35a2f5512218b4683cf10e03f5b7c9dc7346e1bf77d304ae97f60f592b; //#gitleaks:allow
struct ProxyStore {
address implementation;
bool simulatingUpgrade;
}
function _proxyStore() internal pure returns (ProxyStore storage store) {
bytes32 s = _SLOT_PROXY_STORAGE;
assembly {
store.slot := s
}
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
import { IUUPSImplementation } from "src/proxy/IUUPSImplementation.sol";
import { AddressUtil } from "src/libraries/AddressUtil.sol";
import { ProxyStorage } from "src/proxy/ProxyStorage.sol";
/**
* @dev Modified Synthetix UUPS Implementation contract
* https://github.com/Synthetixio/synthetix-v3/blob/main/utils/core-contracts/contracts/proxy/UUPSImplementation.sol
*/
abstract contract UUPSImplementation is IUUPSImplementation, ProxyStorage {
/**
* @notice Function used to determine if a new implementation will be able to receive future upgrades in `upgradeTo`.
* @param newImplementation The address of the new implementation being tested for future upgradeability.
* @dev This function will always revert, but will revert with different error messages. The function `upgradeTo` uses this error to determine the future upgradeability of the implementation in question.
*/
function simulateUpgradeTo(address newImplementation) public override {
if (newImplementation == address(0)) {
revert NullAddress();
}
ProxyStore storage store = _proxyStore();
store.simulatingUpgrade = true;
address currentImplementation = store.implementation;
store.implementation = newImplementation;
// slither-disable-start controlled-delegatecall
// solhint-disable-next-line avoid-low-level-calls
(bool rollbackSuccessful,) = newImplementation.delegatecall(abi.encodeCall(this.upgradeTo, (currentImplementation)));
// slither-disable-end controlled-delegatecall
if (!rollbackSuccessful || _proxyStore().implementation != currentImplementation) {
revert UpgradeSimulationFailed();
}
store.simulatingUpgrade = false;
// solhint-disable-next-line reason-string,gas-custom-errors
revert();
}
/**
* @notice Retrieves the current implementation of the proxy.
* @return The address of the current implementation.
*/
function getImplementation() external view override returns (address) {
return _proxyStore().implementation;
}
/**
* @notice Allows the proxy to be upgraded to a new implementation.
* @param newImplementation The address of the new implementation.
* @dev Will revert if `newImplementation` is not upgradeable.
* @dev The implementation of this function needs to be protected by some sort of access control such as `onlyOwner`.
*/
function _upgradeTo(address newImplementation) internal virtual {
if (newImplementation == address(0)) {
revert NullAddress();
}
if (!AddressUtil.isContract(newImplementation)) {
revert NotAContract(newImplementation);
}
ProxyStore storage store = _proxyStore();
if (newImplementation == store.implementation) {
revert SameImplementation();
}
if (!store.simulatingUpgrade && _implementationIsSterile(newImplementation)) {
revert ImplementationIsSterile(newImplementation);
}
store.implementation = newImplementation;
emit Upgraded(address(this), newImplementation);
}
/**
* @notice Checks if the candidate implementation is sterile.
* @param candidateImplementation The address of the candidate implementation.
* @return True if the candidate implementation is sterile, false otherwise.
*/
function _implementationIsSterile(address candidateImplementation) internal virtual returns (bool) {
(bool simulationReverted, bytes memory simulationResponse) =
// solhint-disable-next-line avoid-low-level-calls
address(this).delegatecall(abi.encodeCall(this.simulateUpgradeTo, (candidateImplementation)));
return !simulationReverted
&& keccak256(abi.encodePacked(simulationResponse)) == keccak256(abi.encodePacked(UpgradeSimulationFailed.selector));
}
}