diff --git a/abi/Beacon.json b/abi/Beacon.json new file mode 100644 index 00000000..135dd8d1 --- /dev/null +++ b/abi/Beacon.json @@ -0,0 +1,83 @@ +[ + { + "inputs": [], + "name": "Ownable__NotOwner", + "type": "error" + }, + { + "inputs": [], + "name": "Ownable__NotTransitiveOwner", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "getImplementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "setImplementation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/ManagedProxyOwnable.json b/abi/BeaconInternal.json similarity index 67% rename from abi/ManagedProxyOwnable.json rename to abi/BeaconInternal.json index fcf0038e..7f336963 100644 --- a/abi/ManagedProxyOwnable.json +++ b/abi/BeaconInternal.json @@ -1,9 +1,4 @@ [ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - }, { "inputs": [], "name": "Ownable__NotOwner", @@ -14,11 +9,6 @@ "name": "Ownable__NotTransitiveOwner", "type": "error" }, - { - "inputs": [], - "name": "Proxy__ImplementationIsNotContract", - "type": "error" - }, { "anonymous": false, "inputs": [ @@ -37,9 +27,5 @@ ], "name": "OwnershipTransferred", "type": "event" - }, - { - "stateMutability": "payable", - "type": "fallback" } ] diff --git a/abi/IManagedProxy.json b/abi/BeaconProxy.json similarity index 61% rename from abi/IManagedProxy.json rename to abi/BeaconProxy.json index a412d93f..7661f993 100644 --- a/abi/IManagedProxy.json +++ b/abi/BeaconProxy.json @@ -1,9 +1,4 @@ [ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - }, { "inputs": [], "name": "Proxy__ImplementationIsNotContract", diff --git a/abi/DiamondBeacon.json b/abi/DiamondBeacon.json new file mode 100644 index 00000000..1a717b98 --- /dev/null +++ b/abi/DiamondBeacon.json @@ -0,0 +1,255 @@ +[ + { + "inputs": [], + "name": "DiamondWritable__InvalidInitializationParameters", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__RemoveTargetNotZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__ReplaceTargetIsIdentical", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__SelectorAlreadyAdded", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__SelectorIsImmutable", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__SelectorNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__SelectorNotSpecified", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__TargetHasNoCode", + "type": "error" + }, + { + "inputs": [], + "name": "Ownable__NotOwner", + "type": "error" + }, + { + "inputs": [], + "name": "Ownable__NotTransitiveOwner", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "enum IERC2535DiamondCutInternal.FacetCutAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "indexed": false, + "internalType": "struct IERC2535DiamondCutInternal.FacetCut[]", + "name": "facetCuts", + "type": "tuple[]" + }, + { + "indexed": false, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "DiamondCut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "enum IERC2535DiamondCutInternal.FacetCutAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "internalType": "struct IERC2535DiamondCutInternal.FacetCut[]", + "name": "facetCuts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "diamondCut", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "facetAddress", + "outputs": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "facetAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "addresses", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + } + ], + "name": "facetFunctionSelectors", + "outputs": [ + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "facets", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "internalType": "struct IERC2535DiamondLoupeInternal.Facet[]", + "name": "diamondFacets", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/DiamondBeaconInternal.json b/abi/DiamondBeaconInternal.json new file mode 100644 index 00000000..7f336963 --- /dev/null +++ b/abi/DiamondBeaconInternal.json @@ -0,0 +1,31 @@ +[ + { + "inputs": [], + "name": "Ownable__NotOwner", + "type": "error" + }, + { + "inputs": [], + "name": "Ownable__NotTransitiveOwner", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } +] diff --git a/abi/IManagedProxyOwnable.json b/abi/DiamondBeaconProxy.json similarity index 61% rename from abi/IManagedProxyOwnable.json rename to abi/DiamondBeaconProxy.json index a412d93f..7661f993 100644 --- a/abi/IManagedProxyOwnable.json +++ b/abi/DiamondBeaconProxy.json @@ -1,9 +1,4 @@ [ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - }, { "inputs": [], "name": "Proxy__ImplementationIsNotContract", diff --git a/abi/IBeacon.json b/abi/IBeacon.json new file mode 100644 index 00000000..135dd8d1 --- /dev/null +++ b/abi/IBeacon.json @@ -0,0 +1,83 @@ +[ + { + "inputs": [], + "name": "Ownable__NotOwner", + "type": "error" + }, + { + "inputs": [], + "name": "Ownable__NotTransitiveOwner", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "getImplementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "setImplementation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/IBeaconInternal.json b/abi/IBeaconInternal.json new file mode 100644 index 00000000..7f336963 --- /dev/null +++ b/abi/IBeaconInternal.json @@ -0,0 +1,31 @@ +[ + { + "inputs": [], + "name": "Ownable__NotOwner", + "type": "error" + }, + { + "inputs": [], + "name": "Ownable__NotTransitiveOwner", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } +] diff --git a/abi/ManagedProxy.json b/abi/IBeaconProxy.json similarity index 61% rename from abi/ManagedProxy.json rename to abi/IBeaconProxy.json index a412d93f..7661f993 100644 --- a/abi/ManagedProxy.json +++ b/abi/IBeaconProxy.json @@ -1,9 +1,4 @@ [ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - }, { "inputs": [], "name": "Proxy__ImplementationIsNotContract", diff --git a/abi/IDiamondBeacon.json b/abi/IDiamondBeacon.json new file mode 100644 index 00000000..1a717b98 --- /dev/null +++ b/abi/IDiamondBeacon.json @@ -0,0 +1,255 @@ +[ + { + "inputs": [], + "name": "DiamondWritable__InvalidInitializationParameters", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__RemoveTargetNotZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__ReplaceTargetIsIdentical", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__SelectorAlreadyAdded", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__SelectorIsImmutable", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__SelectorNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__SelectorNotSpecified", + "type": "error" + }, + { + "inputs": [], + "name": "DiamondWritable__TargetHasNoCode", + "type": "error" + }, + { + "inputs": [], + "name": "Ownable__NotOwner", + "type": "error" + }, + { + "inputs": [], + "name": "Ownable__NotTransitiveOwner", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "enum IERC2535DiamondCutInternal.FacetCutAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "indexed": false, + "internalType": "struct IERC2535DiamondCutInternal.FacetCut[]", + "name": "facetCuts", + "type": "tuple[]" + }, + { + "indexed": false, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "DiamondCut", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "enum IERC2535DiamondCutInternal.FacetCutAction", + "name": "action", + "type": "uint8" + }, + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "internalType": "struct IERC2535DiamondCutInternal.FacetCut[]", + "name": "facetCuts", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "diamondCut", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "facetAddress", + "outputs": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "facetAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "addresses", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "facet", + "type": "address" + } + ], + "name": "facetFunctionSelectors", + "outputs": [ + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "facets", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bytes4[]", + "name": "selectors", + "type": "bytes4[]" + } + ], + "internalType": "struct IERC2535DiamondLoupeInternal.Facet[]", + "name": "diamondFacets", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/abi/IDiamondBeaconInternal.json b/abi/IDiamondBeaconInternal.json new file mode 100644 index 00000000..7f336963 --- /dev/null +++ b/abi/IDiamondBeaconInternal.json @@ -0,0 +1,31 @@ +[ + { + "inputs": [], + "name": "Ownable__NotOwner", + "type": "error" + }, + { + "inputs": [], + "name": "Ownable__NotTransitiveOwner", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } +] diff --git a/abi/IDiamondBeaconProxy.json b/abi/IDiamondBeaconProxy.json new file mode 100644 index 00000000..7661f993 --- /dev/null +++ b/abi/IDiamondBeaconProxy.json @@ -0,0 +1,11 @@ +[ + { + "inputs": [], + "name": "Proxy__ImplementationIsNotContract", + "type": "error" + }, + { + "stateMutability": "payable", + "type": "fallback" + } +] diff --git a/contracts/proxy/beacon/Beacon.sol b/contracts/proxy/beacon/Beacon.sol new file mode 100644 index 00000000..39c97090 --- /dev/null +++ b/contracts/proxy/beacon/Beacon.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { Ownable } from '../../access/ownable/Ownable.sol'; +import { IBeacon } from './IBeacon.sol'; +import { BeaconInternal } from './BeaconInternal.sol'; + +contract Beacon is IBeacon, Ownable, BeaconInternal { + /** + * @inheritdoc IBeacon + */ + function getImplementation() + external + view + returns (address implementation) + { + implementation = _getImplementation(); + } + + /** + * @inheritdoc IBeacon + */ + function setImplementation(address implementation) external { + _setImplementation(implementation); + } +} diff --git a/contracts/proxy/beacon/BeaconInternal.sol b/contracts/proxy/beacon/BeaconInternal.sol new file mode 100644 index 00000000..f4072086 --- /dev/null +++ b/contracts/proxy/beacon/BeaconInternal.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { OwnableInternal } from '../../access/ownable/OwnableInternal.sol'; +import { IBeaconInternal } from './IBeaconInternal.sol'; +import { BeaconStorage } from './BeaconStorage.sol'; + +abstract contract BeaconInternal is IBeaconInternal, OwnableInternal { + /** + * @notice query the address of the implementation that should be used by BeaconProxy instancesf + * @return implementation address of the implementation contract + */ + function _getImplementation() + internal + view + virtual + returns (address implementation) + { + implementation = BeaconStorage.layout().implementation; + } + + /** + * @notice set the address of the implementation that should be used by BeaconProxy instancesf + * @param implementation address of the implementation contract + */ + function _setImplementation( + address implementation + ) internal virtual onlyOwner { + BeaconStorage.layout().implementation = implementation; + } +} diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol new file mode 100644 index 00000000..86f4bf91 --- /dev/null +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { Proxy } from '../Proxy.sol'; +import { IBeacon } from './IBeacon.sol'; +import { IBeaconProxy } from './IBeaconProxy.sol'; + +/** + * @title Proxy with externally controlled implementation + * @dev implementation fetched using immutable function selector + */ +abstract contract BeaconProxy is IBeaconProxy, Proxy { + /** + * @inheritdoc Proxy + */ + function _getImplementation() + internal + view + virtual + override + returns (address) + { + return IBeacon(_getBeacon()).getImplementation(); + } + + /** + * @notice get beacon of proxy implementation + * @return beacon address + */ + function _getBeacon() internal view virtual returns (address); +} diff --git a/contracts/proxy/beacon/BeaconProxyMock.sol b/contracts/proxy/beacon/BeaconProxyMock.sol new file mode 100644 index 00000000..f06155f8 --- /dev/null +++ b/contracts/proxy/beacon/BeaconProxyMock.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { BeaconProxy } from './BeaconProxy.sol'; + +contract BeaconProxyMock is BeaconProxy { + address private _beacon; + + constructor(address beacon) { + setBeacon(beacon); + } + + function _getBeacon() internal view override returns (address) { + return _beacon; + } + + function __getImplementation() external view returns (address) { + return _getImplementation(); + } + + function setBeacon(address beacon) public { + _beacon = beacon; + } + + /** + * @dev suppress compiler warning + */ + receive() external payable {} +} diff --git a/contracts/proxy/beacon/BeaconStorage.sol b/contracts/proxy/beacon/BeaconStorage.sol new file mode 100644 index 00000000..4f67cd3e --- /dev/null +++ b/contracts/proxy/beacon/BeaconStorage.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +library BeaconStorage { + struct Layout { + address implementation; + } + + bytes32 internal constant STORAGE_SLOT = + keccak256('solidstate.contracts.storage.Beacon'); + + function layout() internal pure returns (Layout storage l) { + bytes32 slot = STORAGE_SLOT; + assembly { + l.slot := slot + } + } +} diff --git a/contracts/proxy/beacon/DiamondBeacon.sol b/contracts/proxy/beacon/DiamondBeacon.sol new file mode 100644 index 00000000..711eb407 --- /dev/null +++ b/contracts/proxy/beacon/DiamondBeacon.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { OwnableInternal } from '../../access/ownable/Ownable.sol'; +import { Ownable } from '../../access/ownable/Ownable.sol'; +import { DiamondReadable } from '../diamond/readable/DiamondReadable.sol'; +import { DiamondWritable } from '../diamond/writable/DiamondWritable.sol'; +import { IDiamondBeacon } from './IDiamondBeacon.sol'; +import { DiamondBeaconInternal } from './DiamondBeaconInternal.sol'; + +/** + * @title Beacon contract which imitates the upgrade mechanism of an EIP-2535 diamond proxy. + * @dev Configure this beacon using diamondCut as if it were a diamond proxy. Proxies can fetch their implementations by calling facetAddress. + */ +contract DiamondBeacon is + IDiamondBeacon, + DiamondBeaconInternal, + DiamondReadable, + DiamondWritable, + Ownable +{} diff --git a/contracts/proxy/beacon/DiamondBeaconInternal.sol b/contracts/proxy/beacon/DiamondBeaconInternal.sol new file mode 100644 index 00000000..fbac4703 --- /dev/null +++ b/contracts/proxy/beacon/DiamondBeaconInternal.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IDiamondBeaconInternal } from './IDiamondBeaconInternal.sol'; + +abstract contract DiamondBeaconInternal is IDiamondBeaconInternal {} diff --git a/contracts/proxy/beacon/DiamondBeaconProxy.sol b/contracts/proxy/beacon/DiamondBeaconProxy.sol new file mode 100644 index 00000000..73267188 --- /dev/null +++ b/contracts/proxy/beacon/DiamondBeaconProxy.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { BeaconProxy } from './BeaconProxy.sol'; +import { IDiamondBeacon } from './IDiamondBeacon.sol'; +import { IDiamondBeaconProxy } from './IDiamondBeaconProxy.sol'; + +abstract contract DiamondBeaconProxy is IDiamondBeaconProxy, BeaconProxy { + function _getImplementation() internal view override returns (address) { + return IDiamondBeacon(_getBeacon()).facetAddress(msg.sig); + } +} diff --git a/contracts/proxy/beacon/IBeacon.sol b/contracts/proxy/beacon/IBeacon.sol new file mode 100644 index 00000000..27b754df --- /dev/null +++ b/contracts/proxy/beacon/IBeacon.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IOwnable } from '../../access/ownable/IOwnable.sol'; +import { BeaconStorage } from './BeaconStorage.sol'; + +interface IBeacon is IOwnable { + /** + * @notice query the address of the implementation that should be used by BeaconProxy instancesf + * @return implementation address of the implementation contract + */ + function getImplementation() external view returns (address implementation); + + /** + * @notice set the address of the implementation that should be used by BeaconProxy instancesf + * @param implementation address of the implementation contract + */ + function setImplementation(address implementation) external; +} diff --git a/contracts/proxy/beacon/IBeaconInternal.sol b/contracts/proxy/beacon/IBeaconInternal.sol new file mode 100644 index 00000000..1be4ee09 --- /dev/null +++ b/contracts/proxy/beacon/IBeaconInternal.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IOwnableInternal } from '../../access/ownable/IOwnableInternal.sol'; + +interface IBeaconInternal is IOwnableInternal {} diff --git a/contracts/proxy/managed/IManagedProxy.sol b/contracts/proxy/beacon/IBeaconProxy.sol similarity index 52% rename from contracts/proxy/managed/IManagedProxy.sol rename to contracts/proxy/beacon/IBeaconProxy.sol index 5657a558..613e6076 100644 --- a/contracts/proxy/managed/IManagedProxy.sol +++ b/contracts/proxy/beacon/IBeaconProxy.sol @@ -4,6 +4,4 @@ pragma solidity ^0.8.20; import { IProxy } from '../IProxy.sol'; -interface IManagedProxy is IProxy { - error ManagedProxy__FetchImplementationFailed(); -} +interface IBeaconProxy is IProxy {} diff --git a/contracts/proxy/beacon/IDiamondBeacon.sol b/contracts/proxy/beacon/IDiamondBeacon.sol new file mode 100644 index 00000000..5241e636 --- /dev/null +++ b/contracts/proxy/beacon/IDiamondBeacon.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IOwnable } from '../../access/ownable/IOwnable.sol'; +import { IDiamondReadable } from '../diamond/readable/IDiamondReadable.sol'; +import { IDiamondWritable } from '../diamond/writable/IDiamondWritable.sol'; +import { IDiamondBeaconInternal } from './IDiamondBeaconInternal.sol'; + +interface IDiamondBeacon is + IDiamondReadable, + IDiamondWritable, + IOwnable, + IDiamondBeaconInternal +{} diff --git a/contracts/proxy/beacon/IDiamondBeaconInternal.sol b/contracts/proxy/beacon/IDiamondBeaconInternal.sol new file mode 100644 index 00000000..18d447fe --- /dev/null +++ b/contracts/proxy/beacon/IDiamondBeaconInternal.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IBeaconInternal } from './IBeaconInternal.sol'; + +interface IDiamondBeaconInternal is IBeaconInternal {} diff --git a/contracts/proxy/beacon/IDiamondBeaconProxy.sol b/contracts/proxy/beacon/IDiamondBeaconProxy.sol new file mode 100644 index 00000000..aff1e93f --- /dev/null +++ b/contracts/proxy/beacon/IDiamondBeaconProxy.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IBeaconProxy } from './IBeaconProxy.sol'; + +interface IDiamondBeaconProxy is IBeaconProxy {} diff --git a/contracts/proxy/managed/IManagedProxyOwnable.sol b/contracts/proxy/managed/IManagedProxyOwnable.sol deleted file mode 100644 index 6b87a510..00000000 --- a/contracts/proxy/managed/IManagedProxyOwnable.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { IManagedProxy } from './IManagedProxy.sol'; - -interface IManagedProxyOwnable is IManagedProxy {} diff --git a/contracts/proxy/managed/ManagedProxy.sol b/contracts/proxy/managed/ManagedProxy.sol deleted file mode 100644 index 8f696653..00000000 --- a/contracts/proxy/managed/ManagedProxy.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { Proxy } from '../Proxy.sol'; -import { IManagedProxy } from './IManagedProxy.sol'; - -/** - * @title Proxy with externally controlled implementation - * @dev implementation fetched using immutable function selector - */ -abstract contract ManagedProxy is IManagedProxy, Proxy { - bytes4 internal immutable MANAGER_SELECTOR; - - /** - * @param managerSelector function selector used to fetch implementation from manager - */ - constructor(bytes4 managerSelector) { - MANAGER_SELECTOR = managerSelector; - } - - /** - * @inheritdoc Proxy - */ - function _getImplementation() internal view override returns (address) { - (bool success, bytes memory data) = _getManager().staticcall( - abi.encodePacked(MANAGER_SELECTOR) - ); - if (!success) revert ManagedProxy__FetchImplementationFailed(); - return abi.decode(data, (address)); - } - - /** - * @notice get manager of proxy implementation - * @return manager address - */ - function _getManager() internal view virtual returns (address); -} diff --git a/contracts/proxy/managed/ManagedProxyMock.sol b/contracts/proxy/managed/ManagedProxyMock.sol deleted file mode 100644 index 91b11685..00000000 --- a/contracts/proxy/managed/ManagedProxyMock.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { ManagedProxy } from './ManagedProxy.sol'; - -contract ManagedProxyMock is ManagedProxy { - address private _manager; - - constructor( - address manager, - bytes4 managerSelector - ) ManagedProxy(managerSelector) { - setManager(manager); - } - - function _getManager() internal view override returns (address) { - return _manager; - } - - function __getImplementation() external view returns (address) { - return _getImplementation(); - } - - function setManager(address manager) public { - _manager = manager; - } - - /** - * @dev suppress compiler warning - */ - receive() external payable {} -} diff --git a/contracts/proxy/managed/ManagedProxyOwnable.sol b/contracts/proxy/managed/ManagedProxyOwnable.sol deleted file mode 100644 index 19f4531f..00000000 --- a/contracts/proxy/managed/ManagedProxyOwnable.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { OwnableInternal } from '../../access/ownable/OwnableInternal.sol'; -import { IManagedProxyOwnable } from './IManagedProxyOwnable.sol'; -import { ManagedProxy } from './ManagedProxy.sol'; - -/** - * @title Proxy with implementation controlled by ERC171 owner - */ -abstract contract ManagedProxyOwnable is - IManagedProxyOwnable, - ManagedProxy, - OwnableInternal -{ - /** - * @inheritdoc ManagedProxy - */ - function _getManager() internal view override returns (address) { - return _owner(); - } -} diff --git a/contracts/proxy/managed/ManagedProxyOwnableMock.sol b/contracts/proxy/managed/ManagedProxyOwnableMock.sol deleted file mode 100644 index 6827e60e..00000000 --- a/contracts/proxy/managed/ManagedProxyOwnableMock.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { ManagedProxy, ManagedProxyOwnable } from './ManagedProxyOwnable.sol'; - -contract ManagedProxyOwnableMock is ManagedProxyOwnable { - constructor( - address manager, - bytes4 managerSelector - ) ManagedProxy(managerSelector) { - setOwner(manager); - } - - function __getImplementation() external view returns (address) { - return _getImplementation(); - } - - function __getManager() external view returns (address) { - return _getManager(); - } - - function getOwner() external view returns (address) { - return _owner(); - } - - function setOwner(address owner) public { - _setOwner(owner); - } - - /** - * @dev suppress compiler warning - */ - receive() external payable {} -} diff --git a/spec/proxy/beacon/BeaconProxy.behavior.ts b/spec/proxy/beacon/BeaconProxy.behavior.ts new file mode 100644 index 00000000..13e4d6e7 --- /dev/null +++ b/spec/proxy/beacon/BeaconProxy.behavior.ts @@ -0,0 +1,17 @@ +import { describeBehaviorOfProxy, ProxyBehaviorArgs } from '../Proxy.behavior'; +import { describeFilter } from '@solidstate/library'; +import { IBeaconProxy } from '@solidstate/typechain-types'; + +export interface BeaconProxyBehaviorArgs extends ProxyBehaviorArgs {} + +export function describeBehaviorOfBeaconProxy( + deploy: () => Promise, + args: BeaconProxyBehaviorArgs, + skips?: string[], +) { + const describe = describeFilter(skips); + + describe('::BeaconProxy', () => { + describeBehaviorOfProxy(deploy, args, skips); + }); +} diff --git a/spec/proxy/beacon/index.ts b/spec/proxy/beacon/index.ts new file mode 100644 index 00000000..89e19604 --- /dev/null +++ b/spec/proxy/beacon/index.ts @@ -0,0 +1 @@ +export * from './BeaconProxy.behavior'; diff --git a/spec/proxy/index.ts b/spec/proxy/index.ts index 22e6a8b9..5d8128ab 100644 --- a/spec/proxy/index.ts +++ b/spec/proxy/index.ts @@ -1,5 +1,5 @@ export * from './diamond'; -export * from './managed'; +export * from './beacon'; export * from './upgradeable'; export * from './Proxy.behavior'; diff --git a/spec/proxy/managed/ManagedProxy.behavior.ts b/spec/proxy/managed/ManagedProxy.behavior.ts deleted file mode 100644 index a0c549dc..00000000 --- a/spec/proxy/managed/ManagedProxy.behavior.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describeBehaviorOfProxy, ProxyBehaviorArgs } from '../Proxy.behavior'; -import { describeFilter } from '@solidstate/library'; -import { IManagedProxy } from '@solidstate/typechain-types'; - -export interface ManagedProxyBehaviorArgs extends ProxyBehaviorArgs {} - -export function describeBehaviorOfManagedProxy( - deploy: () => Promise, - args: ManagedProxyBehaviorArgs, - skips?: string[], -) { - const describe = describeFilter(skips); - - describe('::ManagedProxy', () => { - describeBehaviorOfProxy(deploy, args, skips); - }); -} diff --git a/spec/proxy/managed/ManagedProxyOwnable.behavior.ts b/spec/proxy/managed/ManagedProxyOwnable.behavior.ts deleted file mode 100644 index 0d49d9a5..00000000 --- a/spec/proxy/managed/ManagedProxyOwnable.behavior.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - describeBehaviorOfManagedProxy, - ManagedProxyBehaviorArgs, -} from './ManagedProxy.behavior'; -import { describeFilter } from '@solidstate/library'; -import { IManagedProxyOwnable } from '@solidstate/typechain-types'; - -export interface ManagedProxyOwnableBehaviorArgs - extends ManagedProxyBehaviorArgs {} - -export function describeBehaviorOfManagedProxyOwnable( - deploy: () => Promise, - args: ManagedProxyOwnableBehaviorArgs, - skips?: string[], -) { - const describe = describeFilter(skips); - - describe('::ManagedProxyOwnable', () => { - describeBehaviorOfManagedProxy(deploy, args, skips); - }); -} diff --git a/spec/proxy/managed/index.ts b/spec/proxy/managed/index.ts deleted file mode 100644 index c7d0c534..00000000 --- a/spec/proxy/managed/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './ManagedProxy.behavior'; -export * from './ManagedProxyOwnable.behavior'; diff --git a/test/proxy/beacon/BeaconProxy.ts b/test/proxy/beacon/BeaconProxy.ts new file mode 100644 index 00000000..ab1df9cc --- /dev/null +++ b/test/proxy/beacon/BeaconProxy.ts @@ -0,0 +1,57 @@ +import { deployMockContract } from '@solidstate/library'; +import { describeBehaviorOfBeaconProxy } from '@solidstate/spec'; +import { + BeaconProxyMock, + BeaconProxyMock__factory, + OwnableMock__factory, +} from '@solidstate/typechain-types'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +describe('BeaconProxy', () => { + let beacon: any; + let instance: BeaconProxyMock; + + beforeEach(async () => { + const [deployer] = await ethers.getSigners(); + + const implementationInstance = await new OwnableMock__factory( + deployer, + ).deploy(ethers.ZeroAddress); + + beacon = await deployMockContract((await ethers.getSigners())[0], [ + 'function getImplementation () external view returns (address)', + ]); + + await beacon.mock.getImplementation.returns( + await implementationInstance.getAddress(), + ); + + instance = await new BeaconProxyMock__factory(deployer).deploy( + beacon.address, + ); + }); + + describeBehaviorOfBeaconProxy(async () => instance, { + implementationFunction: 'owner()', + implementationFunctionArgs: [], + }); + + describe('__internal', () => { + describe('#_getImplementation()', () => { + it('returns implementation address', async () => { + expect(await instance.__getImplementation.staticCall()).to.be + .properAddress; + }); + + describe('reverts if', () => { + it('beacon is non-contract address', async () => { + await instance.setBeacon(ethers.ZeroAddress); + + await expect(instance.__getImplementation.staticCall()).to.be + .reverted; + }); + }); + }); + }); +}); diff --git a/test/proxy/managed/ManagedProxy.ts b/test/proxy/managed/ManagedProxy.ts deleted file mode 100644 index 60f5dde4..00000000 --- a/test/proxy/managed/ManagedProxy.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { deployMockContract } from '@solidstate/library'; -import { describeBehaviorOfManagedProxy } from '@solidstate/spec'; -import { - ManagedProxyMock, - ManagedProxyMock__factory, - OwnableMock__factory, -} from '@solidstate/typechain-types'; -import { expect } from 'chai'; -import { ethers } from 'hardhat'; - -describe('ManagedProxy', () => { - let manager: any; - let instance: ManagedProxyMock; - - beforeEach(async () => { - const [deployer] = await ethers.getSigners(); - - const implementationInstance = await new OwnableMock__factory( - deployer, - ).deploy(ethers.ZeroAddress); - - manager = await deployMockContract((await ethers.getSigners())[0], [ - 'function getImplementation () external view returns (address)', - ]); - - await manager.mock.getImplementation.returns( - await implementationInstance.getAddress(), - ); - - const selector = ethers.dataSlice( - ethers.solidityPackedKeccak256(['string'], ['getImplementation()']), - 0, - 4, - ); - - instance = await new ManagedProxyMock__factory(deployer).deploy( - manager.address, - selector, - ); - }); - - describeBehaviorOfManagedProxy(async () => instance, { - implementationFunction: 'owner()', - implementationFunctionArgs: [], - }); - - describe('__internal', () => { - describe('#_getImplementation()', () => { - it('returns implementation address', async () => { - expect(await instance.__getImplementation.staticCall()).to.be - .properAddress; - }); - - describe('reverts if', () => { - it('manager is non-contract address', async () => { - await instance.setManager(ethers.ZeroAddress); - - await expect(instance.__getImplementation.staticCall()).to.be - .reverted; - }); - - it('manager fails to return implementation', async () => { - await manager.mock.getImplementation.revertsWithReason('ERROR'); - - await expect( - instance.__getImplementation.staticCall(), - ).to.be.revertedWithCustomError( - instance, - 'ManagedProxy__FetchImplementationFailed', - ); - }); - }); - }); - }); -}); diff --git a/test/proxy/managed/ManagedProxyOwnable.ts b/test/proxy/managed/ManagedProxyOwnable.ts deleted file mode 100644 index a51d8f6d..00000000 --- a/test/proxy/managed/ManagedProxyOwnable.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { deployMockContract } from '@solidstate/library'; -import { describeBehaviorOfManagedProxyOwnable } from '@solidstate/spec'; -import { - ManagedProxyOwnableMock, - ManagedProxyOwnableMock__factory, - OwnableMock__factory, -} from '@solidstate/typechain-types'; -import { expect } from 'chai'; -import { ethers } from 'hardhat'; - -describe('ManagedProxyOwnable', () => { - let manager: any; - let instance: ManagedProxyOwnableMock; - - beforeEach(async () => { - const [deployer] = await ethers.getSigners(); - - manager = await deployMockContract((await ethers.getSigners())[0], [ - 'function getImplementation () external view returns (address)', - ]); - - const implementationInstance = await new OwnableMock__factory( - deployer, - ).deploy(deployer.address); - - await manager.mock.getImplementation.returns( - await implementationInstance.getAddress(), - ); - - const selector = ethers.dataSlice( - ethers.solidityPackedKeccak256(['string'], ['getImplementation()']), - 0, - 4, - ); - - instance = await new ManagedProxyOwnableMock__factory(deployer).deploy( - manager.address, - selector, - ); - }); - - describeBehaviorOfManagedProxyOwnable(async () => instance, { - implementationFunction: 'owner()', - implementationFunctionArgs: [], - }); - - describe('__internal', () => { - describe('#_getImplementation()', () => { - it('returns implementation address'); - - describe('reverts if', () => { - it('manager is non-contract address', async () => { - await instance.setOwner(ethers.ZeroAddress); - - await expect(instance.__getImplementation.staticCall()).to.be - .reverted; - }); - - it('manager fails to return implementation', async () => { - await manager.mock.getImplementation.revertsWithReason('ERROR'); - - await expect( - instance.__getImplementation.staticCall(), - ).to.be.revertedWithCustomError( - instance, - 'ManagedProxy__FetchImplementationFailed', - ); - }); - }); - }); - - describe('#_getManager()', () => { - it('returns address of ERC173 owner', async () => { - expect(await instance.__getManager.staticCall()).to.equal( - await instance.getOwner.staticCall(), - ); - }); - }); - }); -});