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/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/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/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/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index 3a8254956..4ac83fafa 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -78,11 +78,11 @@ 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; - 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, 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..94cf5f5c0 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" @@ -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.test.ts b/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts new file mode 100644 index 000000000..7a808074a --- /dev/null +++ b/tests/LSP23MultiChainDeployment/LSP23MultiChainDeployment.test.ts @@ -0,0 +1,147 @@ +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, + fundingAmount: 0, + implementationContract: universalProfileInit.address, + initializationCalldata: universalProfileInit.interface.encodeFunctionData('initialize', [ + upPostDeploymentModule.address, + ]), + }; + + const ownerDeploymentInit: OwnerControlledContractDeployer.OwnerContractDeploymentInitStruct = { + fundingAmount: 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, + ownerDeploymentInit, + 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]; +}