From e0c79220aa02b19c631fa441b8930841fa0587b8 Mon Sep 17 00:00:00 2001 From: Callum Grindle Date: Thu, 3 Aug 2023 12:00:25 +0200 Subject: [PATCH 1/8] Add virtual (#644) * chore: add missing virtual keyword in LSP6 * chore: add missing virtual keyword in LSP7 * chore: add missing virtual keyword in LSP8 * chore: add missing virtual keyword in LSP9 * chore: add missing virtual keyword in LSP20 * chore: add missing virtual keyword in LSP0 * refactor: add virtual to target() in LSP6 core * chore: rename _revert in LSP20CallVerification * refactor: add virtual to all LSP6 _isAllowed checks --- .../LSP0ERC725AccountCore.sol | 2 +- .../LSP20CallVerification.sol | 9 ++-- .../LSP6KeyManager/LSP6KeyManagerCore.sol | 8 ++-- .../LSP6Modules/LSP6ExecuteModule.sol | 8 ++-- .../LSP6Modules/LSP6SetDataModule.sol | 2 +- .../extensions/LSP7Burnable.sol | 6 ++- .../extensions/LSP7BurnableInitAbstract.sol | 6 ++- .../presets/LSP7CompatibleERC20Mintable.sol | 2 +- ...SP7CompatibleERC20MintableInitAbstract.sol | 2 +- .../extensions/LSP8Burnable.sol | 2 +- .../extensions/LSP8CompatibleERC721.sol | 46 +++++++++---------- .../LSP8CompatibleERC721InitAbstract.sol | 46 +++++++++---------- .../presets/LSP8CompatibleERC721Mintable.sol | 2 +- ...P8CompatibleERC721MintableInitAbstract.sol | 2 +- contracts/LSP9Vault/LSP9VaultCore.sol | 9 +++- 15 files changed, 84 insertions(+), 68 deletions(-) diff --git a/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol b/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol index 925b2668b..c18feddfd 100644 --- a/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol +++ b/contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol @@ -170,7 +170,7 @@ abstract contract LSP0ERC725AccountCore is */ function batchCalls( bytes[] calldata data - ) public returns (bytes[] memory results) { + ) public virtual returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i; i < data.length; ) { (bool success, bytes memory result) = address(this).delegatecall( diff --git a/contracts/LSP20CallVerification/LSP20CallVerification.sol b/contracts/LSP20CallVerification/LSP20CallVerification.sol index 8421f9ab9..f43efcd45 100644 --- a/contracts/LSP20CallVerification/LSP20CallVerification.sol +++ b/contracts/LSP20CallVerification/LSP20CallVerification.sol @@ -70,8 +70,8 @@ abstract contract LSP20CallVerification { bool postCall, bool success, bytes memory returnedData - ) internal pure { - if (!success) _revert(postCall, returnedData); + ) internal pure virtual { + if (!success) _revertWithLSP20DefaultError(postCall, returnedData); // check if the returned data contains at least 32 bytes, potentially an abi encoded bytes4 value // check if the returned data has in the first 32 bytes an abi encoded bytes4 value @@ -81,7 +81,10 @@ abstract contract LSP20CallVerification { ) revert LSP20InvalidMagicValue(postCall, returnedData); } - function _revert(bool postCall, bytes memory returnedData) internal pure { + function _revertWithLSP20DefaultError( + bool postCall, + bytes memory returnedData + ) internal pure virtual { // Look for revert reason and bubble it up if present if (returnedData.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index 3a8254956..a90240023 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -82,7 +82,7 @@ abstract contract LSP6KeyManagerCore is mapping(address => mapping(uint256 => uint256)) internal _nonceStore; - function target() public view returns (address) { + function target() public view virtual returns (address) { return _target; } @@ -116,7 +116,7 @@ abstract contract LSP6KeyManagerCore is function isValidSignature( bytes32 dataHash, bytes memory signature - ) public view returns (bytes4 magicValue) { + ) public view virtual returns (bytes4 magicValue) { // if isValidSignature fail, the error is catched in returnedError (address recoveredAddress, ECDSA.RecoverError returnedError) = ECDSA .tryRecover(dataHash, signature); @@ -250,7 +250,7 @@ abstract contract LSP6KeyManagerCore is address caller, uint256 msgValue, bytes calldata data - ) external returns (bytes4) { + ) external virtual returns (bytes4) { bool isSetData = false; if ( bytes4(data) == IERC725Y.setData.selector || @@ -301,7 +301,7 @@ abstract contract LSP6KeyManagerCore is function lsp20VerifyCallResult( bytes32 /*callHash*/, bytes memory /*result*/ - ) external returns (bytes4) { + ) external virtual returns (bytes4) { // If it's the target calling, set back the reentrancy guard // to false, if not return the magic value if (msg.sender == _target) { diff --git a/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol b/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol index 71c23d935..727c6f9e1 100644 --- a/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol +++ b/contracts/LSP6KeyManager/LSP6Modules/LSP6ExecuteModule.sol @@ -364,7 +364,7 @@ abstract contract LSP6ExecuteModule { function _isAllowedAddress( bytes memory allowedCall, address to - ) internal pure returns (bool) { + ) internal pure virtual returns (bool) { // = 4 bytes x 8 bits = 32 bits // // v----------------address---------------v @@ -380,7 +380,7 @@ abstract contract LSP6ExecuteModule { function _isAllowedStandard( bytes memory allowedCall, address to - ) internal view returns (bool) { + ) internal view virtual returns (bool) { // = 24 bytes x 8 bits = 192 bits // // standard @@ -397,7 +397,7 @@ abstract contract LSP6ExecuteModule { function _isAllowedFunction( bytes memory allowedCall, bytes4 requiredFunction - ) internal pure returns (bool) { + ) internal pure virtual returns (bool) { // = 28 bytes x 8 bits = 224 bits // // function @@ -416,7 +416,7 @@ abstract contract LSP6ExecuteModule { function _isAllowedCallType( bytes memory allowedCall, bytes4 requiredCallTypes - ) internal pure returns (bool) { + ) internal pure virtual returns (bool) { // extract callType // // = 0 diff --git a/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol b/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol index 6fc98b56b..cb6d4d546 100644 --- a/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol +++ b/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol @@ -438,7 +438,7 @@ abstract contract LSP6SetDataModule { bytes32 dataKey, bytes memory dataValue, bool hasBothAddControllerAndEditPermissions - ) internal view returns (bytes32) { + ) internal view virtual returns (bytes32) { if (!LSP6Utils.isCompactBytesArrayOfAllowedERC725YDataKeys(dataValue)) { revert InvalidEncodedAllowedERC725YDataKeys( dataValue, diff --git a/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol b/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol index a180c310f..99ef9d0e1 100644 --- a/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol +++ b/contracts/LSP7DigitalAsset/extensions/LSP7Burnable.sol @@ -14,7 +14,11 @@ abstract contract LSP7Burnable is LSP7DigitalAsset { * * See internal _burn function for more details */ - function burn(address from, uint256 amount, bytes memory data) public { + function burn( + address from, + uint256 amount, + bytes memory data + ) public virtual { _burn(from, amount, data); } } diff --git a/contracts/LSP7DigitalAsset/extensions/LSP7BurnableInitAbstract.sol b/contracts/LSP7DigitalAsset/extensions/LSP7BurnableInitAbstract.sol index f8fbb6fec..76d58d00e 100644 --- a/contracts/LSP7DigitalAsset/extensions/LSP7BurnableInitAbstract.sol +++ b/contracts/LSP7DigitalAsset/extensions/LSP7BurnableInitAbstract.sol @@ -16,7 +16,11 @@ abstract contract LSP7BurnableInitAbstract is LSP7DigitalAssetInitAbstract { * * See internal _burn function for more details */ - function burn(address from, uint256 amount, bytes memory data) public { + function burn( + address from, + uint256 amount, + bytes memory data + ) public virtual { _burn(from, amount, data); } } diff --git a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol index d1c26418f..270c9eb28 100644 --- a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol +++ b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20Mintable.sol @@ -15,7 +15,7 @@ contract LSP7CompatibleERC20Mintable is LSP7CompatibleERC20 { uint256 amount, bool allowNonLSP1Recipient, bytes memory data - ) public onlyOwner { + ) public virtual onlyOwner { _mint(to, amount, allowNonLSP1Recipient, data); } } diff --git a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInitAbstract.sol b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInitAbstract.sol index cc7fa025c..112a7d2ca 100644 --- a/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInitAbstract.sol +++ b/contracts/LSP7DigitalAsset/presets/LSP7CompatibleERC20MintableInitAbstract.sol @@ -25,7 +25,7 @@ contract LSP7CompatibleERC20MintableInitAbstract is uint256 amount, bool allowNonLSP1Recipient, bytes memory data - ) public onlyOwner { + ) public virtual onlyOwner { _mint(to, amount, allowNonLSP1Recipient, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol index e50d9e798..80236b971 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Burnable.sol @@ -13,7 +13,7 @@ import {LSP8NotTokenOperator} from "../LSP8Errors.sol"; * their own tokens and those that they have an allowance for as an operator. */ abstract contract LSP8Burnable is LSP8IdentifiableDigitalAssetCore { - function burn(bytes32 tokenId, bytes memory data) public { + function burn(bytes32 tokenId, bytes memory data) public virtual { if (!_isOperatorOrOwner(msg.sender, tokenId)) { revert LSP8NotTokenOperator(tokenId, msg.sender); } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol index 4a7ceaec3..c950f7e27 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721.sol @@ -310,31 +310,31 @@ abstract contract LSP8CompatibleERC721 is uint256 tokenId, bytes memory data ) private returns (bool) { - if (to.code.length > 0) { - try - IERC721Receiver(to).onERC721Received( - msg.sender, - from, - tokenId, - data - ) - returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert( - "LSP8CompatibleERC721: transfer to non ERC721Receiver implementer" - ); - } else { - // solhint-disable no-inline-assembly - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } + if (to.code.length == 0) { + return true; + } + + try + IERC721Receiver(to).onERC721Received( + msg.sender, + from, + tokenId, + data + ) + returns (bytes4 retval) { + return retval == IERC721Receiver.onERC721Received.selector; + } catch (bytes memory reason) { + if (reason.length == 0) { + revert( + "LSP8CompatibleERC721: transfer to non ERC721Receiver implementer" + ); + } else { + // solhint-disable no-inline-assembly + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) } } - } else { - return true; } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol index 456c035db..ae3f45a4a 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8CompatibleERC721InitAbstract.sol @@ -310,31 +310,31 @@ abstract contract LSP8CompatibleERC721InitAbstract is uint256 tokenId, bytes memory data ) private returns (bool) { - if (to.code.length > 0) { - try - IERC721Receiver(to).onERC721Received( - msg.sender, - from, - tokenId, - data - ) - returns (bytes4 retval) { - return retval == IERC721Receiver.onERC721Received.selector; - } catch (bytes memory reason) { - if (reason.length == 0) { - revert( - "LSP8CompatibleERC721: transfer to non ERC721Receiver implementer" - ); - } else { - // solhint-disable no-inline-assembly - /// @solidity memory-safe-assembly - assembly { - revert(add(32, reason), mload(reason)) - } + if (to.code.length == 0) { + return true; + } + + try + IERC721Receiver(to).onERC721Received( + msg.sender, + from, + tokenId, + data + ) + returns (bytes4 retval) { + return retval == IERC721Receiver.onERC721Received.selector; + } catch (bytes memory reason) { + if (reason.length == 0) { + revert( + "LSP8CompatibleERC721: transfer to non ERC721Receiver implementer" + ); + } else { + // solhint-disable no-inline-assembly + /// @solidity memory-safe-assembly + assembly { + revert(add(32, reason), mload(reason)) } } - } else { - return true; } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol index 571f9e3a2..a853428ea 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol @@ -15,7 +15,7 @@ contract LSP8CompatibleERC721Mintable is LSP8CompatibleERC721 { bytes32 tokenId, bool allowNonLSP1Recipient, bytes memory data - ) public onlyOwner { + ) public virtual onlyOwner { _mint(to, tokenId, allowNonLSP1Recipient, data); } } diff --git a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInitAbstract.sol b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInitAbstract.sol index f857fd0b4..8d631d582 100644 --- a/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInitAbstract.sol +++ b/contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721MintableInitAbstract.sol @@ -25,7 +25,7 @@ contract LSP8CompatibleERC721MintableInitAbstract is bytes32 tokenId, bool allowNonLSP1Recipient, bytes memory data - ) public onlyOwner { + ) public virtual onlyOwner { _mint(to, tokenId, allowNonLSP1Recipient, data); } } diff --git a/contracts/LSP9Vault/LSP9VaultCore.sol b/contracts/LSP9Vault/LSP9VaultCore.sol index f07653bc1..3a573f38d 100644 --- a/contracts/LSP9Vault/LSP9VaultCore.sol +++ b/contracts/LSP9Vault/LSP9VaultCore.sol @@ -233,7 +233,7 @@ contract LSP9VaultCore is */ function batchCalls( bytes[] calldata data - ) public returns (bytes[] memory results) { + ) public virtual returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i; i < data.length; ) { (bool success, bytes memory result) = address(this).delegatecall( @@ -366,7 +366,12 @@ contract LSP9VaultCore is /** * @dev Modifier restricting the call to the owner of the contract and the UniversalReceiverDelegate */ - function _validateAndIdentifyCaller() internal view returns (bool isURD) { + function _validateAndIdentifyCaller() + internal + view + virtual + returns (bool isURD) + { if (msg.sender != owner()) { require( msg.sender == _reentrantDelegate, From 01fd82038811ba65798f3ff8fe8273fd191dcb38 Mon Sep 17 00:00:00 2001 From: Skima Harvey <64636974+skimaharvey@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:14:12 +0200 Subject: [PATCH 2/8] feat: create LSP23MultiChainDeployment (#649) * feat: create LSP23MultiChainDeployment * refactor: improve LSP23 readability (#650) * refactor: improve LSP23 readability * tests: create helper to calculate deployed addresses * tests: add test for lsp23 * refactor: add suggested changes & surther simplify * refactor: remove event split, restore the initial events. * refactor: fix generation of salt * refactor: make salt generation functions virtual * chore: fix typo * refactor: move `emit` before external call to `IPostDeploymentModule` * chore: add suggested changes * docs: improve Natspec as suggetsed * docs: fix Natspec typo * chore: remove unrelated file --------- Co-authored-by: maxvia87 --------- Co-authored-by: b00ste.lyx <62855857+b00ste@users.noreply.github.com> Co-authored-by: Jean Cvllr <31145285+CJ42@users.noreply.github.com> --- .../IOwnerControlledContractDeployer.sol | 174 ++++++++ .../IPostDeploymentModule.sol | 10 + .../LSP23MultiChainDeployment/LSP23Errors.sol | 24 ++ .../OwnerControlledContractDeployer.sol | 388 ++++++++++++++++++ ...iversalProfileInitPostDeploymentModule.sol | 59 +++ .../LSP23MultiChainDeployment.behaviour.ts | 151 +++++++ tests/LSP23MultiChainDeployment/helpers.ts | 50 +++ 7 files changed, 856 insertions(+) create mode 100644 contracts/LSP23MultiChainDeployment/IOwnerControlledContractDeployer.sol create mode 100644 contracts/LSP23MultiChainDeployment/IPostDeploymentModule.sol create mode 100644 contracts/LSP23MultiChainDeployment/LSP23Errors.sol create mode 100644 contracts/LSP23MultiChainDeployment/OwnerControlledContractDeployer.sol create mode 100644 contracts/LSP23MultiChainDeployment/modules/UniversalProfileInitPostDeploymentModule.sol create mode 100644 tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.behaviour.ts create mode 100644 tests/LSP23MultiChainDeployment/helpers.ts diff --git a/contracts/LSP23MultiChainDeployment/IOwnerControlledContractDeployer.sol b/contracts/LSP23MultiChainDeployment/IOwnerControlledContractDeployer.sol new file mode 100644 index 000000000..54b00cc6e --- /dev/null +++ b/contracts/LSP23MultiChainDeployment/IOwnerControlledContractDeployer.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +interface IOwnerControlledContractDeployer { + event DeployedContracts( + address indexed controlledContract, + address indexed ownerContract, + ControlledContractDeployment controlledContractDeployment, + OwnerContractDeployment ownerContractDeployment, + address postDeploymentModule, + bytes postDeploymentModuleCalldata + ); + + event DeployedERC1167Proxies( + address indexed controlledContract, + address indexed ownerContract, + ControlledContractDeploymentInit controlledContractDeploymentInit, + OwnerContractDeploymentInit ownerContractDeploymentInit, + address postDeploymentModule, + bytes postDeploymentModuleCalldata + ); + + /** + * @param salt A unique value used to ensure each created proxies are unique. (Can be used to deploy the contract at a desired address.) + * @param fundingAmount The value to be sent with the deployment transaction. + * @param creationBytecode The bytecode of the contract with the constructor params. + */ + struct ControlledContractDeployment { + bytes32 salt; + uint256 fundingAmount; + bytes creationBytecode; + } + + /** + * @param fundingAmount The value to be sent with the deployment transaction. + * @param creationBytecode The constructor + runtime bytecode (without the controlled contract's address as param) + * @param addControlledContractAddress If set to `true`, this will append the controlled contract's address + the `extraConstructorParams` to the `creationBytecode`. + * @param extraConstructorParams Params to be appended to the `creationBytecode` (after the controlled contract address) if `addControlledContractAddress` is set to `true`. + */ + struct OwnerContractDeployment { + uint256 fundingAmount; + bytes creationBytecode; + bool addControlledContractAddress; + bytes extraConstructorParams; + } + + /** + * @param salt A unique value used to ensure each created proxies are unique. (Can be used to deploy the contract at a desired address.) + * @param fundingAmount The value to be sent with the deployment transaction. + * @param implementationContract The address of the contract that will be used as a base contract for the proxy. + * @param initializationCalldata The calldata used to initialise the contract. (initialization should be similar to a constructor in a normal contract.) + */ + struct ControlledContractDeploymentInit { + bytes32 salt; + uint256 fundingAmount; + address implementationContract; + bytes initializationCalldata; + } + + /** + * @param fundingAmount The value to be sent with the deployment transaction. + * @param implementationContract The address of the contract that will be used as a base contract for the proxy. + * @param initializationCalldata The first part of the initialisation calldata, everything before the controlled contract address. + * @param addControlledContractAddress If set to `true`, this will append the controlled contract's address + the `extraInitializationParams` to the `initializationCalldata`. + * @param extraInitializationParams Params to be appended to the `initializationCalldata` (after the controlled contract address) if `addControlledContractAddress` is set to `true` + */ + struct OwnerContractDeploymentInit { + uint256 fundingAmount; + address implementationContract; + bytes initializationCalldata; + bool addControlledContractAddress; + bytes extraInitializationParams; + } + + /** + * @dev Deploys a contract and its owner contract. + * @notice Contracts deployed. Contract Address: `controlledContractAddress`. Owner Contract Address: `ownerContractAddress` + * + * @param controlledContractDeployment Contains the needed parameter to deploy a contract. (`salt`, `fundingAmount`, `creationBytecode`) + * @param ownerContractDeployment Contains the needed parameter to deploy the owner contract. (`fundingAmount`, `creationBytecode`, `addControlledContractAddress`, `extraConstructorParams`) + * @param postDeploymentModule The module to be executed after deployment + * @param postDeploymentModuleCalldata The data to be passed to the post deployment module + * + * @return controlledContractAddress The address of the deployed controlled contract. + * @return ownerContractAddress The address of the deployed owner contract. + */ + function deployContracts( + ControlledContractDeployment calldata controlledContractDeployment, + OwnerContractDeployment calldata ownerContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + external + payable + returns ( + address controlledContractAddress, + address ownerContractAddress + ); + + /** + * @dev Deploys proxies of a contract and its owner contract + * @notice Contract proxies deployed. Contract Proxy Address: `controlledContractAddress`. Owner Contract Proxy Address: `ownerContractAddress` + * + * @param controlledContractDeploymentInit Contains the needed parameter to deploy a proxy contract. (`salt`, `fundingAmount`, `implementationContract`, `initializationCalldata`) + * @param ownerContractDeploymentInit Contains the needed parameter to deploy the owner proxy contract. (`fundingAmount`, `implementationContract`, `addControlledContractAddress`, `initializationCalldata`, `extraInitializationParams`) + * @param postDeploymentModule The module to be executed after deployment. + * @param postDeploymentModuleCalldata The data to be passed to the post deployment module. + * + * @return controlledContractAddress The address of the deployed controlled contract proxy + * @return ownerContractAddress The address of the deployed owner contract proxy + */ + function deployERC1167Proxies( + ControlledContractDeploymentInit + calldata controlledContractDeploymentInit, + OwnerContractDeploymentInit calldata ownerContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + external + payable + returns ( + address controlledContractAddress, + address ownerContractAddress + ); + + /** + * @dev Computes the addresses of the controlled and owner contracts to be created + * + * @param controlledContractDeployment Contains the needed parameter to deploy a contract. (`salt`, `fundingAmount`, `creationBytecode`) + * @param ownerContractDeployment Contains the needed parameter to deploy the owner contract. (`fundingAmount`, `creationBytecode`, `addControlledContractAddress`, `extraConstructorParams`) + * @param postDeploymentModule The module to be executed after deployment + * @param postDeploymentModuleCalldata The data to be passed to the post deployment module + * + * @return controlledContractAddress The address of the deployed controlled contract. + * @return ownerContractAddress The address of the deployed owner contract. + */ + function computeAddresses( + ControlledContractDeployment calldata controlledContractDeployment, + OwnerContractDeployment calldata ownerContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + external + view + returns ( + address controlledContractAddress, + address ownerContractAddress + ); + + /** + * @dev Computes the addresses of the controlled and owner contract proxies to be created. + * + * @param controlledContractDeploymentInit Contains the needed parameter to deploy a proxy contract. (`salt`, `fundingAmount`, `implementationContract`, `initializationCalldata`) + * @param ownerContractDeploymentInit Contains the needed parameter to deploy the owner proxy contract. (`fundingAmount`, `implementationContract`, `addControlledContractAddress`, `initializationCalldata`, `extraInitializationParams`) + * @param postDeploymentModule The module to be executed after deployment. + * @param postDeploymentModuleCalldata The data to be passed to the post deployment module. + * + * @return controlledContractAddress The address of the deployed controlled contract proxy + * @return ownerContractAddress The address of the deployed owner contract proxy + */ + function computeERC1167Addresses( + ControlledContractDeploymentInit + calldata controlledContractDeploymentInit, + OwnerContractDeploymentInit calldata ownerContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + external + view + returns ( + address controlledContractAddress, + address ownerContractAddress + ); +} diff --git a/contracts/LSP23MultiChainDeployment/IPostDeploymentModule.sol b/contracts/LSP23MultiChainDeployment/IPostDeploymentModule.sol new file mode 100644 index 000000000..083051c57 --- /dev/null +++ b/contracts/LSP23MultiChainDeployment/IPostDeploymentModule.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +interface IPostDeploymentModule { + function executePostDeployment( + address ownerControlledContract, + address ownerContract, + bytes calldata calldataToPostDeploymentModule + ) external; +} diff --git a/contracts/LSP23MultiChainDeployment/LSP23Errors.sol b/contracts/LSP23MultiChainDeployment/LSP23Errors.sol new file mode 100644 index 000000000..90eb8a440 --- /dev/null +++ b/contracts/LSP23MultiChainDeployment/LSP23Errors.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/** + * @dev Reverts when the `msg.value` sent is not equal to the sum of value used for the deployment of the contract & its owner contract. + * @notice Invalid value sent. + */ +error InvalidValueSum(); + +/** + * @dev Reverts when the deployment & intialisation of the contract has failed. + * @notice Failed to deploy & initialise the Controlled Contract Proxy. Error: `errorData`. + * + * @param errorData Potentially information about why the deployment & intialisation have failed. + */ +error ControlledContractProxyInitFailureError(bytes errorData); + +/** + * @dev Reverts when the deployment & intialisation of the owner contract has failed. + * @notice Failed to deploy & initialise the Owner Contract Proxy. Error: `errorData`. + * + * @param errorData Potentially information about why the deployment & intialisation have failed. + */ +error OwnerContractProxyInitFailureError(bytes errorData); diff --git a/contracts/LSP23MultiChainDeployment/OwnerControlledContractDeployer.sol b/contracts/LSP23MultiChainDeployment/OwnerControlledContractDeployer.sol new file mode 100644 index 000000000..85955a495 --- /dev/null +++ b/contracts/LSP23MultiChainDeployment/OwnerControlledContractDeployer.sol @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {IPostDeploymentModule} from "./IPostDeploymentModule.sol"; +import { + IOwnerControlledContractDeployer +} from "./IOwnerControlledContractDeployer.sol"; +import { + InvalidValueSum, + ControlledContractProxyInitFailureError, + OwnerContractProxyInitFailureError +} from "./LSP23Errors.sol"; + +contract OwnerControlledContractDeployer is IOwnerControlledContractDeployer { + /** + * @inheritdoc IOwnerControlledContractDeployer + */ + function deployContracts( + ControlledContractDeployment calldata controlledContractDeployment, + OwnerContractDeployment calldata ownerContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + public + payable + returns ( + address controlledContractAddress, + address ownerContractAddress + ) + { + /* check that the msg.value is equal to the sum of the values of the controlled and owner contracts */ + if ( + msg.value != + controlledContractDeployment.fundingAmount + + ownerContractDeployment.fundingAmount + ) { + revert InvalidValueSum(); + } + + controlledContractAddress = _deployControlledContract( + controlledContractDeployment, + ownerContractDeployment, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + ownerContractAddress = _deployOwnerContract( + ownerContractDeployment, + controlledContractAddress + ); + + emit DeployedContracts( + controlledContractAddress, + ownerContractAddress, + controlledContractDeployment, + ownerContractDeployment, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* execute the post deployment module logic in the postDeploymentModule */ + IPostDeploymentModule(postDeploymentModule).executePostDeployment( + controlledContractAddress, + ownerContractAddress, + postDeploymentModuleCalldata + ); + } + + /** + * @inheritdoc IOwnerControlledContractDeployer + */ + function deployERC1167Proxies( + ControlledContractDeploymentInit + calldata controlledContractDeploymentInit, + OwnerContractDeploymentInit calldata ownerContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + public + payable + returns ( + address controlledContractAddress, + address ownerContractAddress + ) + { + /* check that the msg.value is equal to the sum of the values of the controlled and owner contracts */ + if ( + msg.value != + controlledContractDeploymentInit.fundingAmount + + ownerContractDeploymentInit.fundingAmount + ) { + revert InvalidValueSum(); + } + + /* deploy the controlled contract proxy with the controlledContractGeneratedSalt */ + controlledContractAddress = _deployAndInitializeControlledContractProxy( + controlledContractDeploymentInit, + ownerContractDeploymentInit, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* deploy the owner contract proxy */ + ownerContractAddress = _deployAndInitializeOwnerContractProxy( + ownerContractDeploymentInit, + controlledContractAddress + ); + + emit DeployedERC1167Proxies( + controlledContractAddress, + ownerContractAddress, + controlledContractDeploymentInit, + ownerContractDeploymentInit, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* execute the post deployment logic in the postDeploymentModule */ + IPostDeploymentModule(postDeploymentModule).executePostDeployment( + controlledContractAddress, + ownerContractAddress, + postDeploymentModuleCalldata + ); + } + + /** + * @inheritdoc IOwnerControlledContractDeployer + */ + function computeAddresses( + ControlledContractDeployment calldata controlledContractDeployment, + OwnerContractDeployment calldata ownerContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + public + view + returns ( + address controlledContractAddress, + address ownerContractAddress + ) + { + bytes32 controlledContractGeneratedSalt = _generateControlledContractSalt( + controlledContractDeployment, + ownerContractDeployment, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + controlledContractAddress = Create2.computeAddress( + controlledContractGeneratedSalt, + keccak256(controlledContractDeployment.creationBytecode) + ); + + bytes memory ownerContractByteCodeWithAllParams; + if (ownerContractDeployment.addControlledContractAddress) { + ownerContractByteCodeWithAllParams = abi.encodePacked( + ownerContractDeployment.creationBytecode, + abi.encode(controlledContractAddress), + ownerContractDeployment.extraConstructorParams + ); + } else { + ownerContractByteCodeWithAllParams = ownerContractDeployment + .creationBytecode; + } + + ownerContractAddress = Create2.computeAddress( + keccak256(abi.encodePacked(controlledContractAddress)), + keccak256(ownerContractByteCodeWithAllParams) + ); + } + + /** + * @inheritdoc IOwnerControlledContractDeployer + */ + function computeERC1167Addresses( + ControlledContractDeploymentInit + calldata controlledContractDeploymentInit, + OwnerContractDeploymentInit calldata ownerContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + public + view + returns ( + address controlledContractAddress, + address ownerContractAddress + ) + { + bytes32 controlledContractGeneratedSalt = _generateControlledProxyContractSalt( + controlledContractDeploymentInit, + ownerContractDeploymentInit, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + controlledContractAddress = Clones.predictDeterministicAddress( + controlledContractDeploymentInit.implementationContract, + controlledContractGeneratedSalt + ); + + ownerContractAddress = Clones.predictDeterministicAddress( + ownerContractDeploymentInit.implementationContract, + keccak256(abi.encodePacked(controlledContractAddress)) + ); + } + + function _deployControlledContract( + ControlledContractDeployment calldata controlledContractDeployment, + OwnerContractDeployment calldata ownerContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) internal returns (address controlledContractAddress) { + bytes32 controlledContractGeneratedSalt = _generateControlledContractSalt( + controlledContractDeployment, + ownerContractDeployment, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* deploy the controlled contract */ + controlledContractAddress = Create2.deploy( + controlledContractDeployment.fundingAmount, + controlledContractGeneratedSalt, + controlledContractDeployment.creationBytecode + ); + } + + function _deployOwnerContract( + OwnerContractDeployment calldata ownerContractDeployment, + address controlledContractAddress + ) internal returns (address ownerContractAddress) { + /** + * If `addControlledContractAddress` is `true`, the following will be appended to the constructor params: + * - The controlled contract address + * - `extraConstructorParams` + */ + bytes memory ownerContractByteCode = ownerContractDeployment + .creationBytecode; + + if (ownerContractDeployment.addControlledContractAddress) { + ownerContractByteCode = abi.encodePacked( + ownerContractByteCode, + abi.encode(controlledContractAddress), + ownerContractDeployment.extraConstructorParams + ); + } + + /* Here owner refers to the future owner of the controlled contract at the end of the transaction */ + ownerContractAddress = Create2.deploy( + ownerContractDeployment.fundingAmount, + keccak256(abi.encodePacked(controlledContractAddress)), + ownerContractByteCode + ); + } + + function _deployAndInitializeControlledContractProxy( + ControlledContractDeploymentInit + calldata controlledContractDeploymentInit, + OwnerContractDeploymentInit calldata ownerContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) internal returns (address controlledContractAddress) { + bytes32 controlledContractGeneratedSalt = _generateControlledProxyContractSalt( + controlledContractDeploymentInit, + ownerContractDeploymentInit, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* deploy the controlled contract proxy with the controlledContractGeneratedSalt */ + controlledContractAddress = Clones.cloneDeterministic( + controlledContractDeploymentInit.implementationContract, + controlledContractGeneratedSalt + ); + + /* initialize the controlled contract proxy */ + (bool success, bytes memory returnedData) = controlledContractAddress + .call{value: msg.value}( + controlledContractDeploymentInit.initializationCalldata + ); + if (!success) { + revert ControlledContractProxyInitFailureError(returnedData); + } + } + + function _deployAndInitializeOwnerContractProxy( + OwnerContractDeploymentInit calldata ownerContractDeploymentInit, + address controlledContractAddress + ) internal returns (address ownerContractAddress) { + /* deploy the controlled contract proxy with the controlledContractGeneratedSalt */ + ownerContractAddress = Clones.cloneDeterministic( + ownerContractDeploymentInit.implementationContract, + keccak256(abi.encodePacked(controlledContractAddress)) + ); + + /** + * If `addControlledContractAddress` is `true`, the following will be appended to the `initializationCalldata`: + * - The controlled contract address + * - `extraInitialisationBytes` + */ + bytes memory ownerInitializationBytes = ownerContractDeploymentInit + .initializationCalldata; + + if (ownerContractDeploymentInit.addControlledContractAddress) { + ownerInitializationBytes = abi.encodePacked( + ownerInitializationBytes, + abi.encode(controlledContractAddress), + ownerContractDeploymentInit.extraInitializationParams + ); + } + + /* initialize the controlled contract proxy */ + (bool success, bytes memory returnedData) = ownerContractAddress.call{ + value: msg.value + }(ownerInitializationBytes); + if (!success) { + revert OwnerContractProxyInitFailureError(returnedData); + } + } + + function _generateControlledContractSalt( + ControlledContractDeployment calldata controlledContractDeployment, + OwnerContractDeployment calldata ownerContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) internal pure virtual returns (bytes32 controlledContractGeneratedSalt) { + /* generate salt for the controlled contract + * the salt is generated by hashing the following elements: + * - the salt + * - the owner contract bytecode + * - the owner addControlledContractAddress boolean + * - the owner extraConstructorParams + * - the postDeploymentModule address + * - the postDeploymentModuleCalldata + * + */ + controlledContractGeneratedSalt = keccak256( + abi.encode( + controlledContractDeployment.salt, + controlledContractDeployment.creationBytecode, + ownerContractDeployment.creationBytecode, + ownerContractDeployment.addControlledContractAddress, + ownerContractDeployment.extraConstructorParams, + postDeploymentModule, + postDeploymentModuleCalldata + ) + ); + } + + function _generateControlledProxyContractSalt( + ControlledContractDeploymentInit + calldata controlledContractDeploymentInit, + OwnerContractDeploymentInit calldata ownerContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + internal + pure + virtual + returns (bytes32 controlledProxyContractGeneratedSalt) + { + /** + * Generate the salt for the controlled contract + * The salt is generated by hashing the following elements: + * - the salt + * - the owner implementation contract address + * - the owner contract addControlledContractAddress boolean + * - the owner contract initialization calldata + * - the owner contract extra initialization params (if any) + * - the postDeploymentModule address + * - the callda to the post deployment module + * + */ + controlledProxyContractGeneratedSalt = keccak256( + abi.encode( + controlledContractDeploymentInit.salt, + ownerContractDeploymentInit.implementationContract, + ownerContractDeploymentInit.initializationCalldata, + ownerContractDeploymentInit.addControlledContractAddress, + ownerContractDeploymentInit.extraInitializationParams, + postDeploymentModule, + postDeploymentModuleCalldata + ) + ); + } +} diff --git a/contracts/LSP23MultiChainDeployment/modules/UniversalProfileInitPostDeploymentModule.sol b/contracts/LSP23MultiChainDeployment/modules/UniversalProfileInitPostDeploymentModule.sol new file mode 100644 index 000000000..bb6d4408c --- /dev/null +++ b/contracts/LSP23MultiChainDeployment/modules/UniversalProfileInitPostDeploymentModule.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {UniversalProfileInit} from "../../UniversalProfileInit.sol"; + +contract UniversalProfileInitPostDeploymentModule is UniversalProfileInit { + constructor() { + _disableInitializers(); + } + + function setDataAndTransferOwnership( + bytes32[] memory dataKeys, + bytes[] memory dataValues, + address newOwner + ) public payable { + // check that the msg.sender is the owner + require( + msg.sender == owner(), + "UniversalProfileInitPostDeploymentModule: setDataAndTransferOwnership only allowed through delegate call" + ); + + // update the dataKeys and dataValues in the UniversalProfile contract + for (uint256 i = 0; i < dataKeys.length; ) { + _setData(dataKeys[i], dataValues[i]); + + unchecked { + ++i; + } + } + + // transfer the ownership of the UniversalProfile contract to the newOwner + _setOwner(newOwner); + } + + function executePostDeployment( + address universalProfile, + address keyManager, + bytes calldata setDataBatchBytes + ) public { + // retrieve the dataKeys and dataValues to setData from the initializationCalldata bytes + (bytes32[] memory dataKeys, bytes[] memory dataValues) = abi.decode( + setDataBatchBytes, + (bytes32[], bytes[]) + ); + + // call the execute function with delegate_call on the universalProfile contract to setData and transferOwnership + UniversalProfileInit(payable(universalProfile)).execute( + 4, + address(this), + 0, + abi.encodeWithSignature( + "setDataAndTransferOwnership(bytes32[],bytes[],address)", + dataKeys, + dataValues, + keyManager + ) + ); + } +} diff --git a/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.behaviour.ts b/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.behaviour.ts new file mode 100644 index 000000000..ae511e876 --- /dev/null +++ b/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.behaviour.ts @@ -0,0 +1,151 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { OwnerControlledContractDeployer } from '../../typechain-types'; +import { ERC725YDataKeys } from '../../constants.ts'; +import { calculateProxiesAddresses } from './helpers'; + +describe('UniversalProfileDeployer', function () { + it('should deploy proxies for Universal Profile and Key Manager', async function () { + const [allPermissionsSigner, universalReceiver, recoverySigner] = await ethers.getSigners(); + + const KeyManagerInitFactory = await ethers.getContractFactory('LSP6KeyManagerInit'); + const keyManagerInit = await KeyManagerInitFactory.deploy(); + + const UniversalProfileInitFactory = await ethers.getContractFactory('UniversalProfileInit'); + const universalProfileInit = await UniversalProfileInitFactory.deploy(); + + const OwnerControlledContractDeployerFactory = await ethers.getContractFactory( + 'OwnerControlledContractDeployer', + ); + + const ownerControlledContractDeployer = await OwnerControlledContractDeployerFactory.deploy(); + + const UPDelegatorPostDeploymentManagerFactory = await ethers.getContractFactory( + 'UniversalProfileInitPostDeploymentModule', + ); + + const upPostDeploymentModule = await UPDelegatorPostDeploymentManagerFactory.deploy(); + + const salt = ethers.utils.randomBytes(32); + + const ownerControlledDeploymentInit: OwnerControlledContractDeployer.ControlledContractDeploymentInitStruct = + { + salt, + value: 0, + implementationContract: universalProfileInit.address, + initializationCalldata: universalProfileInit.interface.encodeFunctionData('initialize', [ + upPostDeploymentModule.address, + ]), + }; + + const ownerDeploymentInit: OwnerControlledContractDeployer.OwnerContractDeploymentInitStruct = { + value: 0, + implementationContract: keyManagerInit.address, + addControlledContractAddress: true, + initializationCalldata: '0xc4d66de8', + extraInitializationParams: '0x', + }; + + const allPermissionsSignerPermissionsKey = + '0x4b80742de2bf82acb3630000' + allPermissionsSigner.address.slice(2); + + const universalReceiverPermissionsKey = + '0x4b80742de2bf82acb3630000' + universalReceiver.address.slice(2); + + const recoveryAddressPermissionsKey = + '0x4b80742de2bf82acb3630000' + recoverySigner.address.slice(2); + + const allPermissionsSignerPermissionsValue = + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; + + const create16BytesUint = (value: number) => { + return ethers.utils.hexZeroPad(ethers.utils.hexlify(value), 16).slice(2); + }; + + const types = ['bytes32[]', 'bytes[]']; + + const encodedBytes = ethers.utils.defaultAbiCoder.encode(types, [ + [ + ERC725YDataKeys.LSP3.LSP3Profile, // LSP3Metadata + ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate, // URD Address + universalReceiverPermissionsKey, // URD Permissions + recoveryAddressPermissionsKey, // Recovery Address permissions + allPermissionsSignerPermissionsKey, // Signers permissions + ERC725YDataKeys.LSP6['AddressPermissions[]'].length, // Number of address with permissions + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + create16BytesUint(0), // Index of the first address + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + create16BytesUint(1), // Index of the second address + ERC725YDataKeys.LSP6['AddressPermissions[]'].index + create16BytesUint(2), // Index of the third address + ], + [ + ethers.utils.randomBytes(32), // LSP3Metadata + universalReceiver.address, // URD Address + allPermissionsSignerPermissionsValue, // URD Permissions + allPermissionsSignerPermissionsValue, // Recovery Address permissions + allPermissionsSignerPermissionsValue, // Signers permissions + ethers.utils.defaultAbiCoder.encode(['uint256'], [3]), // Address Permissions array length + universalReceiver.address, + recoverySigner.address, + allPermissionsSigner.address, + ], + ]); + + // get the address of the UP and the KeyManager contracts + const [upAddress, keyManagerAddress] = + await ownerControlledContractDeployer.callStatic.deployERC1167Proxies( + ownerControlledDeploymentInit, + ownerDeploymentInit, + upPostDeploymentModule.address, + encodedBytes, + ); + + await ownerControlledContractDeployer.deployERC1167Proxies( + ownerControlledDeploymentInit, + ownerDeploymentInit, + upPostDeploymentModule.address, + encodedBytes, + ); + + const upProxy = UniversalProfileInitFactory.attach(upAddress); + const keyManagerProxy = KeyManagerInitFactory.attach(keyManagerAddress); + + const upProxyOwner = await upProxy.owner(); + const keyManagerProxyOwner = await keyManagerProxy.target(); + + const [expectedUpProxyAddress, expectedKeyManagerProxyAddress] = + await ownerControlledContractDeployer.computeERC1167Addresses( + ownerControlledDeploymentInit.salt, + ownerControlledDeploymentInit.implementationContract, + ownerDeploymentInit.implementationContract, + ownerDeploymentInit.initializationCalldata, + ownerDeploymentInit.addControlledContractAddress, + ownerDeploymentInit.extraInitializationParams, + upPostDeploymentModule.address, + encodedBytes, + ); + + const [calculatedUpProxyAddress, calculatedKMProxyAddress] = await calculateProxiesAddresses( + ownerControlledDeploymentInit.salt, + ownerControlledDeploymentInit.implementationContract, + ownerDeploymentInit.implementationContract, + ownerDeploymentInit.initializationCalldata, + ownerDeploymentInit.addControlledContractAddress, + ownerDeploymentInit.extraInitializationParams, + upPostDeploymentModule.address, + encodedBytes, + ownerControlledContractDeployer.address, + ); + + expect(upAddress).to.equal(expectedUpProxyAddress); + expect(upAddress).to.equal(expectedUpProxyAddress); + expect(upAddress).to.equal(calculatedUpProxyAddress); + + expect(keyManagerAddress).to.equal(expectedKeyManagerProxyAddress); + expect(keyManagerAddress).to.equal(expectedKeyManagerProxyAddress); + expect(keyManagerAddress).to.equal(calculatedKMProxyAddress); + + expect(upProxyOwner).to.equal(keyManagerProxy.address); + expect(upProxyOwner).to.equal(keyManagerProxy.address); + expect(keyManagerProxyOwner).to.equal(upProxy.address); + expect(keyManagerProxyOwner).to.equal(upProxy.address); + }); +}); diff --git a/tests/LSP23MultiChainDeployment/helpers.ts b/tests/LSP23MultiChainDeployment/helpers.ts new file mode 100644 index 000000000..847d2a0b5 --- /dev/null +++ b/tests/LSP23MultiChainDeployment/helpers.ts @@ -0,0 +1,50 @@ +import { ethers } from 'ethers'; + +export async function calculateProxiesAddresses( + salt: ethers.utils.BytesLike, + ownerControlledImplementationContractAddress: string, + ownerImplementationContractAddress: string, + ownerInitializationCalldata: ethers.utils.BytesLike, + ownerAddControlledContractAddress: boolean, + ownerExtraInitializationParams: ethers.utils.BytesLike, + upPostDeploymentModuleAddress: string, + postDeploymentCalldata: ethers.utils.BytesLike, + ownerControlledContractDeployerAddress: string, +): Promise<[string, string]> { + const generatedSalt = ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'address', 'bytes', 'bool', 'bytes', 'address', 'bytes'], + [ + salt, + ownerImplementationContractAddress, + ownerInitializationCalldata, + ownerAddControlledContractAddress, + ownerExtraInitializationParams, + upPostDeploymentModuleAddress, + postDeploymentCalldata, + ], + ), + ); + + const expectedOwnerControlledAddress = ethers.utils.getCreate2Address( + ownerControlledContractDeployerAddress, + generatedSalt, + ethers.utils.keccak256( + '0x3d602d80600a3d3981f3363d3d373d3d3d363d73' + + ownerControlledImplementationContractAddress.slice(2) + + '5af43d82803e903d91602b57fd5bf3', + ), + ); + + const expectedOwnerAddress = ethers.utils.getCreate2Address( + ownerControlledContractDeployerAddress, + ethers.utils.keccak256(expectedOwnerControlledAddress), + ethers.utils.keccak256( + '0x3d602d80600a3d3981f3363d3d373d3d3d363d73' + + ownerImplementationContractAddress.slice(2) + + '5af43d82803e903d91602b57fd5bf3', + ), + ); + + return [expectedOwnerControlledAddress, expectedOwnerAddress]; +} From f971b8ca6117f3b225cdd753785205b82a8eb6af Mon Sep 17 00:00:00 2001 From: Skima Harvey <64636974+skimaharvey@users.noreply.github.com> Date: Fri, 4 Aug 2023 14:11:56 +0200 Subject: [PATCH 3/8] feat!: make reentracy status internal (#651) --- contracts/LSP6KeyManager/LSP6KeyManagerCore.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index a90240023..4ac83fafa 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -78,7 +78,7 @@ abstract contract LSP6KeyManagerCore is // Variables, methods and modifier used for ReentrancyGuard are taken from the link below and modified accordingly. // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.8/contracts/security/ReentrancyGuard.sol - bool private _reentrancyStatus; + bool internal _reentrancyStatus; mapping(address => mapping(uint256 => uint256)) internal _nonceStore; From e26f9d537820a6f22fced6e947ff2cd1f8892fa9 Mon Sep 17 00:00:00 2001 From: Jean Cvllr <31145285+CJ42@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:38:41 +0100 Subject: [PATCH 4/8] chore(release): 0.11.0-rc.0 (#652) --- CHANGELOG.md | 11 +++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba8650c6c..54c2a38c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [0.11.0-rc.0](https://github.com/lukso-network/lsp-smart-contracts/compare/v0.10.3...v0.11.0-rc.0) (2023-08-04) + +### ⚠ BREAKING CHANGES + +- change visibility of `_reentrancyStatus` state variable from `private` to `internal` ([#651](https://github.com/lukso-network/lsp-smart-contracts/pull/651)) + +### Features + +- create LSP23MultiChainDeployment ([#649](https://github.com/lukso-network/lsp-smart-contracts/issues/649)) ([01fd820](https://github.com/lukso-network/lsp-smart-contracts/commit/01fd82038811ba65798f3ff8fe8273fd191dcb38)), closes [#650](https://github.com/lukso-network/lsp-smart-contracts/issues/650) +- change visibility of `_reentrancyStatus` state variable from `private` to `internal` ([#651](https://github.com/lukso-network/lsp-smart-contracts/pull/651)) + ## [0.10.3](https://github.com/lukso-network/lsp-smart-contracts/compare/v0.10.2...v0.10.3) (2023-07-20) ### Bug Fixes diff --git a/package-lock.json b/package-lock.json index aae83b59e..4aab040ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lukso/lsp-smart-contracts", - "version": "0.10.3", + "version": "0.11.0-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@lukso/lsp-smart-contracts", - "version": "0.10.3", + "version": "0.11.0-rc.0", "license": "Apache-2.0", "dependencies": { "@erc725/smart-contracts": "^5.1.0", diff --git a/package.json b/package.json index ede0068fb..5969ea24f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lukso/lsp-smart-contracts", - "version": "0.10.3", + "version": "0.11.0-rc.0", "description": "The reference implementation for universal profiles smart contracts", "directories": { "test": "test" From 0e5d9bf7f9b212976f5c754b28f96b37a8039076 Mon Sep 17 00:00:00 2001 From: Skima Harvey <64636974+skimaharvey@users.noreply.github.com> Date: Mon, 7 Aug 2023 11:11:26 +0200 Subject: [PATCH 5/8] fix: failing tests for lsp23 + add LSP23 test suite in CI (#657) * tests: fix lsp23 tests * ci: add lsp23 tests to lint workflow --- .github/workflows/build-lint-test.yml | 1 + package.json | 1 + ...ehaviour.ts => LSP23MultiChainDeployment.test.ts} | 12 ++++-------- 3 files changed, 6 insertions(+), 8 deletions(-) rename tests/LSP23MultiChainDeployment/{LSP23MultiChainDeployment.behaviour.ts => LSP23MultiChainDeployment.test.ts} (94%) diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 60f54e5e5..435d99d21 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -66,6 +66,7 @@ jobs: "lsp11init", "lsp20", "lsp20init", + "lsp23", "universalfactory", "reentrancy", "reentrancyinit", diff --git a/package.json b/package.json index 5969ea24f..94cf5f5c0 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "test:lsp11init": "hardhat test --no-compile tests/LSP11BasicSocialRecovery/LSP11BasicSocialRecoveryInit.test.ts", "test:lsp20": "hardhat test --no-compile tests/LSP20CallVerification/LSP6/LSP20WithLSP6.test.ts", "test:lsp20init": "hardhat test --no-compile tests/LSP20CallVerification/LSP6/LSP20WithLSP6Init.test.ts", + "test:lsp23": "hardhat test --no-compile tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts", "test:universalfactory": "hardhat test --no-compile tests/LSP16UniversalFactory/LSP16UniversalFactory.test.ts", "test:reentrancy": "hardhat test --no-compile tests/Reentrancy/Reentrancy.test.ts", "test:reentrancyinit": "hardhat test --no-compile tests/Reentrancy/ReentrancyInit.test.ts", diff --git a/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.behaviour.ts b/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts similarity index 94% rename from tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.behaviour.ts rename to tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts index ae511e876..7a808074a 100644 --- a/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.behaviour.ts +++ b/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts @@ -31,7 +31,7 @@ describe('UniversalProfileDeployer', function () { const ownerControlledDeploymentInit: OwnerControlledContractDeployer.ControlledContractDeploymentInitStruct = { salt, - value: 0, + fundingAmount: 0, implementationContract: universalProfileInit.address, initializationCalldata: universalProfileInit.interface.encodeFunctionData('initialize', [ upPostDeploymentModule.address, @@ -39,7 +39,7 @@ describe('UniversalProfileDeployer', function () { }; const ownerDeploymentInit: OwnerControlledContractDeployer.OwnerContractDeploymentInitStruct = { - value: 0, + fundingAmount: 0, implementationContract: keyManagerInit.address, addControlledContractAddress: true, initializationCalldata: '0xc4d66de8', @@ -113,12 +113,8 @@ describe('UniversalProfileDeployer', function () { const [expectedUpProxyAddress, expectedKeyManagerProxyAddress] = await ownerControlledContractDeployer.computeERC1167Addresses( - ownerControlledDeploymentInit.salt, - ownerControlledDeploymentInit.implementationContract, - ownerDeploymentInit.implementationContract, - ownerDeploymentInit.initializationCalldata, - ownerDeploymentInit.addControlledContractAddress, - ownerDeploymentInit.extraInitializationParams, + ownerControlledDeploymentInit, + ownerDeploymentInit, upPostDeploymentModule.address, encodedBytes, ); From d18390950171008625770244b6466af9eabdce19 Mon Sep 17 00:00:00 2001 From: Skima Harvey <64636974+skimaharvey@users.noreply.github.com> Date: Tue, 8 Aug 2023 10:34:27 +0200 Subject: [PATCH 6/8] refactor: rename LSP23 to LinkedContractsFactory (#658) * refactor: rename LSP23 to LinkedContractsFactory * refactor: remove primary bytecode from salt generation * chore: rename LSP23 --- .../ILinkedContractsFactory.sol | 174 ++++++++ .../IPostDeploymentModule.sol | 4 +- .../LSP23Errors.sol | 10 +- .../LinkedContractsFactory.sol | 384 +++++++++++++++++ ...iversalProfileInitPostDeploymentModule.sol | 0 .../IOwnerControlledContractDeployer.sol | 174 -------- .../OwnerControlledContractDeployer.sol | 388 ------------------ .../LSP23MultiChainDeployment.test.ts | 57 ++- 8 files changed, 593 insertions(+), 598 deletions(-) create mode 100644 contracts/LSP23LinkedContractsDeployment/ILinkedContractsFactory.sol rename contracts/{LSP23MultiChainDeployment => LSP23LinkedContractsDeployment}/IPostDeploymentModule.sol (73%) rename contracts/{LSP23MultiChainDeployment => LSP23LinkedContractsDeployment}/LSP23Errors.sol (58%) create mode 100644 contracts/LSP23LinkedContractsDeployment/LinkedContractsFactory.sol rename contracts/{LSP23MultiChainDeployment => LSP23LinkedContractsDeployment}/modules/UniversalProfileInitPostDeploymentModule.sol (100%) delete mode 100644 contracts/LSP23MultiChainDeployment/IOwnerControlledContractDeployer.sol delete mode 100644 contracts/LSP23MultiChainDeployment/OwnerControlledContractDeployer.sol diff --git a/contracts/LSP23LinkedContractsDeployment/ILinkedContractsFactory.sol b/contracts/LSP23LinkedContractsDeployment/ILinkedContractsFactory.sol new file mode 100644 index 000000000..3245fbd95 --- /dev/null +++ b/contracts/LSP23LinkedContractsDeployment/ILinkedContractsFactory.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +interface ILinkedContractsFactory { + event DeployedContracts( + address indexed primaryContract, + address indexed secondaryContract, + PrimaryContractDeployment primaryContractDeployment, + SecondaryContractDeployment secondaryContractDeployment, + address postDeploymentModule, + bytes postDeploymentModuleCalldata + ); + + event DeployedERC1167Proxies( + address indexed primaryContract, + address indexed secondaryContract, + PrimaryContractDeploymentInit primaryContractDeploymentInit, + SecondaryContractDeploymentInit secondaryContractDeploymentInit, + address postDeploymentModule, + bytes postDeploymentModuleCalldata + ); + + /** + * @param salt A unique value used to ensure each created proxies are unique. (Can be used to deploy the contract at a desired address.) + * @param fundingAmount The value to be sent with the deployment transaction. + * @param creationBytecode The bytecode of the contract with the constructor params. + */ + struct PrimaryContractDeployment { + bytes32 salt; + uint256 fundingAmount; + bytes creationBytecode; + } + + /** + * @param fundingAmount The value to be sent with the deployment transaction. + * @param creationBytecode The constructor + runtime bytecode (without the primary contract's address as param) + * @param addPrimaryContractAddress If set to `true`, this will append the primary contract's address + the `extraConstructorParams` to the `creationBytecode`. + * @param extraConstructorParams Params to be appended to the `creationBytecode` (after the primary contract address) if `addPrimaryContractAddress` is set to `true`. + */ + struct SecondaryContractDeployment { + uint256 fundingAmount; + bytes creationBytecode; + bool addPrimaryContractAddress; + bytes extraConstructorParams; + } + + /** + * @param salt A unique value used to ensure each created proxies are unique. (Can be used to deploy the contract at a desired address.) + * @param fundingAmount The value to be sent with the deployment transaction. + * @param implementationContract The address of the contract that will be used as a base contract for the proxy. + * @param initializationCalldata The calldata used to initialise the contract. (initialization should be similar to a constructor in a normal contract.) + */ + struct PrimaryContractDeploymentInit { + bytes32 salt; + uint256 fundingAmount; + address implementationContract; + bytes initializationCalldata; + } + + /** + * @param fundingAmount The value to be sent with the deployment transaction. + * @param implementationContract The address of the contract that will be used as a base contract for the proxy. + * @param initializationCalldata The first part of the initialisation calldata, everything before the primary contract address. + * @param addPrimaryContractAddress If set to `true`, this will append the primary contract's address + the `extraInitializationParams` to the `initializationCalldata`. + * @param extraInitializationParams Params to be appended to the `initializationCalldata` (after the primary contract address) if `addPrimaryContractAddress` is set to `true` + */ + struct SecondaryContractDeploymentInit { + uint256 fundingAmount; + address implementationContract; + bytes initializationCalldata; + bool addPrimaryContractAddress; + bytes extraInitializationParams; + } + + /** + * @dev Deploys a primary and a secondary linked contract. + * @notice Contracts deployed. Contract Address: `primaryContractAddress`. Primary Contract Address: `primaryContractAddress` + * + * @param primaryContractDeployment Contains the needed parameter to deploy a contract. (`salt`, `fundingAmount`, `creationBytecode`) + * @param secondaryContractDeployment Contains the needed parameter to deploy the secondary contract. (`fundingAmount`, `creationBytecode`, `addPrimaryContractAddress`, `extraConstructorParams`) + * @param postDeploymentModule The module to be executed after deployment + * @param postDeploymentModuleCalldata The data to be passed to the post deployment module + * + * @return primaryContractAddress The address of the primary contract. + * @return secondaryContractAddress The address of the secondary contract. + */ + function deployContracts( + PrimaryContractDeployment calldata primaryContractDeployment, + SecondaryContractDeployment calldata secondaryContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + external + payable + returns ( + address primaryContractAddress, + address secondaryContractAddress + ); + + /** + * @dev Deploys proxies of a primary contract and a secondary linked contract + * @notice Contract proxies deployed. Primary Proxy Address: `primaryContractAddress`. Secondary Contract Proxy Address: `secondaryContractAddress` + * + * @param primaryContractDeploymentInit Contains the needed parameters to deploy a proxy contract. (`salt`, `fundingAmount`, `implementationContract`, `initializationCalldata`) + * @param secondaryContractDeploymentInit Contains the needed parameters to deploy the secondary proxy contract. (`fundingAmount`, `implementationContract`, `addPrimaryContractAddress`, `initializationCalldata`, `extraInitializationParams`) + * @param postDeploymentModule The module to be executed after deployment. + * @param postDeploymentModuleCalldata The data to be passed to the post deployment module. + * + * @return primaryContractAddress The address of the deployed primary contract proxy + * @return secondaryContractAddress The address of the deployed secondary contract proxy + */ + function deployERC1167Proxies( + PrimaryContractDeploymentInit calldata primaryContractDeploymentInit, + SecondaryContractDeploymentInit + calldata secondaryContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + external + payable + returns ( + address primaryContractAddress, + address secondaryContractAddress + ); + + /** + * @dev Computes the addresses of a primary contract and a secondary linked contract + * + * @param primaryContractDeployment Contains the needed parameter to deploy the primary contract. (`salt`, `fundingAmount`, `creationBytecode`) + * @param secondaryContractDeployment Contains the needed parameter to deploy the secondary contract. (`fundingAmount`, `creationBytecode`, `addPrimaryContractAddress`, `extraConstructorParams`) + * @param postDeploymentModule The module to be executed after deployment + * @param postDeploymentModuleCalldata The data to be passed to the post deployment module + * + * @return primaryContractAddress The address of the deployed primary contract. + * @return secondaryContractAddress The address of the deployed secondary contract. + */ + function computeAddresses( + PrimaryContractDeployment calldata primaryContractDeployment, + SecondaryContractDeployment calldata secondaryContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + external + view + returns ( + address primaryContractAddress, + address secondaryContractAddress + ); + + /** + * @dev Computes the addresses of a primary and a secondary linked contracts proxies to be created + * + * @param primaryContractDeploymentInit Contains the needed parameters to deploy a primary proxy contract. (`salt`, `fundingAmount`, `implementationContract`, `initializationCalldata`) + * @param secondaryContractDeploymentInit Contains the needed parameters to deploy the secondary proxy contract. (`fundingAmount`, `implementationContract`, `addPrimaryContractAddress`, `initializationCalldata`, `extraInitializationParams`) + * @param postDeploymentModule The module to be executed after deployment. + * @param postDeploymentModuleCalldata The data to be passed to the post deployment module. + * + * @return primaryContractAddress The address of the deployed primary contract proxy + * @return secondaryContractAddress The address of the deployed secondary contract proxy + */ + function computeERC1167Addresses( + PrimaryContractDeploymentInit calldata primaryContractDeploymentInit, + SecondaryContractDeploymentInit + calldata secondaryContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + external + view + returns ( + address primaryContractAddress, + address secondaryContractAddress + ); +} diff --git a/contracts/LSP23MultiChainDeployment/IPostDeploymentModule.sol b/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol similarity index 73% rename from contracts/LSP23MultiChainDeployment/IPostDeploymentModule.sol rename to contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol index 083051c57..5ed8b2301 100644 --- a/contracts/LSP23MultiChainDeployment/IPostDeploymentModule.sol +++ b/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.4; interface IPostDeploymentModule { function executePostDeployment( - address ownerControlledContract, - address ownerContract, + address primaryContract, + address secondaryContract, bytes calldata calldataToPostDeploymentModule ) external; } diff --git a/contracts/LSP23MultiChainDeployment/LSP23Errors.sol b/contracts/LSP23LinkedContractsDeployment/LSP23Errors.sol similarity index 58% rename from contracts/LSP23MultiChainDeployment/LSP23Errors.sol rename to contracts/LSP23LinkedContractsDeployment/LSP23Errors.sol index 90eb8a440..3690fe9fb 100644 --- a/contracts/LSP23MultiChainDeployment/LSP23Errors.sol +++ b/contracts/LSP23LinkedContractsDeployment/LSP23Errors.sol @@ -9,16 +9,16 @@ error InvalidValueSum(); /** * @dev Reverts when the deployment & intialisation of the contract has failed. - * @notice Failed to deploy & initialise the Controlled Contract Proxy. Error: `errorData`. + * @notice Failed to deploy & initialise the Primary Contract Proxy. Error: `errorData`. * * @param errorData Potentially information about why the deployment & intialisation have failed. */ -error ControlledContractProxyInitFailureError(bytes errorData); +error PrimaryContractProxyInitFailureError(bytes errorData); /** - * @dev Reverts when the deployment & intialisation of the owner contract has failed. - * @notice Failed to deploy & initialise the Owner Contract Proxy. Error: `errorData`. + * @dev Reverts when the deployment & intialisation of the secondary contract has failed. + * @notice Failed to deploy & initialise the Secondary Contract Proxy. Error: `errorData`. * * @param errorData Potentially information about why the deployment & intialisation have failed. */ -error OwnerContractProxyInitFailureError(bytes errorData); +error SecondaryContractProxyInitFailureError(bytes errorData); diff --git a/contracts/LSP23LinkedContractsDeployment/LinkedContractsFactory.sol b/contracts/LSP23LinkedContractsDeployment/LinkedContractsFactory.sol new file mode 100644 index 000000000..434f30e79 --- /dev/null +++ b/contracts/LSP23LinkedContractsDeployment/LinkedContractsFactory.sol @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {IPostDeploymentModule} from "./IPostDeploymentModule.sol"; +import {ILinkedContractsFactory} from "./ILinkedContractsFactory.sol"; +import { + InvalidValueSum, + PrimaryContractProxyInitFailureError, + SecondaryContractProxyInitFailureError +} from "./LSP23Errors.sol"; + +contract LinkedContractsFactory is ILinkedContractsFactory { + /** + * @inheritdoc ILinkedContractsFactory + */ + function deployContracts( + PrimaryContractDeployment calldata primaryContractDeployment, + SecondaryContractDeployment calldata secondaryContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + public + payable + returns ( + address primaryContractAddress, + address secondaryContractAddress + ) + { + /* check that the msg.value is equal to the sum of the values of the primary and secondary contracts */ + if ( + msg.value != + primaryContractDeployment.fundingAmount + + secondaryContractDeployment.fundingAmount + ) { + revert InvalidValueSum(); + } + + primaryContractAddress = _deployPrimaryContract( + primaryContractDeployment, + secondaryContractDeployment, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + secondaryContractAddress = _deploySecondaryContract( + secondaryContractDeployment, + primaryContractAddress + ); + + emit DeployedContracts( + primaryContractAddress, + secondaryContractAddress, + primaryContractDeployment, + secondaryContractDeployment, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* execute the post deployment module logic in the postDeploymentModule */ + IPostDeploymentModule(postDeploymentModule).executePostDeployment( + primaryContractAddress, + secondaryContractAddress, + postDeploymentModuleCalldata + ); + } + + /** + * @inheritdoc ILinkedContractsFactory + */ + function deployERC1167Proxies( + PrimaryContractDeploymentInit calldata primaryContractDeploymentInit, + SecondaryContractDeploymentInit + calldata secondaryContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + public + payable + returns ( + address primaryContractAddress, + address secondaryContractAddress + ) + { + /* check that the msg.value is equal to the sum of the values of the primary and secondary contracts */ + if ( + msg.value != + primaryContractDeploymentInit.fundingAmount + + secondaryContractDeploymentInit.fundingAmount + ) { + revert InvalidValueSum(); + } + + /* deploy the primary contract proxy with the primaryContractGeneratedSalt */ + primaryContractAddress = _deployAndInitializePrimaryContractProxy( + primaryContractDeploymentInit, + secondaryContractDeploymentInit, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* deploy the secondary contract proxy */ + secondaryContractAddress = _deployAndInitializeSecondaryContractProxy( + secondaryContractDeploymentInit, + primaryContractAddress + ); + + emit DeployedERC1167Proxies( + primaryContractAddress, + secondaryContractAddress, + primaryContractDeploymentInit, + secondaryContractDeploymentInit, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* execute the post deployment logic in the postDeploymentModule */ + IPostDeploymentModule(postDeploymentModule).executePostDeployment( + primaryContractAddress, + secondaryContractAddress, + postDeploymentModuleCalldata + ); + } + + /** + * @inheritdoc ILinkedContractsFactory + */ + function computeAddresses( + PrimaryContractDeployment calldata primaryContractDeployment, + SecondaryContractDeployment calldata secondaryContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + public + view + returns ( + address primaryContractAddress, + address secondaryContractAddress + ) + { + bytes32 primaryContractGeneratedSalt = _generatePrimaryContractSalt( + primaryContractDeployment, + secondaryContractDeployment, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + primaryContractAddress = Create2.computeAddress( + primaryContractGeneratedSalt, + keccak256(primaryContractDeployment.creationBytecode) + ); + + bytes memory secondaryContractByteCodeWithAllParams; + if (secondaryContractDeployment.addPrimaryContractAddress) { + secondaryContractByteCodeWithAllParams = abi.encodePacked( + secondaryContractDeployment.creationBytecode, + abi.encode(primaryContractAddress), + secondaryContractDeployment.extraConstructorParams + ); + } else { + secondaryContractByteCodeWithAllParams = secondaryContractDeployment + .creationBytecode; + } + + secondaryContractAddress = Create2.computeAddress( + keccak256(abi.encodePacked(primaryContractAddress)), + keccak256(secondaryContractByteCodeWithAllParams) + ); + } + + /** + * @inheritdoc ILinkedContractsFactory + */ + function computeERC1167Addresses( + PrimaryContractDeploymentInit calldata primaryContractDeploymentInit, + SecondaryContractDeploymentInit + calldata secondaryContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + public + view + returns ( + address primaryContractAddress, + address secondaryContractAddress + ) + { + bytes32 primaryContractGeneratedSalt = _generatePrimaryContractProxySalt( + primaryContractDeploymentInit, + secondaryContractDeploymentInit, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + primaryContractAddress = Clones.predictDeterministicAddress( + primaryContractDeploymentInit.implementationContract, + primaryContractGeneratedSalt + ); + + secondaryContractAddress = Clones.predictDeterministicAddress( + secondaryContractDeploymentInit.implementationContract, + keccak256(abi.encodePacked(primaryContractAddress)) + ); + } + + function _deployPrimaryContract( + PrimaryContractDeployment calldata primaryContractDeployment, + SecondaryContractDeployment calldata secondaryContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) internal returns (address primaryContractAddress) { + bytes32 primaryContractGeneratedSalt = _generatePrimaryContractSalt( + primaryContractDeployment, + secondaryContractDeployment, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* deploy the primary contract */ + primaryContractAddress = Create2.deploy( + primaryContractDeployment.fundingAmount, + primaryContractGeneratedSalt, + primaryContractDeployment.creationBytecode + ); + } + + function _deploySecondaryContract( + SecondaryContractDeployment calldata secondaryContractDeployment, + address primaryContractAddress + ) internal returns (address secondaryContractAddress) { + /** + * If `addPrimaryContractAddress` is `true`, the following will be appended to the constructor params: + * - The primary contract address + * - `extraConstructorParams` + */ + bytes memory secondaryContractByteCode = secondaryContractDeployment + .creationBytecode; + + if (secondaryContractDeployment.addPrimaryContractAddress) { + secondaryContractByteCode = abi.encodePacked( + secondaryContractByteCode, + abi.encode(primaryContractAddress), + secondaryContractDeployment.extraConstructorParams + ); + } + + secondaryContractAddress = Create2.deploy( + secondaryContractDeployment.fundingAmount, + keccak256(abi.encodePacked(primaryContractAddress)), + secondaryContractByteCode + ); + } + + function _deployAndInitializePrimaryContractProxy( + PrimaryContractDeploymentInit calldata primaryContractDeploymentInit, + SecondaryContractDeploymentInit + calldata secondaryContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) internal returns (address primaryContractAddress) { + bytes32 primaryContractGeneratedSalt = _generatePrimaryContractProxySalt( + primaryContractDeploymentInit, + secondaryContractDeploymentInit, + postDeploymentModule, + postDeploymentModuleCalldata + ); + + /* deploy the primary contract proxy with the primaryContractGeneratedSalt */ + primaryContractAddress = Clones.cloneDeterministic( + primaryContractDeploymentInit.implementationContract, + primaryContractGeneratedSalt + ); + + /* initialize the primary contract proxy */ + (bool success, bytes memory returnedData) = primaryContractAddress.call{ + value: msg.value + }(primaryContractDeploymentInit.initializationCalldata); + if (!success) { + revert PrimaryContractProxyInitFailureError(returnedData); + } + } + + function _deployAndInitializeSecondaryContractProxy( + SecondaryContractDeploymentInit + calldata secondaryContractDeploymentInit, + address primaryContractAddress + ) internal returns (address secondaryContractAddress) { + /* deploy the secondary contract proxy with the primaryContractGeneratedSalt */ + secondaryContractAddress = Clones.cloneDeterministic( + secondaryContractDeploymentInit.implementationContract, + keccak256(abi.encodePacked(primaryContractAddress)) + ); + + /** + * If `addPrimaryContractAddress` is `true`, the following will be appended to the `initializationCalldata`: + * - The primary contract address + * - `extraInitialisationBytes` + */ + bytes + memory secondaryInitializationBytes = secondaryContractDeploymentInit + .initializationCalldata; + + if (secondaryContractDeploymentInit.addPrimaryContractAddress) { + secondaryInitializationBytes = abi.encodePacked( + secondaryInitializationBytes, + abi.encode(primaryContractAddress), + secondaryContractDeploymentInit.extraInitializationParams + ); + } + + /* initialize the primary contract proxy */ + (bool success, bytes memory returnedData) = secondaryContractAddress + .call{value: msg.value}(secondaryInitializationBytes); + if (!success) { + revert SecondaryContractProxyInitFailureError(returnedData); + } + } + + function _generatePrimaryContractSalt( + PrimaryContractDeployment calldata primaryContractDeployment, + SecondaryContractDeployment calldata secondaryContractDeployment, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) internal pure virtual returns (bytes32 primaryContractGeneratedSalt) { + /* generate salt for the primary contract + * the salt is generated by hashing the following elements: + * - the salt + * - the secondary contract bytecode + * - the secondary addPrimaryContractAddress boolean + * - the secondary extraConstructorParams + * - the postDeploymentModule address + * - the postDeploymentModuleCalldata + * + */ + primaryContractGeneratedSalt = keccak256( + abi.encode( + primaryContractDeployment.salt, + secondaryContractDeployment.creationBytecode, + secondaryContractDeployment.addPrimaryContractAddress, + secondaryContractDeployment.extraConstructorParams, + postDeploymentModule, + postDeploymentModuleCalldata + ) + ); + } + + function _generatePrimaryContractProxySalt( + PrimaryContractDeploymentInit calldata primaryContractDeploymentInit, + SecondaryContractDeploymentInit + calldata secondaryContractDeploymentInit, + address postDeploymentModule, + bytes calldata postDeploymentModuleCalldata + ) + internal + pure + virtual + returns (bytes32 primaryContractProxyGeneratedSalt) + { + /** + * Generate the salt for the primary contract + * The salt is generated by hashing the following elements: + * - the salt + * - the secondary implementation contract address + * - the secondary contract addPrimaryContractAddress boolean + * - the secondary contract initialization calldata + * - the secondary contract extra initialization params (if any) + * - the postDeploymentModule address + * - the callda to the post deployment module + * + */ + primaryContractProxyGeneratedSalt = keccak256( + abi.encode( + primaryContractDeploymentInit.salt, + secondaryContractDeploymentInit.implementationContract, + secondaryContractDeploymentInit.initializationCalldata, + secondaryContractDeploymentInit.addPrimaryContractAddress, + secondaryContractDeploymentInit.extraInitializationParams, + postDeploymentModule, + postDeploymentModuleCalldata + ) + ); + } +} diff --git a/contracts/LSP23MultiChainDeployment/modules/UniversalProfileInitPostDeploymentModule.sol b/contracts/LSP23LinkedContractsDeployment/modules/UniversalProfileInitPostDeploymentModule.sol similarity index 100% rename from contracts/LSP23MultiChainDeployment/modules/UniversalProfileInitPostDeploymentModule.sol rename to contracts/LSP23LinkedContractsDeployment/modules/UniversalProfileInitPostDeploymentModule.sol diff --git a/contracts/LSP23MultiChainDeployment/IOwnerControlledContractDeployer.sol b/contracts/LSP23MultiChainDeployment/IOwnerControlledContractDeployer.sol deleted file mode 100644 index 54b00cc6e..000000000 --- a/contracts/LSP23MultiChainDeployment/IOwnerControlledContractDeployer.sol +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -interface IOwnerControlledContractDeployer { - event DeployedContracts( - address indexed controlledContract, - address indexed ownerContract, - ControlledContractDeployment controlledContractDeployment, - OwnerContractDeployment ownerContractDeployment, - address postDeploymentModule, - bytes postDeploymentModuleCalldata - ); - - event DeployedERC1167Proxies( - address indexed controlledContract, - address indexed ownerContract, - ControlledContractDeploymentInit controlledContractDeploymentInit, - OwnerContractDeploymentInit ownerContractDeploymentInit, - address postDeploymentModule, - bytes postDeploymentModuleCalldata - ); - - /** - * @param salt A unique value used to ensure each created proxies are unique. (Can be used to deploy the contract at a desired address.) - * @param fundingAmount The value to be sent with the deployment transaction. - * @param creationBytecode The bytecode of the contract with the constructor params. - */ - struct ControlledContractDeployment { - bytes32 salt; - uint256 fundingAmount; - bytes creationBytecode; - } - - /** - * @param fundingAmount The value to be sent with the deployment transaction. - * @param creationBytecode The constructor + runtime bytecode (without the controlled contract's address as param) - * @param addControlledContractAddress If set to `true`, this will append the controlled contract's address + the `extraConstructorParams` to the `creationBytecode`. - * @param extraConstructorParams Params to be appended to the `creationBytecode` (after the controlled contract address) if `addControlledContractAddress` is set to `true`. - */ - struct OwnerContractDeployment { - uint256 fundingAmount; - bytes creationBytecode; - bool addControlledContractAddress; - bytes extraConstructorParams; - } - - /** - * @param salt A unique value used to ensure each created proxies are unique. (Can be used to deploy the contract at a desired address.) - * @param fundingAmount The value to be sent with the deployment transaction. - * @param implementationContract The address of the contract that will be used as a base contract for the proxy. - * @param initializationCalldata The calldata used to initialise the contract. (initialization should be similar to a constructor in a normal contract.) - */ - struct ControlledContractDeploymentInit { - bytes32 salt; - uint256 fundingAmount; - address implementationContract; - bytes initializationCalldata; - } - - /** - * @param fundingAmount The value to be sent with the deployment transaction. - * @param implementationContract The address of the contract that will be used as a base contract for the proxy. - * @param initializationCalldata The first part of the initialisation calldata, everything before the controlled contract address. - * @param addControlledContractAddress If set to `true`, this will append the controlled contract's address + the `extraInitializationParams` to the `initializationCalldata`. - * @param extraInitializationParams Params to be appended to the `initializationCalldata` (after the controlled contract address) if `addControlledContractAddress` is set to `true` - */ - struct OwnerContractDeploymentInit { - uint256 fundingAmount; - address implementationContract; - bytes initializationCalldata; - bool addControlledContractAddress; - bytes extraInitializationParams; - } - - /** - * @dev Deploys a contract and its owner contract. - * @notice Contracts deployed. Contract Address: `controlledContractAddress`. Owner Contract Address: `ownerContractAddress` - * - * @param controlledContractDeployment Contains the needed parameter to deploy a contract. (`salt`, `fundingAmount`, `creationBytecode`) - * @param ownerContractDeployment Contains the needed parameter to deploy the owner contract. (`fundingAmount`, `creationBytecode`, `addControlledContractAddress`, `extraConstructorParams`) - * @param postDeploymentModule The module to be executed after deployment - * @param postDeploymentModuleCalldata The data to be passed to the post deployment module - * - * @return controlledContractAddress The address of the deployed controlled contract. - * @return ownerContractAddress The address of the deployed owner contract. - */ - function deployContracts( - ControlledContractDeployment calldata controlledContractDeployment, - OwnerContractDeployment calldata ownerContractDeployment, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) - external - payable - returns ( - address controlledContractAddress, - address ownerContractAddress - ); - - /** - * @dev Deploys proxies of a contract and its owner contract - * @notice Contract proxies deployed. Contract Proxy Address: `controlledContractAddress`. Owner Contract Proxy Address: `ownerContractAddress` - * - * @param controlledContractDeploymentInit Contains the needed parameter to deploy a proxy contract. (`salt`, `fundingAmount`, `implementationContract`, `initializationCalldata`) - * @param ownerContractDeploymentInit Contains the needed parameter to deploy the owner proxy contract. (`fundingAmount`, `implementationContract`, `addControlledContractAddress`, `initializationCalldata`, `extraInitializationParams`) - * @param postDeploymentModule The module to be executed after deployment. - * @param postDeploymentModuleCalldata The data to be passed to the post deployment module. - * - * @return controlledContractAddress The address of the deployed controlled contract proxy - * @return ownerContractAddress The address of the deployed owner contract proxy - */ - function deployERC1167Proxies( - ControlledContractDeploymentInit - calldata controlledContractDeploymentInit, - OwnerContractDeploymentInit calldata ownerContractDeploymentInit, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) - external - payable - returns ( - address controlledContractAddress, - address ownerContractAddress - ); - - /** - * @dev Computes the addresses of the controlled and owner contracts to be created - * - * @param controlledContractDeployment Contains the needed parameter to deploy a contract. (`salt`, `fundingAmount`, `creationBytecode`) - * @param ownerContractDeployment Contains the needed parameter to deploy the owner contract. (`fundingAmount`, `creationBytecode`, `addControlledContractAddress`, `extraConstructorParams`) - * @param postDeploymentModule The module to be executed after deployment - * @param postDeploymentModuleCalldata The data to be passed to the post deployment module - * - * @return controlledContractAddress The address of the deployed controlled contract. - * @return ownerContractAddress The address of the deployed owner contract. - */ - function computeAddresses( - ControlledContractDeployment calldata controlledContractDeployment, - OwnerContractDeployment calldata ownerContractDeployment, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) - external - view - returns ( - address controlledContractAddress, - address ownerContractAddress - ); - - /** - * @dev Computes the addresses of the controlled and owner contract proxies to be created. - * - * @param controlledContractDeploymentInit Contains the needed parameter to deploy a proxy contract. (`salt`, `fundingAmount`, `implementationContract`, `initializationCalldata`) - * @param ownerContractDeploymentInit Contains the needed parameter to deploy the owner proxy contract. (`fundingAmount`, `implementationContract`, `addControlledContractAddress`, `initializationCalldata`, `extraInitializationParams`) - * @param postDeploymentModule The module to be executed after deployment. - * @param postDeploymentModuleCalldata The data to be passed to the post deployment module. - * - * @return controlledContractAddress The address of the deployed controlled contract proxy - * @return ownerContractAddress The address of the deployed owner contract proxy - */ - function computeERC1167Addresses( - ControlledContractDeploymentInit - calldata controlledContractDeploymentInit, - OwnerContractDeploymentInit calldata ownerContractDeploymentInit, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) - external - view - returns ( - address controlledContractAddress, - address ownerContractAddress - ); -} diff --git a/contracts/LSP23MultiChainDeployment/OwnerControlledContractDeployer.sol b/contracts/LSP23MultiChainDeployment/OwnerControlledContractDeployer.sol deleted file mode 100644 index 85955a495..000000000 --- a/contracts/LSP23MultiChainDeployment/OwnerControlledContractDeployer.sol +++ /dev/null @@ -1,388 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import {Create2} from "@openzeppelin/contracts/utils/Create2.sol"; -import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; -import {IPostDeploymentModule} from "./IPostDeploymentModule.sol"; -import { - IOwnerControlledContractDeployer -} from "./IOwnerControlledContractDeployer.sol"; -import { - InvalidValueSum, - ControlledContractProxyInitFailureError, - OwnerContractProxyInitFailureError -} from "./LSP23Errors.sol"; - -contract OwnerControlledContractDeployer is IOwnerControlledContractDeployer { - /** - * @inheritdoc IOwnerControlledContractDeployer - */ - function deployContracts( - ControlledContractDeployment calldata controlledContractDeployment, - OwnerContractDeployment calldata ownerContractDeployment, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) - public - payable - returns ( - address controlledContractAddress, - address ownerContractAddress - ) - { - /* check that the msg.value is equal to the sum of the values of the controlled and owner contracts */ - if ( - msg.value != - controlledContractDeployment.fundingAmount + - ownerContractDeployment.fundingAmount - ) { - revert InvalidValueSum(); - } - - controlledContractAddress = _deployControlledContract( - controlledContractDeployment, - ownerContractDeployment, - postDeploymentModule, - postDeploymentModuleCalldata - ); - - ownerContractAddress = _deployOwnerContract( - ownerContractDeployment, - controlledContractAddress - ); - - emit DeployedContracts( - controlledContractAddress, - ownerContractAddress, - controlledContractDeployment, - ownerContractDeployment, - postDeploymentModule, - postDeploymentModuleCalldata - ); - - /* execute the post deployment module logic in the postDeploymentModule */ - IPostDeploymentModule(postDeploymentModule).executePostDeployment( - controlledContractAddress, - ownerContractAddress, - postDeploymentModuleCalldata - ); - } - - /** - * @inheritdoc IOwnerControlledContractDeployer - */ - function deployERC1167Proxies( - ControlledContractDeploymentInit - calldata controlledContractDeploymentInit, - OwnerContractDeploymentInit calldata ownerContractDeploymentInit, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) - public - payable - returns ( - address controlledContractAddress, - address ownerContractAddress - ) - { - /* check that the msg.value is equal to the sum of the values of the controlled and owner contracts */ - if ( - msg.value != - controlledContractDeploymentInit.fundingAmount + - ownerContractDeploymentInit.fundingAmount - ) { - revert InvalidValueSum(); - } - - /* deploy the controlled contract proxy with the controlledContractGeneratedSalt */ - controlledContractAddress = _deployAndInitializeControlledContractProxy( - controlledContractDeploymentInit, - ownerContractDeploymentInit, - postDeploymentModule, - postDeploymentModuleCalldata - ); - - /* deploy the owner contract proxy */ - ownerContractAddress = _deployAndInitializeOwnerContractProxy( - ownerContractDeploymentInit, - controlledContractAddress - ); - - emit DeployedERC1167Proxies( - controlledContractAddress, - ownerContractAddress, - controlledContractDeploymentInit, - ownerContractDeploymentInit, - postDeploymentModule, - postDeploymentModuleCalldata - ); - - /* execute the post deployment logic in the postDeploymentModule */ - IPostDeploymentModule(postDeploymentModule).executePostDeployment( - controlledContractAddress, - ownerContractAddress, - postDeploymentModuleCalldata - ); - } - - /** - * @inheritdoc IOwnerControlledContractDeployer - */ - function computeAddresses( - ControlledContractDeployment calldata controlledContractDeployment, - OwnerContractDeployment calldata ownerContractDeployment, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) - public - view - returns ( - address controlledContractAddress, - address ownerContractAddress - ) - { - bytes32 controlledContractGeneratedSalt = _generateControlledContractSalt( - controlledContractDeployment, - ownerContractDeployment, - postDeploymentModule, - postDeploymentModuleCalldata - ); - - controlledContractAddress = Create2.computeAddress( - controlledContractGeneratedSalt, - keccak256(controlledContractDeployment.creationBytecode) - ); - - bytes memory ownerContractByteCodeWithAllParams; - if (ownerContractDeployment.addControlledContractAddress) { - ownerContractByteCodeWithAllParams = abi.encodePacked( - ownerContractDeployment.creationBytecode, - abi.encode(controlledContractAddress), - ownerContractDeployment.extraConstructorParams - ); - } else { - ownerContractByteCodeWithAllParams = ownerContractDeployment - .creationBytecode; - } - - ownerContractAddress = Create2.computeAddress( - keccak256(abi.encodePacked(controlledContractAddress)), - keccak256(ownerContractByteCodeWithAllParams) - ); - } - - /** - * @inheritdoc IOwnerControlledContractDeployer - */ - function computeERC1167Addresses( - ControlledContractDeploymentInit - calldata controlledContractDeploymentInit, - OwnerContractDeploymentInit calldata ownerContractDeploymentInit, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) - public - view - returns ( - address controlledContractAddress, - address ownerContractAddress - ) - { - bytes32 controlledContractGeneratedSalt = _generateControlledProxyContractSalt( - controlledContractDeploymentInit, - ownerContractDeploymentInit, - postDeploymentModule, - postDeploymentModuleCalldata - ); - - controlledContractAddress = Clones.predictDeterministicAddress( - controlledContractDeploymentInit.implementationContract, - controlledContractGeneratedSalt - ); - - ownerContractAddress = Clones.predictDeterministicAddress( - ownerContractDeploymentInit.implementationContract, - keccak256(abi.encodePacked(controlledContractAddress)) - ); - } - - function _deployControlledContract( - ControlledContractDeployment calldata controlledContractDeployment, - OwnerContractDeployment calldata ownerContractDeployment, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) internal returns (address controlledContractAddress) { - bytes32 controlledContractGeneratedSalt = _generateControlledContractSalt( - controlledContractDeployment, - ownerContractDeployment, - postDeploymentModule, - postDeploymentModuleCalldata - ); - - /* deploy the controlled contract */ - controlledContractAddress = Create2.deploy( - controlledContractDeployment.fundingAmount, - controlledContractGeneratedSalt, - controlledContractDeployment.creationBytecode - ); - } - - function _deployOwnerContract( - OwnerContractDeployment calldata ownerContractDeployment, - address controlledContractAddress - ) internal returns (address ownerContractAddress) { - /** - * If `addControlledContractAddress` is `true`, the following will be appended to the constructor params: - * - The controlled contract address - * - `extraConstructorParams` - */ - bytes memory ownerContractByteCode = ownerContractDeployment - .creationBytecode; - - if (ownerContractDeployment.addControlledContractAddress) { - ownerContractByteCode = abi.encodePacked( - ownerContractByteCode, - abi.encode(controlledContractAddress), - ownerContractDeployment.extraConstructorParams - ); - } - - /* Here owner refers to the future owner of the controlled contract at the end of the transaction */ - ownerContractAddress = Create2.deploy( - ownerContractDeployment.fundingAmount, - keccak256(abi.encodePacked(controlledContractAddress)), - ownerContractByteCode - ); - } - - function _deployAndInitializeControlledContractProxy( - ControlledContractDeploymentInit - calldata controlledContractDeploymentInit, - OwnerContractDeploymentInit calldata ownerContractDeploymentInit, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) internal returns (address controlledContractAddress) { - bytes32 controlledContractGeneratedSalt = _generateControlledProxyContractSalt( - controlledContractDeploymentInit, - ownerContractDeploymentInit, - postDeploymentModule, - postDeploymentModuleCalldata - ); - - /* deploy the controlled contract proxy with the controlledContractGeneratedSalt */ - controlledContractAddress = Clones.cloneDeterministic( - controlledContractDeploymentInit.implementationContract, - controlledContractGeneratedSalt - ); - - /* initialize the controlled contract proxy */ - (bool success, bytes memory returnedData) = controlledContractAddress - .call{value: msg.value}( - controlledContractDeploymentInit.initializationCalldata - ); - if (!success) { - revert ControlledContractProxyInitFailureError(returnedData); - } - } - - function _deployAndInitializeOwnerContractProxy( - OwnerContractDeploymentInit calldata ownerContractDeploymentInit, - address controlledContractAddress - ) internal returns (address ownerContractAddress) { - /* deploy the controlled contract proxy with the controlledContractGeneratedSalt */ - ownerContractAddress = Clones.cloneDeterministic( - ownerContractDeploymentInit.implementationContract, - keccak256(abi.encodePacked(controlledContractAddress)) - ); - - /** - * If `addControlledContractAddress` is `true`, the following will be appended to the `initializationCalldata`: - * - The controlled contract address - * - `extraInitialisationBytes` - */ - bytes memory ownerInitializationBytes = ownerContractDeploymentInit - .initializationCalldata; - - if (ownerContractDeploymentInit.addControlledContractAddress) { - ownerInitializationBytes = abi.encodePacked( - ownerInitializationBytes, - abi.encode(controlledContractAddress), - ownerContractDeploymentInit.extraInitializationParams - ); - } - - /* initialize the controlled contract proxy */ - (bool success, bytes memory returnedData) = ownerContractAddress.call{ - value: msg.value - }(ownerInitializationBytes); - if (!success) { - revert OwnerContractProxyInitFailureError(returnedData); - } - } - - function _generateControlledContractSalt( - ControlledContractDeployment calldata controlledContractDeployment, - OwnerContractDeployment calldata ownerContractDeployment, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) internal pure virtual returns (bytes32 controlledContractGeneratedSalt) { - /* generate salt for the controlled contract - * the salt is generated by hashing the following elements: - * - the salt - * - the owner contract bytecode - * - the owner addControlledContractAddress boolean - * - the owner extraConstructorParams - * - the postDeploymentModule address - * - the postDeploymentModuleCalldata - * - */ - controlledContractGeneratedSalt = keccak256( - abi.encode( - controlledContractDeployment.salt, - controlledContractDeployment.creationBytecode, - ownerContractDeployment.creationBytecode, - ownerContractDeployment.addControlledContractAddress, - ownerContractDeployment.extraConstructorParams, - postDeploymentModule, - postDeploymentModuleCalldata - ) - ); - } - - function _generateControlledProxyContractSalt( - ControlledContractDeploymentInit - calldata controlledContractDeploymentInit, - OwnerContractDeploymentInit calldata ownerContractDeploymentInit, - address postDeploymentModule, - bytes calldata postDeploymentModuleCalldata - ) - internal - pure - virtual - returns (bytes32 controlledProxyContractGeneratedSalt) - { - /** - * Generate the salt for the controlled contract - * The salt is generated by hashing the following elements: - * - the salt - * - the owner implementation contract address - * - the owner contract addControlledContractAddress boolean - * - the owner contract initialization calldata - * - the owner contract extra initialization params (if any) - * - the postDeploymentModule address - * - the callda to the post deployment module - * - */ - controlledProxyContractGeneratedSalt = keccak256( - abi.encode( - controlledContractDeploymentInit.salt, - ownerContractDeploymentInit.implementationContract, - ownerContractDeploymentInit.initializationCalldata, - ownerContractDeploymentInit.addControlledContractAddress, - ownerContractDeploymentInit.extraInitializationParams, - postDeploymentModule, - postDeploymentModuleCalldata - ) - ); - } -} diff --git a/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts b/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts index 7a808074a..334f7cfc2 100644 --- a/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts +++ b/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts @@ -1,6 +1,6 @@ import { ethers } from 'hardhat'; import { expect } from 'chai'; -import { OwnerControlledContractDeployer } from '../../typechain-types'; +import { LinkedContractsFactory } from '../../typechain-types'; import { ERC725YDataKeys } from '../../constants.ts'; import { calculateProxiesAddresses } from './helpers'; @@ -14,11 +14,9 @@ describe('UniversalProfileDeployer', function () { const UniversalProfileInitFactory = await ethers.getContractFactory('UniversalProfileInit'); const universalProfileInit = await UniversalProfileInitFactory.deploy(); - const OwnerControlledContractDeployerFactory = await ethers.getContractFactory( - 'OwnerControlledContractDeployer', - ); + const LinkedContractsFactoryFactory = await ethers.getContractFactory('LinkedContractsFactory'); - const ownerControlledContractDeployer = await OwnerControlledContractDeployerFactory.deploy(); + const LinkedContractsFactory = await LinkedContractsFactoryFactory.deploy(); const UPDelegatorPostDeploymentManagerFactory = await ethers.getContractFactory( 'UniversalProfileInitPostDeploymentModule', @@ -28,7 +26,7 @@ describe('UniversalProfileDeployer', function () { const salt = ethers.utils.randomBytes(32); - const ownerControlledDeploymentInit: OwnerControlledContractDeployer.ControlledContractDeploymentInitStruct = + const primaryContractDeploymentInit: LinkedContractsFactory.PrimaryContractDeploymentInitStruct = { salt, fundingAmount: 0, @@ -38,13 +36,14 @@ describe('UniversalProfileDeployer', function () { ]), }; - const ownerDeploymentInit: OwnerControlledContractDeployer.OwnerContractDeploymentInitStruct = { - fundingAmount: 0, - implementationContract: keyManagerInit.address, - addControlledContractAddress: true, - initializationCalldata: '0xc4d66de8', - extraInitializationParams: '0x', - }; + const secondaryContractDeploymentInit: LinkedContractsFactory.SecondaryContractDeploymentInitStruct = + { + fundingAmount: 0, + implementationContract: keyManagerInit.address, + addPrimaryContractAddress: true, + initializationCalldata: '0xc4d66de8', + extraInitializationParams: '0x', + }; const allPermissionsSignerPermissionsKey = '0x4b80742de2bf82acb3630000' + allPermissionsSigner.address.slice(2); @@ -91,16 +90,16 @@ describe('UniversalProfileDeployer', function () { // get the address of the UP and the KeyManager contracts const [upAddress, keyManagerAddress] = - await ownerControlledContractDeployer.callStatic.deployERC1167Proxies( - ownerControlledDeploymentInit, - ownerDeploymentInit, + await LinkedContractsFactory.callStatic.deployERC1167Proxies( + primaryContractDeploymentInit, + secondaryContractDeploymentInit, upPostDeploymentModule.address, encodedBytes, ); - await ownerControlledContractDeployer.deployERC1167Proxies( - ownerControlledDeploymentInit, - ownerDeploymentInit, + await LinkedContractsFactory.deployERC1167Proxies( + primaryContractDeploymentInit, + secondaryContractDeploymentInit, upPostDeploymentModule.address, encodedBytes, ); @@ -112,23 +111,23 @@ describe('UniversalProfileDeployer', function () { const keyManagerProxyOwner = await keyManagerProxy.target(); const [expectedUpProxyAddress, expectedKeyManagerProxyAddress] = - await ownerControlledContractDeployer.computeERC1167Addresses( - ownerControlledDeploymentInit, - ownerDeploymentInit, + await LinkedContractsFactory.computeERC1167Addresses( + primaryContractDeploymentInit, + secondaryContractDeploymentInit, upPostDeploymentModule.address, encodedBytes, ); const [calculatedUpProxyAddress, calculatedKMProxyAddress] = await calculateProxiesAddresses( - ownerControlledDeploymentInit.salt, - ownerControlledDeploymentInit.implementationContract, - ownerDeploymentInit.implementationContract, - ownerDeploymentInit.initializationCalldata, - ownerDeploymentInit.addControlledContractAddress, - ownerDeploymentInit.extraInitializationParams, + primaryContractDeploymentInit.salt, + primaryContractDeploymentInit.implementationContract, + secondaryContractDeploymentInit.implementationContract, + secondaryContractDeploymentInit.initializationCalldata, + secondaryContractDeploymentInit.addPrimaryContractAddress, + secondaryContractDeploymentInit.extraInitializationParams, upPostDeploymentModule.address, encodedBytes, - ownerControlledContractDeployer.address, + LinkedContractsFactory.address, ); expect(upAddress).to.equal(expectedUpProxyAddress); From 1ca6352ae8f85645a7f80b1eb24d01c22e89fb39 Mon Sep 17 00:00:00 2001 From: Jean Cvllr <31145285+CJ42@users.noreply.github.com> Date: Tue, 8 Aug 2023 09:52:02 +0100 Subject: [PATCH 7/8] build: add LSP23 in artifacts (#662) --- hardhat.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/hardhat.config.ts b/hardhat.config.ts index 1e3944771..9d69635e0 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -150,6 +150,7 @@ const config: HardhatUserConfig = { // ------------------ 'Create2Factory', 'LSP16UniversalFactory', + 'LinkedContractsFactory', ], // Whether to include the TypeChain factories or not. // If this is enabled, you need to run the TypeChain files through the TypeScript compiler before shipping to the registry. From af376dd4b3f8251c1bd297ad5d622a6e697bc713 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Tue, 8 Aug 2023 12:56:08 +0100 Subject: [PATCH 8/8] chore(release): 0.11.0-rc.1 --- CHANGELOG.md | 11 +++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54c2a38c4..18690536c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [0.11.0-rc.1](https://github.com/lukso-network/lsp-smart-contracts/compare/v0.1...v0.11.0-rc.1) (2023-08-08) + +### ⚠ BREAKING CHANGES + +- refactor: rename LSP23 to LinkedContractsFactory (#658) + +### Bug Fixes + +- build: add LSP23 in artifacts (#662) +- failing tests for lsp23 + add LSP23 test suite in CI ([#657](https://github.com/lukso-network/lsp-smart-contracts/issues/657)) ([0e5d9bf](https://github.com/lukso-network/lsp-smart-contracts/commit/0e5d9bf7f9b212976f5c754b28f96b37a8039076)) + ## [0.11.0-rc.0](https://github.com/lukso-network/lsp-smart-contracts/compare/v0.10.3...v0.11.0-rc.0) (2023-08-04) ### ⚠ BREAKING CHANGES diff --git a/package-lock.json b/package-lock.json index 4aab040ca..b2bbdebf4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lukso/lsp-smart-contracts", - "version": "0.11.0-rc.0", + "version": "0.11.0-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@lukso/lsp-smart-contracts", - "version": "0.11.0-rc.0", + "version": "0.11.0-rc.1", "license": "Apache-2.0", "dependencies": { "@erc725/smart-contracts": "^5.1.0", diff --git a/package.json b/package.json index 94cf5f5c0..2ac205c53 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lukso/lsp-smart-contracts", - "version": "0.11.0-rc.0", + "version": "0.11.0-rc.1", "description": "The reference implementation for universal profiles smart contracts", "directories": { "test": "test"