diff --git a/abi/Beacon.json b/abi/Beacon.json new file mode 100644 index 000000000..135dd8d12 --- /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/ManagedProxyOwnableInternal.json b/abi/BeaconInternal.json similarity index 84% rename from abi/ManagedProxyOwnableInternal.json rename to abi/BeaconInternal.json index 7c8698a0b..7f3369632 100644 --- a/abi/ManagedProxyOwnableInternal.json +++ b/abi/BeaconInternal.json @@ -1,9 +1,4 @@ [ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - }, { "inputs": [], "name": "Ownable__NotOwner", diff --git a/abi/IManagedProxy.json b/abi/BeaconProxy.json similarity index 61% rename from abi/IManagedProxy.json rename to abi/BeaconProxy.json index a412d93f2..7661f993a 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 000000000..3f999842d --- /dev/null +++ b/abi/DiamondBeacon.json @@ -0,0 +1,260 @@ +[ + { + "inputs": [], + "name": "DiamondBeacon__InvalidInput", + "type": "error" + }, + { + "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 000000000..27e2dbeb8 --- /dev/null +++ b/abi/DiamondBeaconInternal.json @@ -0,0 +1,118 @@ +[ + { + "inputs": [], + "name": "DiamondBeacon__InvalidInput", + "type": "error" + }, + { + "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" + } +] diff --git a/abi/ManagedProxy.json b/abi/DiamondBeaconProxy.json similarity index 61% rename from abi/ManagedProxy.json rename to abi/DiamondBeaconProxy.json index a412d93f2..7661f993a 100644 --- a/abi/ManagedProxy.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 000000000..135dd8d12 --- /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/IManagedProxyOwnableInternal.json b/abi/IBeaconInternal.json similarity index 84% rename from abi/IManagedProxyOwnableInternal.json rename to abi/IBeaconInternal.json index 7c8698a0b..7f3369632 100644 --- a/abi/IManagedProxyOwnableInternal.json +++ b/abi/IBeaconInternal.json @@ -1,9 +1,4 @@ [ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - }, { "inputs": [], "name": "Ownable__NotOwner", diff --git a/abi/IBeaconProxy.json b/abi/IBeaconProxy.json new file mode 100644 index 000000000..7661f993a --- /dev/null +++ b/abi/IBeaconProxy.json @@ -0,0 +1,11 @@ +[ + { + "inputs": [], + "name": "Proxy__ImplementationIsNotContract", + "type": "error" + }, + { + "stateMutability": "payable", + "type": "fallback" + } +] diff --git a/abi/IDiamondBeacon.json b/abi/IDiamondBeacon.json new file mode 100644 index 000000000..3f999842d --- /dev/null +++ b/abi/IDiamondBeacon.json @@ -0,0 +1,260 @@ +[ + { + "inputs": [], + "name": "DiamondBeacon__InvalidInput", + "type": "error" + }, + { + "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 000000000..27e2dbeb8 --- /dev/null +++ b/abi/IDiamondBeaconInternal.json @@ -0,0 +1,118 @@ +[ + { + "inputs": [], + "name": "DiamondBeacon__InvalidInput", + "type": "error" + }, + { + "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" + } +] diff --git a/abi/IDiamondBeaconProxy.json b/abi/IDiamondBeaconProxy.json new file mode 100644 index 000000000..7661f993a --- /dev/null +++ b/abi/IDiamondBeaconProxy.json @@ -0,0 +1,11 @@ +[ + { + "inputs": [], + "name": "Proxy__ImplementationIsNotContract", + "type": "error" + }, + { + "stateMutability": "payable", + "type": "fallback" + } +] diff --git a/abi/IManagedProxyInternal.json b/abi/IManagedProxyInternal.json deleted file mode 100644 index 26b418d32..000000000 --- a/abi/IManagedProxyInternal.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - } -] diff --git a/abi/IManagedProxyOwnable.json b/abi/IManagedProxyOwnable.json deleted file mode 100644 index fcf0038e3..000000000 --- a/abi/IManagedProxyOwnable.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - }, - { - "inputs": [], - "name": "Ownable__NotOwner", - "type": "error" - }, - { - "inputs": [], - "name": "Ownable__NotTransitiveOwner", - "type": "error" - }, - { - "inputs": [], - "name": "Proxy__ImplementationIsNotContract", - "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" - }, - { - "stateMutability": "payable", - "type": "fallback" - } -] diff --git a/abi/ManagedProxyInternal.json b/abi/ManagedProxyInternal.json deleted file mode 100644 index 26b418d32..000000000 --- a/abi/ManagedProxyInternal.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - } -] diff --git a/abi/ManagedProxyOwnable.json b/abi/ManagedProxyOwnable.json deleted file mode 100644 index fcf0038e3..000000000 --- a/abi/ManagedProxyOwnable.json +++ /dev/null @@ -1,45 +0,0 @@ -[ - { - "inputs": [], - "name": "ManagedProxy__FetchImplementationFailed", - "type": "error" - }, - { - "inputs": [], - "name": "Ownable__NotOwner", - "type": "error" - }, - { - "inputs": [], - "name": "Ownable__NotTransitiveOwner", - "type": "error" - }, - { - "inputs": [], - "name": "Proxy__ImplementationIsNotContract", - "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" - }, - { - "stateMutability": "payable", - "type": "fallback" - } -] diff --git a/contracts/proxy/beacon/Beacon.sol b/contracts/proxy/beacon/Beacon.sol new file mode 100644 index 000000000..98e742f57 --- /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, BeaconInternal, Ownable { + /** + * @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 000000000..ad8cb88c3 --- /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 instances + * @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 instances + * @param implementation address of the implementation contract + */ + function _setImplementation( + address implementation + ) internal virtual onlyOwner { + BeaconStorage.layout().implementation = implementation; + } +} diff --git a/contracts/proxy/beacon/BeaconMock.sol b/contracts/proxy/beacon/BeaconMock.sol new file mode 100644 index 000000000..1bf70c9c3 --- /dev/null +++ b/contracts/proxy/beacon/BeaconMock.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { Beacon } from './Beacon.sol'; + +contract BeaconMock is Beacon { + constructor(address owner) { + _setOwner(owner); + } + + function __getImplementation() external view returns (address) { + return _getImplementation(); + } + + function __setImplementation(address implementation) external { + _setImplementation(implementation); + } +} diff --git a/contracts/proxy/beacon/BeaconProxy.sol b/contracts/proxy/beacon/BeaconProxy.sol new file mode 100644 index 000000000..38d9739d6 --- /dev/null +++ b/contracts/proxy/beacon/BeaconProxy.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { Proxy } from '../Proxy.sol'; +import { IBeaconProxy } from './IBeaconProxy.sol'; +import { BeaconProxyInternal } from './BeaconProxyInternal.sol'; + +/** + * @title Proxy with externally controlled implementation + * @dev implementation fetched using immutable function selector + */ +abstract contract BeaconProxy is IBeaconProxy, BeaconProxyInternal, Proxy {} diff --git a/contracts/proxy/beacon/BeaconProxyInternal.sol b/contracts/proxy/beacon/BeaconProxyInternal.sol new file mode 100644 index 000000000..8d54eddbd --- /dev/null +++ b/contracts/proxy/beacon/BeaconProxyInternal.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { ProxyInternal } from '../ProxyInternal.sol'; +import { IBeacon } from './IBeacon.sol'; +import { IBeaconProxyInternal } from './IBeaconProxyInternal.sol'; + +abstract contract BeaconProxyInternal is IBeaconProxyInternal, ProxyInternal { + /** + * @inheritdoc ProxyInternal + */ + function _getImplementation() + internal + view + virtual + override + returns (address implementation) + { + implementation = 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 000000000..f06155f84 --- /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 000000000..4f67cd3ee --- /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/IBeacon.sol b/contracts/proxy/beacon/IBeacon.sol new file mode 100644 index 000000000..f1ca75a88 --- /dev/null +++ b/contracts/proxy/beacon/IBeacon.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IOwnable } from '../../access/ownable/IOwnable.sol'; +import { IBeaconInternal } from './IBeaconInternal.sol'; +import { BeaconStorage } from './BeaconStorage.sol'; + +interface IBeacon is IBeaconInternal, IOwnable { + /** + * @notice query the address of the implementation that should be used by BeaconProxy instances + * @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 instances + * @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 000000000..1be4ee09c --- /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/beacon/IBeaconProxy.sol b/contracts/proxy/beacon/IBeaconProxy.sol new file mode 100644 index 000000000..45c91c317 --- /dev/null +++ b/contracts/proxy/beacon/IBeaconProxy.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IProxy } from '../IProxy.sol'; +import { IBeaconProxyInternal } from './IBeaconProxyInternal.sol'; + +interface IBeaconProxy is IBeaconProxyInternal, IProxy {} diff --git a/contracts/proxy/managed/IManagedProxyInternal.sol b/contracts/proxy/beacon/IBeaconProxyInternal.sol similarity index 52% rename from contracts/proxy/managed/IManagedProxyInternal.sol rename to contracts/proxy/beacon/IBeaconProxyInternal.sol index 294ae4894..1ade8ab16 100644 --- a/contracts/proxy/managed/IManagedProxyInternal.sol +++ b/contracts/proxy/beacon/IBeaconProxyInternal.sol @@ -4,6 +4,4 @@ pragma solidity ^0.8.20; import { IProxyInternal } from '../IProxyInternal.sol'; -interface IManagedProxyInternal is IProxyInternal { - error ManagedProxy__FetchImplementationFailed(); -} +interface IBeaconProxyInternal is IProxyInternal {} diff --git a/contracts/proxy/beacon/diamond/DiamondBeacon.sol b/contracts/proxy/beacon/diamond/DiamondBeacon.sol new file mode 100644 index 000000000..5049e801a --- /dev/null +++ b/contracts/proxy/beacon/diamond/DiamondBeacon.sol @@ -0,0 +1,38 @@ +// 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 { DiamondWritableInternal } from '../../diamond/writable/DiamondWritableInternal.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 +{ + /** + * @inheritdoc DiamondBeaconInternal + */ + function _diamondCut( + FacetCut[] memory facetCuts, + address target, + bytes memory data + ) + internal + virtual + override(DiamondWritableInternal, DiamondBeaconInternal) + { + super._diamondCut(facetCuts, target, data); + } +} diff --git a/contracts/proxy/beacon/diamond/DiamondBeaconInternal.sol b/contracts/proxy/beacon/diamond/DiamondBeaconInternal.sol new file mode 100644 index 000000000..e63aba412 --- /dev/null +++ b/contracts/proxy/beacon/diamond/DiamondBeaconInternal.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { OwnableInternal } from '../../../access/ownable/OwnableInternal.sol'; +import { DiamondReadableInternal } from '../../diamond/readable/DiamondReadableInternal.sol'; +import { DiamondWritableInternal } from '../../diamond/writable/DiamondWritableInternal.sol'; +import { IDiamondBeaconInternal } from './IDiamondBeaconInternal.sol'; + +abstract contract DiamondBeaconInternal is + IDiamondBeaconInternal, + OwnableInternal, + DiamondReadableInternal, + DiamondWritableInternal +{ + /** + * @inheritdoc DiamondWritableInternal + * @param target unused (input must be zero address) + * @param data unused (input must be zero bytes) + */ + function _diamondCut( + FacetCut[] memory facetCuts, + address target, + bytes memory data + ) internal virtual override { + if (target != address(0) || data.length != 0) + revert DiamondBeacon__InvalidInput(); + super._diamondCut(facetCuts, target, data); + } +} diff --git a/contracts/proxy/beacon/diamond/DiamondBeaconMock.sol b/contracts/proxy/beacon/diamond/DiamondBeaconMock.sol new file mode 100644 index 000000000..4a902737f --- /dev/null +++ b/contracts/proxy/beacon/diamond/DiamondBeaconMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { DiamondBeacon } from './DiamondBeacon.sol'; + +contract DiamondBeaconMock is DiamondBeacon { + constructor(address owner, FacetCut[] memory cuts) { + _setOwner(owner); + _diamondCut(cuts, address(0), ''); + } +} diff --git a/contracts/proxy/beacon/diamond/DiamondBeaconProxy.sol b/contracts/proxy/beacon/diamond/DiamondBeaconProxy.sol new file mode 100644 index 000000000..ab4e281c7 --- /dev/null +++ b/contracts/proxy/beacon/diamond/DiamondBeaconProxy.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { ProxyInternal } from '../../ProxyInternal.sol'; +import { BeaconProxy } from '../BeaconProxy.sol'; +import { BeaconProxyInternal } from '../BeaconProxyInternal.sol'; +import { DiamondBeaconProxyInternal } from './DiamondBeaconProxyInternal.sol'; +import { IDiamondBeaconProxy } from './IDiamondBeaconProxy.sol'; + +abstract contract DiamondBeaconProxy is + IDiamondBeaconProxy, + DiamondBeaconProxyInternal, + BeaconProxy +{ + function _getImplementation() + internal + view + override(ProxyInternal, BeaconProxyInternal, DiamondBeaconProxyInternal) + returns (address implementation) + { + implementation = super._getImplementation(); + } +} diff --git a/contracts/proxy/beacon/diamond/DiamondBeaconProxyInternal.sol b/contracts/proxy/beacon/diamond/DiamondBeaconProxyInternal.sol new file mode 100644 index 000000000..47d7f03fc --- /dev/null +++ b/contracts/proxy/beacon/diamond/DiamondBeaconProxyInternal.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { BeaconProxyInternal } from '../BeaconProxyInternal.sol'; +import { IDiamondBeacon } from './IDiamondBeacon.sol'; +import { IDiamondBeaconProxyInternal } from './IDiamondBeaconProxyInternal.sol'; + +abstract contract DiamondBeaconProxyInternal is + IDiamondBeaconProxyInternal, + BeaconProxyInternal +{ + function _getImplementation() + internal + view + virtual + override + returns (address implementation) + { + implementation = _getImplementation(msg.sig); + } + + function _getImplementation( + bytes4 sig + ) internal view virtual returns (address implementation) { + implementation = IDiamondBeacon(_getBeacon()).facetAddress(sig); + } +} diff --git a/contracts/proxy/beacon/diamond/DiamondBeaconProxyMock.sol b/contracts/proxy/beacon/diamond/DiamondBeaconProxyMock.sol new file mode 100644 index 000000000..6a27c9163 --- /dev/null +++ b/contracts/proxy/beacon/diamond/DiamondBeaconProxyMock.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { DiamondBeaconProxy } from './DiamondBeaconProxy.sol'; + +contract DiamondBeaconProxyMock is DiamondBeaconProxy { + 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 __getImplementation(bytes4 sig) external view returns (address) { + return _getImplementation(sig); + } + + function setBeacon(address beacon) public { + _beacon = beacon; + } + + /** + * @dev suppress compiler warning + */ + receive() external payable {} +} diff --git a/contracts/proxy/beacon/diamond/IDiamondBeacon.sol b/contracts/proxy/beacon/diamond/IDiamondBeacon.sol new file mode 100644 index 000000000..b0b189b77 --- /dev/null +++ b/contracts/proxy/beacon/diamond/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 + IDiamondBeaconInternal, + IDiamondReadable, + IDiamondWritable, + IOwnable +{} diff --git a/contracts/proxy/beacon/diamond/IDiamondBeaconInternal.sol b/contracts/proxy/beacon/diamond/IDiamondBeaconInternal.sol new file mode 100644 index 000000000..ad8a6058a --- /dev/null +++ b/contracts/proxy/beacon/diamond/IDiamondBeaconInternal.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IOwnableInternal } from '../../../access/ownable/IOwnableInternal.sol'; +import { IDiamondReadableInternal } from '../../diamond/readable/IDiamondReadableInternal.sol'; +import { IDiamondWritableInternal } from '../../diamond/writable/IDiamondWritableInternal.sol'; + +interface IDiamondBeaconInternal is + IOwnableInternal, + IDiamondReadableInternal, + IDiamondWritableInternal +{ + error DiamondBeacon__InvalidInput(); +} diff --git a/contracts/proxy/beacon/diamond/IDiamondBeaconProxy.sol b/contracts/proxy/beacon/diamond/IDiamondBeaconProxy.sol new file mode 100644 index 000000000..f9729452a --- /dev/null +++ b/contracts/proxy/beacon/diamond/IDiamondBeaconProxy.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IBeaconProxy } from '../IBeaconProxy.sol'; +import { IDiamondBeaconProxyInternal } from './IDiamondBeaconProxyInternal.sol'; + +interface IDiamondBeaconProxy is IDiamondBeaconProxyInternal, IBeaconProxy {} diff --git a/contracts/proxy/beacon/diamond/IDiamondBeaconProxyInternal.sol b/contracts/proxy/beacon/diamond/IDiamondBeaconProxyInternal.sol new file mode 100644 index 000000000..7323e98be --- /dev/null +++ b/contracts/proxy/beacon/diamond/IDiamondBeaconProxyInternal.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IBeaconProxyInternal } from '../IBeaconProxyInternal.sol'; + +interface IDiamondBeaconProxyInternal is IBeaconProxyInternal {} diff --git a/contracts/proxy/managed/IManagedProxy.sol b/contracts/proxy/managed/IManagedProxy.sol deleted file mode 100644 index 092363df1..000000000 --- a/contracts/proxy/managed/IManagedProxy.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { IProxy } from '../IProxy.sol'; -import { IManagedProxyInternal } from './IManagedProxyInternal.sol'; - -interface IManagedProxy is IManagedProxyInternal, IProxy {} diff --git a/contracts/proxy/managed/IManagedProxyOwnable.sol b/contracts/proxy/managed/IManagedProxyOwnable.sol deleted file mode 100644 index 0cc3196ca..000000000 --- a/contracts/proxy/managed/IManagedProxyOwnable.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { IManagedProxy } from './IManagedProxy.sol'; -import { IManagedProxyOwnableInternal } from './IManagedProxyOwnableInternal.sol'; - -interface IManagedProxyOwnable is IManagedProxyOwnableInternal, IManagedProxy {} diff --git a/contracts/proxy/managed/IManagedProxyOwnableInternal.sol b/contracts/proxy/managed/IManagedProxyOwnableInternal.sol deleted file mode 100644 index 7cb4eb81e..000000000 --- a/contracts/proxy/managed/IManagedProxyOwnableInternal.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { IOwnableInternal } from '../../access/ownable/IOwnableInternal.sol'; -import { IManagedProxyInternal } from './IManagedProxyInternal.sol'; - -interface IManagedProxyOwnableInternal is - IManagedProxyInternal, - IOwnableInternal -{} diff --git a/contracts/proxy/managed/ManagedProxy.sol b/contracts/proxy/managed/ManagedProxy.sol deleted file mode 100644 index 4c3e38493..000000000 --- a/contracts/proxy/managed/ManagedProxy.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { Proxy } from '../Proxy.sol'; -import { ProxyInternal } from '../ProxyInternal.sol'; -import { IManagedProxy } from './IManagedProxy.sol'; -import { ManagedProxyInternal } from './ManagedProxyInternal.sol'; - -/** - * @title Proxy with externally controlled implementation - * @dev implementation fetched using immutable function selector - */ -abstract contract ManagedProxy is IManagedProxy, ManagedProxyInternal, Proxy { - /** - * @param managerSelector function selector used to fetch implementation from manager - */ - constructor(bytes4 managerSelector) { - MANAGER_SELECTOR = managerSelector; - } -} diff --git a/contracts/proxy/managed/ManagedProxyInternal.sol b/contracts/proxy/managed/ManagedProxyInternal.sol deleted file mode 100644 index 206288e37..000000000 --- a/contracts/proxy/managed/ManagedProxyInternal.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { ProxyInternal } from '../ProxyInternal.sol'; -import { IManagedProxyInternal } from './IManagedProxyInternal.sol'; - -abstract contract ManagedProxyInternal is IManagedProxyInternal, ProxyInternal { - bytes4 internal immutable MANAGER_SELECTOR; - - /** - * @inheritdoc ProxyInternal - */ - 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 91b11685d..000000000 --- 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 9bfa8477f..000000000 --- a/contracts/proxy/managed/ManagedProxyOwnable.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { IManagedProxyOwnable } from './IManagedProxyOwnable.sol'; -import { ManagedProxyOwnableInternal } from './ManagedProxyOwnableInternal.sol'; -import { ManagedProxy } from './ManagedProxy.sol'; - -/** - * @title Proxy with implementation controlled by ERC171 owner - */ -abstract contract ManagedProxyOwnable is - IManagedProxyOwnable, - ManagedProxyOwnableInternal, - ManagedProxy -{} diff --git a/contracts/proxy/managed/ManagedProxyOwnableInternal.sol b/contracts/proxy/managed/ManagedProxyOwnableInternal.sol deleted file mode 100644 index 1d657ba06..000000000 --- a/contracts/proxy/managed/ManagedProxyOwnableInternal.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import { OwnableInternal } from '../../access/ownable/OwnableInternal.sol'; -import { IManagedProxyOwnableInternal } from './IManagedProxyOwnableInternal.sol'; -import { ManagedProxyInternal } from './ManagedProxyInternal.sol'; - -abstract contract ManagedProxyOwnableInternal is - IManagedProxyOwnableInternal, - ManagedProxyInternal, - OwnableInternal -{ - /** - * @inheritdoc ManagedProxyInternal - */ - 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 6827e60ea..000000000 --- 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/Beacon.behavior.ts b/spec/proxy/beacon/Beacon.behavior.ts new file mode 100644 index 000000000..21ebd3e1b --- /dev/null +++ b/spec/proxy/beacon/Beacon.behavior.ts @@ -0,0 +1,64 @@ +import { + describeBehaviorOfOwnable, + OwnableBehaviorArgs, +} from '../../access/ownable/Ownable.behavior'; +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { describeFilter } from '@solidstate/library'; +import { IBeacon } from '@solidstate/typechain-types'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +export interface BeaconBehaviorArgs extends OwnableBehaviorArgs {} + +export function describeBehaviorOfBeacon( + deploy: () => Promise, + args: BeaconBehaviorArgs, + skips?: string[], +) { + const describe = describeFilter(skips); + + describe('::Beacon', () => { + let owner: SignerWithAddress; + let nonOwner: SignerWithAddress; + let instance: IBeacon; + + beforeEach(async () => { + owner = await args.getOwner(); + nonOwner = await args.getNonOwner(); + instance = await deploy(); + }); + + describeBehaviorOfOwnable(deploy, args, skips); + + describe('#getImplementation()', () => { + it('returns implementation address', async () => { + expect(await instance.getImplementation.staticCall()).to.be + .properAddress; + }); + }); + + describe('#setImplementation(address)', () => { + it('updates implementation address', async () => { + expect(await instance.getImplementation.staticCall()).to.eq( + ethers.ZeroAddress, + ); + + const address = ethers.getAddress( + ethers.zeroPadValue(ethers.randomBytes(20), 20), + ); + + await instance.connect(owner).setImplementation(address); + + expect(await instance.getImplementation.staticCall()).to.eq(address); + }); + + describe('reverts if', () => { + it('sender is not owner', async () => { + await expect( + instance.connect(nonOwner).setImplementation(ethers.ZeroAddress), + ).to.be.revertedWithCustomError(instance, 'Ownable__NotOwner'); + }); + }); + }); + }); +} diff --git a/spec/proxy/beacon/BeaconProxy.behavior.ts b/spec/proxy/beacon/BeaconProxy.behavior.ts new file mode 100644 index 000000000..13e4d6e77 --- /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/diamond/DiamondBeacon.behavior.ts b/spec/proxy/beacon/diamond/DiamondBeacon.behavior.ts new file mode 100644 index 000000000..046410243 --- /dev/null +++ b/spec/proxy/beacon/diamond/DiamondBeacon.behavior.ts @@ -0,0 +1,72 @@ +import { + describeBehaviorOfOwnable, + OwnableBehaviorArgs, +} from '../../../access/ownable/Ownable.behavior'; +import { + describeBehaviorOfDiamondReadable, + describeBehaviorOfDiamondWritable, + DiamondReadableBehaviorArgs, + DiamondWritableBehaviorArgs, +} from '../../diamond'; +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { describeFilter } from '@solidstate/library'; +import { IDiamondBeacon } from '@solidstate/typechain-types'; +import { expect } from 'chai'; +import { ethers } from 'ethers'; + +export interface DiamondBeaconBehaviorArgs + extends OwnableBehaviorArgs, + DiamondReadableBehaviorArgs, + DiamondWritableBehaviorArgs {} + +export function describeBehaviorOfDiamondBeacon( + deploy: () => Promise, + args: DiamondBeaconBehaviorArgs, + skips?: string[], +) { + const describe = describeFilter(skips); + + describe('::DiamondBeacon', () => { + let instance: IDiamondBeacon; + let owner: SignerWithAddress; + + beforeEach(async () => { + instance = await deploy(); + owner = await args.getOwner(); + }); + + describeBehaviorOfOwnable(deploy, args, skips); + + describeBehaviorOfDiamondReadable(deploy, args, [ + '::ERC165Base', + ...(skips ?? []), + ]); + + // TODO: can't use DiamondWritable spec because it's incorrectly designed to rely on DiamondBase + // describeBehaviorOfDiamondWritable(deploy, args, skips); + + describe('#diamondCut((address,enum,bytes4[])[],address,bytes)', () => { + describe('reverts if', () => { + it('target is not zero address', async () => { + await expect( + instance + .connect(owner) + .diamondCut([], await instance.getAddress(), '0x'), + ).to.be.revertedWithCustomError( + instance, + 'DiamondBeacon__InvalidInput', + ); + }); + + it('data does not have zero length', async () => { + await expect( + instance.connect(owner).diamondCut([], ethers.ZeroAddress, '0x01'), + ).to.be.revertedWithCustomError( + instance, + 'DiamondBeacon__InvalidInput', + ); + }); + }); + }); + }); +} diff --git a/spec/proxy/beacon/diamond/DiamondBeaconProxy.behavior.ts b/spec/proxy/beacon/diamond/DiamondBeaconProxy.behavior.ts new file mode 100644 index 000000000..0295c6590 --- /dev/null +++ b/spec/proxy/beacon/diamond/DiamondBeaconProxy.behavior.ts @@ -0,0 +1,21 @@ +import { + describeBehaviorOfBeaconProxy, + BeaconProxyBehaviorArgs, +} from '../BeaconProxy.behavior'; +import { describeFilter } from '@solidstate/library'; +import { IDiamondBeaconProxy } from '@solidstate/typechain-types'; + +export interface DiamondBeaconProxyBehaviorArgs + extends BeaconProxyBehaviorArgs {} + +export function describeBehaviorOfDiamondBeaconProxy( + deploy: () => Promise, + args: DiamondBeaconProxyBehaviorArgs, + skips?: string[], +) { + const describe = describeFilter(skips); + + describe('::DiamondBeaconProxy', () => { + describeBehaviorOfBeaconProxy(deploy, args, skips); + }); +} diff --git a/spec/proxy/beacon/diamond/index.ts b/spec/proxy/beacon/diamond/index.ts new file mode 100644 index 000000000..e95647867 --- /dev/null +++ b/spec/proxy/beacon/diamond/index.ts @@ -0,0 +1,2 @@ +export * from './DiamondBeacon.behavior'; +export * from './DiamondBeaconProxy.behavior'; diff --git a/spec/proxy/beacon/index.ts b/spec/proxy/beacon/index.ts new file mode 100644 index 000000000..c6f5261c3 --- /dev/null +++ b/spec/proxy/beacon/index.ts @@ -0,0 +1,3 @@ +export * from './diamond'; +export * from './Beacon.behavior'; +export * from './BeaconProxy.behavior'; diff --git a/spec/proxy/index.ts b/spec/proxy/index.ts index 22e6a8b95..5d8128ab0 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 a0c549dc2..000000000 --- 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 592eb7998..000000000 --- a/spec/proxy/managed/ManagedProxyOwnable.behavior.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - ManagedProxyBehaviorArgs, - describeBehaviorOfManagedProxy, -} 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 c7d0c5349..000000000 --- a/spec/proxy/managed/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './ManagedProxy.behavior'; -export * from './ManagedProxyOwnable.behavior'; diff --git a/test/inheritance.ts b/test/inheritance.ts index cd475c378..592afdb92 100644 --- a/test/inheritance.ts +++ b/test/inheritance.ts @@ -381,13 +381,13 @@ describe('Inheritance Graph', () => { const internalContractName = `${name}Internal`; const externalInterfaceName = `I${name}`; - expect(ancestors[name].indexOf(internalContractName)).to.eq( - ancestors[name].length - 2, - `First inherited ancestor for ${name} should be ${internalContractName}`, - ); expect(ancestors[name].indexOf(externalInterfaceName)).to.eq( ancestors[name].length - 1, - `Second inherited ancestor for ${name} should be ${externalInterfaceName}`, + `First inherited ancestor for ${name} should be ${externalInterfaceName}`, + ); + expect(ancestors[name].indexOf(internalContractName)).to.eq( + ancestors[name].length - 2, + `Second inherited ancestor for ${name} should be ${internalContractName}`, ); } }); diff --git a/test/proxy/beacon/Beacon.ts b/test/proxy/beacon/Beacon.ts new file mode 100644 index 000000000..80dff3390 --- /dev/null +++ b/test/proxy/beacon/Beacon.ts @@ -0,0 +1,28 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { describeBehaviorOfBeacon } from '@solidstate/spec'; +import { BeaconMock, BeaconMock__factory } from '@solidstate/typechain-types'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +describe('Beacon', () => { + let owner: SignerWithAddress; + let nonOwner: SignerWithAddress; + let instance: BeaconMock; + + before(async () => { + [owner, nonOwner] = await ethers.getSigners(); + }); + + beforeEach(async () => { + const [deployer] = await ethers.getSigners(); + + instance = await new BeaconMock__factory(deployer).deploy( + await owner.getAddress(), + ); + }); + + describeBehaviorOfBeacon(async () => instance, { + getOwner: async () => owner, + getNonOwner: async () => nonOwner, + }); +}); diff --git a/test/proxy/beacon/BeaconProxy.ts b/test/proxy/beacon/BeaconProxy.ts new file mode 100644 index 000000000..4c1b43d20 --- /dev/null +++ b/test/proxy/beacon/BeaconProxy.ts @@ -0,0 +1,59 @@ +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 implementation: any; + let instance: BeaconProxyMock; + + beforeEach(async () => { + const [deployer] = await ethers.getSigners(); + + implementation = 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 implementation.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.eq( + await implementation.getAddress(), + ); + }); + + 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/beacon/diamond/DiamondBeacon.ts b/test/proxy/beacon/diamond/DiamondBeacon.ts new file mode 100644 index 000000000..3886c0259 --- /dev/null +++ b/test/proxy/beacon/diamond/DiamondBeacon.ts @@ -0,0 +1,61 @@ +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { deployMockContract } from '@solidstate/library'; +import { describeBehaviorOfDiamondBeacon } from '@solidstate/spec'; +import { + DiamondBeaconMock, + DiamondBeaconMock__factory, +} from '@solidstate/typechain-types'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +describe('DiamondBeacon', () => { + let owner: SignerWithAddress; + let nonOwner: SignerWithAddress; + let instance: DiamondBeaconMock; + const facetCuts: any[] = []; + + before(async () => { + [owner, nonOwner] = await ethers.getSigners(); + + const functions = []; + const selectors = []; + + for (let i = 0; i < 24; i++) { + const fn = `fn${i}()`; + functions.push(fn); + selectors.push( + ethers.dataSlice( + ethers.solidityPackedKeccak256(['string'], [fn]), + 0, + 4, + ), + ); + } + + const abi = functions.map((fn) => `function ${fn}`); + + const facet = await deployMockContract(owner, abi); + + facetCuts.push({ + target: facet.address, + action: 0, + selectors, + }); + }); + + beforeEach(async () => { + const [deployer] = await ethers.getSigners(); + + instance = await new DiamondBeaconMock__factory(deployer).deploy( + await owner.getAddress(), + facetCuts, + ); + }); + + describeBehaviorOfDiamondBeacon(async () => instance, { + getOwner: async () => owner, + getNonOwner: async () => nonOwner, + facetCuts, + immutableSelectors: [], + }); +}); diff --git a/test/proxy/beacon/diamond/DiamondBeaconProxy.ts b/test/proxy/beacon/diamond/DiamondBeaconProxy.ts new file mode 100644 index 000000000..5e98e8e11 --- /dev/null +++ b/test/proxy/beacon/diamond/DiamondBeaconProxy.ts @@ -0,0 +1,79 @@ +import { deployMockContract } from '@solidstate/library'; +import { describeBehaviorOfDiamondBeaconProxy } from '@solidstate/spec'; +import { + DiamondBeaconProxyMock, + DiamondBeaconProxyMock__factory, + OwnableMock__factory, +} from '@solidstate/typechain-types'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; + +describe('DiamondBeaconProxy', () => { + let beacon: any; + let implementation: any; + let instance: DiamondBeaconProxyMock; + + beforeEach(async () => { + const [deployer] = await ethers.getSigners(); + + implementation = await new OwnableMock__factory(deployer).deploy( + ethers.ZeroAddress, + ); + + beacon = await deployMockContract((await ethers.getSigners())[0], [ + 'function facetAddress (bytes4) external view returns (address)', + ]); + + await beacon.mock.facetAddress.returns(await implementation.getAddress()); + + instance = await new DiamondBeaconProxyMock__factory(deployer).deploy( + beacon.address, + ); + }); + + describeBehaviorOfDiamondBeaconProxy(async () => instance, { + implementationFunction: 'owner()', + implementationFunctionArgs: [], + }); + + describe('__internal', () => { + describe('#_getImplementation()', () => { + it('returns implementation address', async () => { + expect(await instance['__getImplementation()'].staticCall()).to.eq( + await implementation.getAddress(), + ); + }); + + describe('reverts if', () => { + it('beacon is non-contract address', async () => { + await instance.setBeacon(ethers.ZeroAddress); + + await expect(instance['__getImplementation()'].staticCall()).to.be + .reverted; + }); + }); + }); + + describe('#_getImplementation(bytes4)', () => { + it('returns implementation address', async () => { + expect( + await instance['__getImplementation(bytes4)'].staticCall( + ethers.randomBytes(4), + ), + ).to.eq(await implementation.getAddress()); + }); + + describe('reverts if', () => { + it('beacon is non-contract address', async () => { + await instance.setBeacon(ethers.ZeroAddress); + + await expect( + instance['__getImplementation(bytes4)'].staticCall( + ethers.randomBytes(4), + ), + ).to.be.reverted; + }); + }); + }); + }); +}); diff --git a/test/proxy/managed/ManagedProxy.ts b/test/proxy/managed/ManagedProxy.ts deleted file mode 100644 index 60f5dde4c..000000000 --- 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 a51d8f6d1..000000000 --- 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(), - ); - }); - }); - }); -});