diff --git a/.github/workflows/build-lint-test.yml b/.github/workflows/build-lint-test.yml index 4355625a2..57a184b8a 100644 --- a/.github/workflows/build-lint-test.yml +++ b/.github/workflows/build-lint-test.yml @@ -84,7 +84,6 @@ jobs: "lsp20", "lsp20init", "lsp23", - "universalfactory", "reentrancy", "reentrancyinit", "mocks", diff --git a/config/eslint-config-custom/README.md b/config/eslint-config-custom/README.md index 990f3ae44..3c7105373 100755 --- a/config/eslint-config-custom/README.md +++ b/config/eslint-config-custom/README.md @@ -1 +1 @@ -# `eslint-custom-config` \ No newline at end of file +# `eslint-custom-config` diff --git a/docs/contracts/LSP16UniversalFactory/LSP16UniversalFactory.md b/docs/contracts/LSP16UniversalFactory/LSP16UniversalFactory.md deleted file mode 100644 index e57e5fc17..000000000 --- a/docs/contracts/LSP16UniversalFactory/LSP16UniversalFactory.md +++ /dev/null @@ -1,435 +0,0 @@ - - - -# LSP16UniversalFactory - -:::info Standard Specifications - -[`LSP-16-UniversalFactory`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md) - -::: -:::info Solidity implementation - -[`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) - -::: - -> LSP16 Universal Factory - -Factory contract to deploy different types of contracts using the CREATE2 opcode standardized as LSP16 - -- UniversalFactory: https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-16-UniversalFactory.md The UniversalFactory will be deployed using Nick's Factory (0x4e59b44847b379578588920ca78fbf26c0b4956c) The deployed address can be found in the LSP16 specification. Please refer to the LSP16 Specification to obtain the exact creation bytecode and salt that should be used to produce the address of the UniversalFactory on different chains. This factory contract is designed to deploy contracts at the same address on multiple chains. The UniversalFactory can deploy 2 types of contracts: - -- non-initializable (normal deployment) - -- initializable (external call after deployment, e.g: proxy contracts) The `providedSalt` parameter given by the deployer is not used directly as the salt by the CREATE2 opcode. Instead, it is used along with these parameters: - -- `initializable` boolean - -- `initializeCalldata` (when the contract is initializable and `initializable` is set to `true`). These three parameters are concatenated together and hashed to generate the final salt for CREATE2. See [`generateSalt`](#generatesalt) function for more details. The constructor and `initializeCalldata` SHOULD NOT include any network-specific parameters (e.g: chain-id, a local token contract address), otherwise the deployed contract will not be recreated at the same address across different networks, thus defeating the purpose of the UniversalFactory. One way to solve this problem is to set an EOA owner in the `initializeCalldata`/constructor that can later call functions that set these parameters as variables in the contract. The UniversalFactory must be deployed at the same address on different chains to successfully deploy contracts at the same address across different chains. - -## Public Methods - -Public methods are accessible externally from users, allowing interaction with this function from dApps or other smart contracts. -When marked as 'public', a method can be called both externally and internally, on the other hand, when marked as 'external', a method can only be called externally. - -### computeAddress - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#computeaddress) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Function signature: `computeAddress(bytes32,bytes32,bool,bytes)` -- Function selector: `0x3b315680` - -::: - -```solidity -function computeAddress( - bytes32 creationBytecodeHash, - bytes32 providedSalt, - bool initializable, - bytes initializeCalldata -) external view returns (address); -``` - -Computes the address of a contract to be deployed using CREATE2, based on the input parameters. Any change in one of these parameters will result in a different address. When the `initializable` boolean is set to `false`, `initializeCalldata` will not affect the function output. - -#### Parameters - -| Name | Type | Description | -| ---------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `creationBytecodeHash` | `bytes32` | The keccak256 hash of the creation bytecode to be deployed | -| `providedSalt` | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment | -| `initializable` | `bool` | A boolean that indicates whether an external call should be made to initialize the contract after deployment | -| `initializeCalldata` | `bytes` | The calldata to be executed on the created contract if `initializable` is set to `true` | - -#### Returns - -| Name | Type | Description | -| ---- | :-------: | ----------------------------------------------- | -| `0` | `address` | The address where the contract will be deployed | - -
- -### computeERC1167Address - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#computeerc1167address) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Function signature: `computeERC1167Address(address,bytes32,bool,bytes)` -- Function selector: `0xe888edcb` - -::: - -```solidity -function computeERC1167Address( - address implementationContract, - bytes32 providedSalt, - bool initializable, - bytes initializeCalldata -) external view returns (address); -``` - -Computes the address of an ERC1167 proxy contract based on the input parameters. Any change in one of these parameters will result in a different address. When the `initializable` boolean is set to `false`, `initializeCalldata` will not affect the function output. - -#### Parameters - -| Name | Type | Description | -| ------------------------ | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `implementationContract` | `address` | The contract to create a clone of according to ERC1167 | -| `providedSalt` | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment | -| `initializable` | `bool` | A boolean that indicates whether an external call should be made to initialize the proxy contract after deployment | -| `initializeCalldata` | `bytes` | The calldata to be executed on the created contract if `initializable` is set to `true` | - -#### Returns - -| Name | Type | Description | -| ---- | :-------: | ------------------------------------------------------------- | -| `0` | `address` | The address where the ERC1167 proxy contract will be deployed | - -
- -### deployCreate2 - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#deploycreate2) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Function signature: `deployCreate2(bytes,bytes32)` -- Function selector: `0x26736355` - -::: - -```solidity -function deployCreate2( - bytes creationBytecode, - bytes32 providedSalt -) external payable returns (address); -``` - -_Deploys a smart contract._ - -Deploys a contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the [`computeAddress`](#computeaddress) function. This function deploys contracts without initialization (external call after deployment). The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(false, providedSalt))`. See [`generateSalt`](#generatesalt) function for more details. Using the same `creationBytecode` and `providedSalt` multiple times will revert, as the contract cannot be deployed twice at the same address. If the constructor of the contract to deploy is payable, value can be sent to this function to fund the created contract. However, sending value to this function while the constructor is not payable will result in a revert. - -#### Parameters - -| Name | Type | Description | -| ------------------ | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `creationBytecode` | `bytes` | The creation bytecode of the contract to be deployed | -| `providedSalt` | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment | - -#### Returns - -| Name | Type | Description | -| ---- | :-------: | ------------------------------------ | -| `0` | `address` | The address of the deployed contract | - -
- -### deployCreate2AndInitialize - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#deploycreate2andinitialize) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Function signature: `deployCreate2AndInitialize(bytes,bytes32,bytes,uint256,uint256)` -- Function selector: `0xcdbd473a` - -::: - -```solidity -function deployCreate2AndInitialize( - bytes creationBytecode, - bytes32 providedSalt, - bytes initializeCalldata, - uint256 constructorMsgValue, - uint256 initializeCalldataMsgValue -) external payable returns (address); -``` - -_Deploys a smart contract and initializes it._ - -Deploys a contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the [`computeAddress`](#computeaddress) function. This function deploys contracts with initialization (external call after deployment). The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(true, initializeCalldata, providedSalt))`. See [`generateSalt`](#generatesalt) function for more details. Using the same `creationBytecode`, `providedSalt` and `initializeCalldata` multiple times will revert, as the contract cannot be deployed twice at the same address. If the constructor or the initialize function of the contract to deploy is payable, value can be sent along with the deployment/initialization to fund the created contract. However, sending value to this function while the constructor/initialize function is not payable will result in a revert. Will revert if the `msg.value` sent to the function is not equal to the sum of `constructorMsgValue` and `initializeCalldataMsgValue`. - -#### Parameters - -| Name | Type | Description | -| ---------------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `creationBytecode` | `bytes` | The creation bytecode of the contract to be deployed | -| `providedSalt` | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment | -| `initializeCalldata` | `bytes` | The calldata to be executed on the created contract | -| `constructorMsgValue` | `uint256` | The value sent to the contract during deployment | -| `initializeCalldataMsgValue` | `uint256` | The value sent to the contract during initialization | - -#### Returns - -| Name | Type | Description | -| ---- | :-------: | ------------------------------------ | -| `0` | `address` | The address of the deployed contract | - -
- -### deployERC1167Proxy - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#deployerc1167proxy) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Function signature: `deployERC1167Proxy(address,bytes32)` -- Function selector: `0x49d8abed` - -::: - -```solidity -function deployERC1167Proxy( - address implementationContract, - bytes32 providedSalt -) external nonpayable returns (address); -``` - -_Deploys a proxy smart contract._ - -Deploys an ERC1167 minimal proxy contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the [`computeERC1167Address`](#computeerc1167address) function. This function deploys contracts without initialization (external call after deployment). The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(false, providedSalt))`. See [`generateSalt`](#generatesalt) function for more details. Using the same `implementationContract` and `providedSalt` multiple times will revert, as the contract cannot be deployed twice at the same address. Sending value to the contract created is not possible since the constructor of the ERC1167 minimal proxy is not payable. - -#### Parameters - -| Name | Type | Description | -| ------------------------ | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `implementationContract` | `address` | The contract address to use as the base implementation behind the proxy that will be deployed | -| `providedSalt` | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment | - -#### Returns - -| Name | Type | Description | -| ---- | :-------: | ----------------------------------------- | -| `0` | `address` | The address of the minimal proxy deployed | - -
- -### deployERC1167ProxyAndInitialize - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#deployerc1167proxyandinitialize) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Function signature: `deployERC1167ProxyAndInitialize(address,bytes32,bytes)` -- Function selector: `0x5340165f` - -::: - -```solidity -function deployERC1167ProxyAndInitialize( - address implementationContract, - bytes32 providedSalt, - bytes initializeCalldata -) external payable returns (address); -``` - -_Deploys a proxy smart contract and initializes it._ - -Deploys an ERC1167 minimal proxy contract using the CREATE2 opcode. The address where the contract will be deployed can be known in advance via the [`computeERC1167Address`](#computeerc1167address) function. This function deploys contracts with initialization (external call after deployment). The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is hashed with keccak256: `keccak256(abi.encodePacked(true, initializeCalldata, providedSalt))`. See [`generateSalt`](#generatesalt) function for more details. Using the same `implementationContract`, `providedSalt` and `initializeCalldata` multiple times will revert, as the contract cannot be deployed twice at the same address. If the initialize function of the contract to deploy is payable, value can be sent along to fund the created contract while initializing. However, sending value to this function while the initialize function is not payable will result in a revert. - -#### Parameters - -| Name | Type | Description | -| ------------------------ | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `implementationContract` | `address` | The contract address to use as the base implementation behind the proxy that will be deployed | -| `providedSalt` | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment | -| `initializeCalldata` | `bytes` | The calldata to be executed on the created contract | - -#### Returns - -| Name | Type | Description | -| ---- | :-------: | ----------------------------------------- | -| `0` | `address` | The address of the minimal proxy deployed | - -
- -### generateSalt - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#generatesalt) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Function signature: `generateSalt(bytes32,bool,bytes)` -- Function selector: `0x1a17ccbf` - -::: - -```solidity -function generateSalt( - bytes32 providedSalt, - bool initializable, - bytes initializeCalldata -) external pure returns (bytes32); -``` - -Generates the salt used to deploy the contract by hashing the following parameters (concatenated together) with keccak256: - -1. the `providedSalt` - -2. the `initializable` boolean - -3. the `initializeCalldata`, only if the contract is initializable (the `initializable` boolean is set to `true`) - -- The `providedSalt` parameter is not used directly as the salt by the CREATE2 opcode. Instead, it is used along with these parameters: - -1. `initializable` boolean - -2. `initializeCalldata` (when the contract is initializable and `initializable` is set to `true`). - -- This approach ensures that in order to reproduce an initializable contract at the same address on another chain, not only the `providedSalt` is required to be the same, but also the initialize parameters within the `initializeCalldata` must also be the same. This maintains consistent deployment behaviour. Users are required to initialize contracts with the same parameters across different chains to ensure contracts are deployed at the same address across different chains. - -1. Example (for initializable contracts) - -- For an existing contract A on chain 1 owned by X, to replicate the same contract at the same address with the same owner X on chain 2, the salt used to generate the address should include the initializeCalldata that assigns X as the owner of contract A. - -- For instance, if another user, Y, tries to deploy the contract at the same address on chain 2 using the same providedSalt, but with a different initializeCalldata to make Y the owner instead of X, the generated address would be different, preventing Y from deploying the contract with different ownership at the same address. - -- However, for non-initializable contracts, if the constructor has arguments that specify the deployment behavior, they will be included in the creation bytecode. Any change in the constructor arguments will lead to a different contract's creation bytecode which will result in a different address on other chains. - -2. Example (for non-initializable contracts) - -- If a contract is deployed with specific constructor arguments on chain 1, these arguments are embedded within the creation bytecode. For instance, if contract B is deployed with a specific `tokenName` and `tokenSymbol` on chain 1, and a user wants to deploy the same contract with the same `tokenName` and `tokenSymbol` on chain 2, they must use the same constructor arguments to produce the same creation bytecode. This ensures that the same deployment behaviour is maintained across different chains, as long as the same creation bytecode is used. - -- If another user Z, tries to deploy the same contract B at the same address on chain 2 using the same `providedSalt` but different constructor arguments (a different `tokenName` and/or `tokenSymbol`), the generated address will be different. This prevents user Z from deploying the contract with different constructor arguments at the same address on chain 2. - -- The providedSalt was hashed to produce the salt used by CREATE2 opcode to prevent users from deploying initializable contracts using non-initializable functions such as [`deployCreate2`](#deploycreate2) without having the initialization call. - -- In other words, if the providedSalt was not hashed and was used as it is as the salt by the CREATE2 opcode, malicious users can check the generated salt used for the already deployed initializable contract on chain 1, and deploy the contract from [`deployCreate2`](#deploycreate2) function on chain 2, with passing the generated salt of the deployed contract as providedSalt that will produce the same address but without the initialization, where the malicious user can initialize after. - -#### Parameters - -| Name | Type | Description | -| -------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `providedSalt` | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment | -| `initializable` | `bool` | The Boolean that specifies if the contract must be initialized or not | -| `initializeCalldata` | `bytes` | The calldata to be executed on the created contract if `initializable` is set to `true` | - -#### Returns - -| Name | Type | Description | -| ---- | :-------: | ------------------------------------------------------------ | -| `0` | `bytes32` | The generated salt which will be used for CREATE2 deployment | - -
- -## Internal Methods - -Any method labeled as `internal` serves as utility function within the contract. They can be used when writing solidity contracts that inherit from this contract. These methods can be extended or modified by overriding their internal behavior to suit specific needs. - -Internal functions cannot be called externally, whether from other smart contracts, dApp interfaces, or backend services. Their restricted accessibility ensures that they remain exclusively available within the context of the current contract, promoting controlled and encapsulated usage of these internal utilities. - -### \_verifyCallResult - -```solidity -function _verifyCallResult(bool success, bytes returndata) internal pure; -``` - -Verifies that the contract created was initialized correctly. -Bubble the revert reason if present, revert with `ContractInitializationFailed` otherwise. - -
- -## Events - -### ContractCreated - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#contractcreated) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Event signature: `ContractCreated(address,bytes32,bytes32,bool,bytes)` -- Event topic hash: `0x8872a323d65599f01bf90dc61c94b4e0cc8e2347d6af4122fccc3e112ee34a84` - -::: - -```solidity -event ContractCreated( - address indexed createdContract, - bytes32 indexed providedSalt, - bytes32 generatedSalt, - bool indexed initialized, - bytes initializeCalldata -); -``` - -_Contract created. Contract address: `createdContract`._ - -Emitted whenever a contract is created. - -#### Parameters - -| Name | Type | Description | -| ------------------------------- | :-------: | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| `createdContract` **`indexed`** | `address` | The address of the contract created. | -| `providedSalt` **`indexed`** | `bytes32` | The salt provided by the deployer, which will be used to generate the final salt that will be used by the `CREATE2` opcode for contract deployment. | -| `generatedSalt` | `bytes32` | The salt used by the `CREATE2` opcode for contract deployment. | -| `initialized` **`indexed`** | `bool` | The Boolean that specifies if the contract must be initialized or not. | -| `initializeCalldata` | `bytes` | The bytes provided as initializeCalldata (Empty string when `initialized` is set to false). | - -
- -## Errors - -### ContractInitializationFailed - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#contractinitializationfailed) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Error signature: `ContractInitializationFailed()` -- Error hash: `0xc1ee8543` - -::: - -```solidity -error ContractInitializationFailed(); -``` - -_Couldn't initialize the contract._ - -Reverts when there is no revert reason bubbled up by the created contract when initializing - -
- -### InvalidValueSum - -:::note References - -- Specification details: [**LSP-16-UniversalFactory**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-16-UniversalFactory.md#invalidvaluesum) -- Solidity implementation: [`LSP16UniversalFactory.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol) -- Error signature: `InvalidValueSum()` -- Error hash: `0x2fd9ca91` - -::: - -```solidity -error InvalidValueSum(); -``` - -Reverts when `msg.value` sent to [`deployCreate2AndInitialize(..)`](#deploycreate2andinitialize) function is not equal to the sum of the `initializeCalldataMsgValue` and `constructorMsgValue` - -
diff --git a/dodoc/config.ts b/dodoc/config.ts index b5ecb6c59..ff476991a 100644 --- a/dodoc/config.ts +++ b/dodoc/config.ts @@ -12,7 +12,7 @@ export const dodocConfig = { 'contracts/LSP9Vault/LSP9Vault.sol', 'contracts/LSP11BasicSocialRecovery/LSP11BasicSocialRecovery.sol', 'contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol', - 'contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol', + 'lsp16/contracts/LSP16UniversalFactory.sol', 'contracts/LSP17ContractExtension/LSP17Extendable.sol', 'contracts/LSP17ContractExtension/LSP17Extension.sol', 'contracts/LSP17Extensions/Extension4337.sol', diff --git a/foundry.toml b/foundry.toml index 044d6b7b8..61ac07c73 100644 --- a/foundry.toml +++ b/foundry.toml @@ -15,3 +15,8 @@ max_test_rejects = 200_000 src = 'packages/LSP2ERC725YJSONSchema/contracts' test = 'packages/LSP2ERC725YJSONSchema/foundry' out = 'packages/LSP2ERC725YJSONSchema/contracts/foundry_artifacts' + +[profile.lsp16] +src = 'packages/LSP16UniversalFactory/contracts' +test = 'packages/LSP16UniversalFactory/foundry' +out = 'packages/LSP16UniversalFactory/contracts/foundry_artifacts' diff --git a/hardhat.config.ts b/hardhat.config.ts index 63ab5b9e4..a60fcdc49 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -155,7 +155,6 @@ const config: HardhatUserConfig = { // Tools // ------------------ 'Create2Factory', - 'LSP16UniversalFactory', 'LSP23LinkedContractsFactory', ], // Whether to include the TypeChain factories or not. diff --git a/package-lock.json b/package-lock.json index 7759fa29b..b3bb80467 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,10 +17,11 @@ "@erc725/smart-contracts": "^7.0.0", "@openzeppelin/contracts": "^4.9.2", "@openzeppelin/contracts-upgradeable": "^4.9.2", + "lsp16": "*", "lsp2": "*", - "lsp4": "*", "lsp20": "*", "lsp25": "*", + "lsp4": "*", "solidity-bytes-utils": "0.8.0" }, "devDependencies": { @@ -4336,9 +4337,9 @@ "peer": true }, "node_modules/@types/node": { - "version": "20.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.7.tgz", - "integrity": "sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==", + "version": "20.11.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz", + "integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==", "dependencies": { "undici-types": "~5.26.4" } @@ -5733,9 +5734,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", "funding": [ { "type": "opencollective", @@ -5751,8 +5752,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -6042,9 +6043,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001580", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz", - "integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==", + "version": "1.0.30001581", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz", + "integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==", "funding": [ { "type": "opencollective", @@ -7490,9 +7491,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.647", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.647.tgz", - "integrity": "sha512-Z/fTNGwc45WrYQhPaEcz5tAJuZZ8G7S/DBnhS6Kgp4BxnS40Z/HqlJ0hHg3Z79IGVzuVartIlTcjw/cQbPLgOw==" + "version": "1.4.648", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz", + "integrity": "sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -9394,9 +9395,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", + "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", "dependencies": { "reusify": "^1.0.4" } @@ -12909,11 +12910,12 @@ } }, "node_modules/level": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", - "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-8.0.1.tgz", + "integrity": "sha512-oPBGkheysuw7DmzFQYyFe8NAia5jFLAgEnkgWnK3OXAuJr8qFT+xBQIwokAZPME2bhPFzS8hlYcL16m8UZrtwQ==", "dev": true, "dependencies": { + "abstract-level": "^1.0.4", "browser-level": "^1.0.1", "classic-level": "^1.2.0" }, @@ -13320,6 +13322,10 @@ "yallist": "^3.0.2" } }, + "node_modules/lsp16": { + "resolved": "packages/LSP16UniversalFactory", + "link": true + }, "node_modules/lsp2": { "resolved": "packages/LSP2ERC725YJSONSchema", "link": true @@ -18829,14 +18835,14 @@ "dev": true }, "node_modules/solidity-coverage": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.5.tgz", - "integrity": "sha512-6C6N6OV2O8FQA0FWA95FdzVH+L16HU94iFgg5wAFZ29UpLFkgNI/DRR2HotG1bC0F4gAc/OMs2BJI44Q/DYlKQ==", + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.6.tgz", + "integrity": "sha512-vV03mA/0nNMskOdVwNarUcqk0N/aYdelxAbf6RZ5l84FcYHbqDTr2JXyeYMp4bT48qHtAQjnKrygW1FrECyWNw==", "dev": true, "peer": true, "dependencies": { "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.16.0", + "@solidity-parser/parser": "^0.18.0", "chalk": "^2.4.2", "death": "^1.1.0", "detect-port": "^1.3.0", @@ -18847,7 +18853,7 @@ "globby": "^10.0.1", "jsonschema": "^1.2.4", "lodash": "^4.17.15", - "mocha": "10.2.0", + "mocha": "^10.2.0", "node-emoji": "^1.10.0", "pify": "^4.0.1", "recursive-readdir": "^2.2.2", @@ -18864,14 +18870,11 @@ } }, "node_modules/solidity-coverage/node_modules/@solidity-parser/parser": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.2.tgz", - "integrity": "sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", "dev": true, - "peer": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } + "peer": true }, "node_modules/solidity-coverage/node_modules/fs-extra": { "version": "8.1.0", @@ -23576,6 +23579,50 @@ "ethers": "^5.7.0" } }, + "packages/LSP16UniversalFactory": { + "name": "lsp16", + "version": "0.12.1", + "license": "Apache-2.0", + "dependencies": { + "@openzeppelin/contracts": "^4.9.2" + }, + "devDependencies": { + "@erc725/smart-contracts": "7.0.0", + "@nomicfoundation/hardhat-toolbox": "^2.0.2", + "@openzeppelin/contracts-upgradeable": "4.9.3", + "@typechain/ethers-v5": "^10.2.0", + "dotenv": "^16.0.3", + "eslint-config-custom": "*", + "ethers": "^5.7.2", + "hardhat": "^2.13.0", + "hardhat-contract-sizer": "^2.8.0", + "hardhat-deploy": "^0.11.25", + "hardhat-gas-reporter": "^1.0.9", + "hardhat-packager": "^1.4.2", + "solhint": "^3.3.6", + "ts-node": "^10.2.0", + "tsconfig": "*", + "typechain": "^8.0.0", + "typescript": "^4.9.5" + } + }, + "packages/LSP16UniversalFactory/node_modules/@openzeppelin/contracts-upgradeable": { + "version": "4.9.3", + "dev": true, + "license": "MIT" + }, + "packages/LSP16UniversalFactory/node_modules/typescript": { + "version": "4.9.5", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "packages/LSP20CallVerification": { "name": "lsp20", "version": "0.12.1", @@ -23674,6 +23721,7 @@ } }, "packages/LSP4DigitalAssetMetadata": { + "name": "lsp4", "version": "0.12.1", "license": "Apache-2.0", "dependencies": { @@ -23699,9 +23747,8 @@ }, "packages/LSP4DigitalAssetMetadata/node_modules/typescript": { "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26693,9 +26740,9 @@ "peer": true }, "@types/node": { - "version": "20.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.7.tgz", - "integrity": "sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==", + "version": "20.11.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz", + "integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==", "requires": { "undici-types": "~5.26.4" } @@ -27745,12 +27792,12 @@ } }, "browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", "requires": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" } @@ -27973,9 +28020,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001580", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz", - "integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==" + "version": "1.0.30001581", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz", + "integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==" }, "case": { "version": "1.6.3", @@ -29085,9 +29132,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.647", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.647.tgz", - "integrity": "sha512-Z/fTNGwc45WrYQhPaEcz5tAJuZZ8G7S/DBnhS6Kgp4BxnS40Z/HqlJ0hHg3Z79IGVzuVartIlTcjw/cQbPLgOw==" + "version": "1.4.648", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz", + "integrity": "sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==" }, "elliptic": { "version": "6.5.4", @@ -30707,9 +30754,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", + "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", "requires": { "reusify": "^1.0.4" } @@ -33426,11 +33473,12 @@ } }, "level": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-8.0.0.tgz", - "integrity": "sha512-ypf0jjAk2BWI33yzEaaotpq7fkOPALKAgDBxggO6Q9HGX2MRXn0wbP1Jn/tJv1gtL867+YOjOB49WaUF3UoJNQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/level/-/level-8.0.1.tgz", + "integrity": "sha512-oPBGkheysuw7DmzFQYyFe8NAia5jFLAgEnkgWnK3OXAuJr8qFT+xBQIwokAZPME2bhPFzS8hlYcL16m8UZrtwQ==", "dev": true, "requires": { + "abstract-level": "^1.0.4", "browser-level": "^1.0.1", "classic-level": "^1.2.0" } @@ -33783,6 +33831,39 @@ "yallist": "^3.0.2" } }, + "lsp16": { + "version": "file:packages/LSP16UniversalFactory", + "requires": { + "@erc725/smart-contracts": "7.0.0", + "@nomicfoundation/hardhat-toolbox": "^2.0.2", + "@openzeppelin/contracts": "^4.9.2", + "@openzeppelin/contracts-upgradeable": "4.9.3", + "@typechain/ethers-v5": "^10.2.0", + "dotenv": "^16.0.3", + "eslint-config-custom": "*", + "ethers": "^5.7.2", + "hardhat": "^2.13.0", + "hardhat-contract-sizer": "^2.8.0", + "hardhat-deploy": "^0.11.25", + "hardhat-gas-reporter": "^1.0.9", + "hardhat-packager": "^1.4.2", + "solhint": "^3.3.6", + "ts-node": "^10.2.0", + "tsconfig": "*", + "typechain": "^8.0.0", + "typescript": "^4.9.5" + }, + "dependencies": { + "@openzeppelin/contracts-upgradeable": { + "version": "4.9.3", + "dev": true + }, + "typescript": { + "version": "4.9.5", + "dev": true + } + } + }, "lsp2": { "version": "file:packages/LSP2ERC725YJSONSchema", "requires": { @@ -33881,8 +33962,6 @@ "dependencies": { "typescript": { "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true } } @@ -37903,14 +37982,14 @@ "dev": true }, "solidity-coverage": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.5.tgz", - "integrity": "sha512-6C6N6OV2O8FQA0FWA95FdzVH+L16HU94iFgg5wAFZ29UpLFkgNI/DRR2HotG1bC0F4gAc/OMs2BJI44Q/DYlKQ==", + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.6.tgz", + "integrity": "sha512-vV03mA/0nNMskOdVwNarUcqk0N/aYdelxAbf6RZ5l84FcYHbqDTr2JXyeYMp4bT48qHtAQjnKrygW1FrECyWNw==", "dev": true, "peer": true, "requires": { "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.16.0", + "@solidity-parser/parser": "^0.18.0", "chalk": "^2.4.2", "death": "^1.1.0", "detect-port": "^1.3.0", @@ -37921,7 +38000,7 @@ "globby": "^10.0.1", "jsonschema": "^1.2.4", "lodash": "^4.17.15", - "mocha": "10.2.0", + "mocha": "^10.2.0", "node-emoji": "^1.10.0", "pify": "^4.0.1", "recursive-readdir": "^2.2.2", @@ -37932,14 +38011,11 @@ }, "dependencies": { "@solidity-parser/parser": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.16.2.tgz", - "integrity": "sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", + "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", "dev": true, - "peer": true, - "requires": { - "antlr4ts": "^0.5.0-alpha.4" - } + "peer": true }, "fs-extra": { "version": "8.1.0", diff --git a/package.json b/package.json index 9183f580c..2829fb74b 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,6 @@ "test:lsp20init": "hardhat test --no-compile tests/LSP20CallVerification/LSP6/LSP20WithLSP6Init.test.ts", "test:lsp23": "hardhat test --no-compile tests/LSP23LinkedContractsDeployment/LSP23LinkedContractsDeployment.test.ts", "test:lsp25": "hardhat test --no-compile tests/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.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", "test:foundry": "forge test --no-match-test Skip -vvv --gas-report > gasreport.ansi", @@ -114,6 +113,7 @@ "solidity-bytes-utils": "0.8.0", "lsp2": "*", "lsp4": "*", + "lsp16": "*", "lsp20": "*", "lsp25": "*" }, diff --git a/packages/LSP16UniversalFactory/.eslintrc.js b/packages/LSP16UniversalFactory/.eslintrc.js new file mode 100644 index 000000000..03ee7431b --- /dev/null +++ b/packages/LSP16UniversalFactory/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ['custom'], +}; diff --git a/packages/LSP16UniversalFactory/.gitmodules b/packages/LSP16UniversalFactory/.gitmodules new file mode 100644 index 000000000..3dce69ce0 --- /dev/null +++ b/packages/LSP16UniversalFactory/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = ../../lib/forge-std + url = https://github.com/foundry-rs/forge-std.git diff --git a/packages/LSP16UniversalFactory/.solhint.json b/packages/LSP16UniversalFactory/.solhint.json new file mode 100644 index 000000000..26e01c48a --- /dev/null +++ b/packages/LSP16UniversalFactory/.solhint.json @@ -0,0 +1,25 @@ +{ + "extends": "solhint:recommended", + "rules": { + "avoid-sha3": "error", + "avoid-suicide": "error", + "avoid-throw": "error", + "avoid-tx-origin": "error", + "check-send-result": "error", + "compiler-version": ["error", "^0.8.0"], + "func-visibility": ["error", { "ignoreConstructors": true }], + "not-rely-on-block-hash": "error", + "not-rely-on-time": "error", + "reentrancy": "error", + "constructor-syntax": "error", + "private-vars-leading-underscore": ["error", { "strict": false }], + "imports-on-top": "error", + "visibility-modifier-order": "error", + "no-unused-import": "error", + "no-global-import": "error", + "reason-string": ["warn", { "maxLength": 120 }], + "avoid-low-level-calls": "off", + "no-empty-blocks": ["error", { "ignoreConstructors": true }], + "custom-errors": "off" + } +} diff --git a/packages/LSP16UniversalFactory/README.md b/packages/LSP16UniversalFactory/README.md new file mode 100644 index 000000000..98655b90c --- /dev/null +++ b/packages/LSP16UniversalFactory/README.md @@ -0,0 +1,3 @@ +# LSP16 Universal Factory + +Package for the [LSP16-UniversalFactory](https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-16-UniversalFactory.md) standard, contains a contract that allows deploying contracts on multiple chains with the same address. diff --git a/contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol b/packages/LSP16UniversalFactory/contracts/LSP16UniversalFactory.sol similarity index 100% rename from contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol rename to packages/LSP16UniversalFactory/contracts/LSP16UniversalFactory.sol diff --git a/packages/LSP16UniversalFactory/contracts/Mocks/Account.sol b/packages/LSP16UniversalFactory/contracts/Mocks/Account.sol new file mode 100644 index 000000000..2f937bae1 --- /dev/null +++ b/packages/LSP16UniversalFactory/contracts/Mocks/Account.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.13; + +import {ERC725} from "@erc725/smart-contracts/contracts/ERC725.sol"; + +contract Account is ERC725 { + // solhint-disable-next-line no-empty-blocks + constructor(address contractOwner) ERC725(contractOwner) {} +} diff --git a/packages/LSP16UniversalFactory/contracts/Mocks/AccountInit.sol b/packages/LSP16UniversalFactory/contracts/Mocks/AccountInit.sol new file mode 100644 index 000000000..2711ba0a6 --- /dev/null +++ b/packages/LSP16UniversalFactory/contracts/Mocks/AccountInit.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.13; + +import { + ERC725InitAbstract +} from "@erc725/smart-contracts/contracts/ERC725InitAbstract.sol"; + +// solhint-disable-next-line no-empty-blocks +contract AccountInit is ERC725InitAbstract { + function initialize(address newOwner) public virtual initializer { + ERC725InitAbstract._initialize(newOwner); + } +} diff --git a/packages/LSP16UniversalFactory/contracts/Mocks/ContractNoConstructor.sol b/packages/LSP16UniversalFactory/contracts/Mocks/ContractNoConstructor.sol new file mode 100644 index 000000000..77f796fe7 --- /dev/null +++ b/packages/LSP16UniversalFactory/contracts/Mocks/ContractNoConstructor.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +contract ContractNoConstructor { + uint256 private _number = 5; + + function getNumber() public view returns (uint256) { + return _number; + } + + function setNumber(uint256 newNumber) public { + _number = newNumber; + } +} diff --git a/packages/LSP16UniversalFactory/contracts/Mocks/FallbackInitializer.sol b/packages/LSP16UniversalFactory/contracts/Mocks/FallbackInitializer.sol new file mode 100644 index 000000000..c74031b9a --- /dev/null +++ b/packages/LSP16UniversalFactory/contracts/Mocks/FallbackInitializer.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +/** + * @dev sample contract used for testing + */ +contract FallbackInitializer { + address public caller; + + receive() external payable { + _initialize(); + } + + fallback() external payable { + _initialize(); + } + + function _initialize() internal { + caller = msg.sender; + } +} diff --git a/contracts/Mocks/ImplementationTester.sol b/packages/LSP16UniversalFactory/contracts/Mocks/ImplementationTester.sol similarity index 100% rename from contracts/Mocks/ImplementationTester.sol rename to packages/LSP16UniversalFactory/contracts/Mocks/ImplementationTester.sol diff --git a/packages/LSP16UniversalFactory/contracts/Mocks/NonPayableContract.sol b/packages/LSP16UniversalFactory/contracts/Mocks/NonPayableContract.sol new file mode 100644 index 000000000..8899a127e --- /dev/null +++ b/packages/LSP16UniversalFactory/contracts/Mocks/NonPayableContract.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.13; + +contract NonPayableContract { + address private _owner; + + constructor(address newOwner) { + _owner = newOwner; + } + + function getOwner() public view returns (address) { + return _owner; + } +} diff --git a/contracts/Mocks/PayableContract.sol b/packages/LSP16UniversalFactory/contracts/Mocks/PayableContract.sol similarity index 100% rename from contracts/Mocks/PayableContract.sol rename to packages/LSP16UniversalFactory/contracts/Mocks/PayableContract.sol diff --git a/contracts/LSP16UniversalFactory/README.md b/packages/LSP16UniversalFactory/contracts/README.md similarity index 100% rename from contracts/LSP16UniversalFactory/README.md rename to packages/LSP16UniversalFactory/contracts/README.md diff --git a/packages/LSP16UniversalFactory/foundry.toml b/packages/LSP16UniversalFactory/foundry.toml new file mode 100644 index 000000000..aac84236b --- /dev/null +++ b/packages/LSP16UniversalFactory/foundry.toml @@ -0,0 +1,10 @@ +[profile.default] +src = 'contracts' +out = 'out' +libs = ['node_modules','lib'] +cache_path = 'forge-cache' +test = 'foundry' +solc_version = "0.8.17" +[fuzz] +runs = 10_000 +max_test_rejects = 200_000 diff --git a/tests/foundry/LSP16UniversalFactory/LSP16UniversalProfile.t.sol b/packages/LSP16UniversalFactory/foundry/LSP16UniversalFactory.t.sol similarity index 91% rename from tests/foundry/LSP16UniversalFactory/LSP16UniversalProfile.t.sol rename to packages/LSP16UniversalFactory/foundry/LSP16UniversalFactory.t.sol index 48d4c397b..4a25dfe71 100644 --- a/tests/foundry/LSP16UniversalFactory/LSP16UniversalProfile.t.sol +++ b/packages/LSP16UniversalFactory/foundry/LSP16UniversalFactory.t.sol @@ -6,18 +6,23 @@ import "forge-std/console.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; -import "../../../contracts/LSP16UniversalFactory/LSP16UniversalFactory.sol"; -import "../../../contracts/Mocks/NonPayableFallback.sol"; -import "../../../contracts/Mocks/FallbackInitializer.sol"; -import "../../../contracts/LSP0ERC725Account/LSP0ERC725Account.sol"; -import "../../../contracts/LSP0ERC725Account/LSP0ERC725AccountInit.sol"; +import {LSP16UniversalFactory} from "../contracts/LSP16UniversalFactory.sol"; +import { + NonPayableFallback +} from "../../../contracts/Mocks/NonPayableFallback.sol"; +import { + FallbackInitializer +} from "../../../contracts/Mocks/FallbackInitializer.sol"; +import {Account} from "../contracts/Mocks/Account.sol"; +import {AccountInit} from "../contracts/Mocks/AccountInit.sol"; contract LSP16UniversalProfileTest is Test { LSP16UniversalFactory public lsp16; NonPayableFallback public nonPayableFallbackContract; FallbackInitializer public fallbackInitializer; - LSP0ERC725Account public lsp0; - LSP0ERC725AccountInit public lsp0Init; + + Account public account; + AccountInit public accountInit; bytes public nonPayableFallbackBytecode = type(NonPayableFallback).creationCode; @@ -35,8 +40,9 @@ contract LSP16UniversalProfileTest is Test { nonPayableFallbackContract = new NonPayableFallback(); fallbackInitializer = new FallbackInitializer(); - lsp0Init = new LSP0ERC725AccountInit(); - lsp0 = new LSP0ERC725Account(address(20)); + + account = new Account(address(20)); + accountInit = new AccountInit(); uniqueInitializableSalt = lsp16.generateSalt( randomBytes32ForSalt, @@ -103,13 +109,13 @@ contract LSP16UniversalProfileTest is Test { assert(salt != uniqueInitializableSalt); } - function testdeployERC1167ProxyWithUPInit() public { + function testdeployERC1167ProxyWithAccountInit() public { bytes32 salt = lsp16.generateSalt(bytes32(++testCounter), false, ""); (bool success, bytes memory returnData) = address(lsp16).call( abi.encodeWithSignature( "deployERC1167Proxy(address,bytes32)", - address(lsp0Init), + address(accountInit), salt ) ); @@ -120,7 +126,7 @@ contract LSP16UniversalProfileTest is Test { ); } - function testdeployERC1167ProxyAndInitializeShouldNotKeepValueWithUPInit( + function testdeployERC1167ProxyAndInitializeShouldNotKeepValueWithAccountInit( uint256 valueToTransfer, bytes memory initializeCalldata ) public { @@ -143,7 +149,7 @@ contract LSP16UniversalProfileTest is Test { }( abi.encodeWithSignature( "deployERC1167ProxyAndInitialize(address,bytes32,bytes)", - address(lsp0Init), + address(accountInit), salt, lsp0Initbytes ) @@ -156,7 +162,7 @@ contract LSP16UniversalProfileTest is Test { assert(address(lsp16).balance == 0); } - function testDeployCreate2ShouldNotKeepValueWithUP( + function testDeployCreate2ShouldNotKeepValueWithAccount( uint256 valueToTransfer ) public { vm.deal(address(this), valueToTransfer); @@ -170,7 +176,7 @@ contract LSP16UniversalProfileTest is Test { abi.encodeWithSignature( "deployCreate2(bytes,bytes32)", abi.encodePacked( - type(LSP0ERC725Account).creationCode, + type(Account).creationCode, abi.encode(address(this)) ), salt @@ -187,7 +193,7 @@ contract LSP16UniversalProfileTest is Test { ); } - function testdeployCreate2AndInitializeShouldNotKeepValueWithUPInit( + function testdeployCreate2AndInitializeShouldNotKeepValueWithAccountInit( uint128 valueForInitializer, bytes4 initilializerBytes ) public { @@ -205,7 +211,7 @@ contract LSP16UniversalProfileTest is Test { }( abi.encodeWithSignature( "deployCreate2AndInitialize(bytes,bytes32,bytes,uint256,uint256)", - type(LSP0ERC725AccountInit).creationCode, + type(AccountInit).creationCode, salt, _removeRandomByteFromBytes4(initilializerBytes), 0, // constructor is not payable @@ -267,7 +273,7 @@ contract LSP16UniversalProfileTest is Test { (bool success, ) = address(lsp16).call{value: valueToTransfer}( abi.encodeWithSignature( "deployERC1167ProxyAndInitialize(address,bytes32,bytes)", - address(lsp0Init), + address(accountInit), salt, initializeCalldata ) @@ -342,7 +348,7 @@ contract LSP16UniversalProfileTest is Test { ); } - function testcomputeAddressShouldReturnCorrectUPAddressWithdeployCreate2AndInitialize( + function testcomputeAddressShouldReturnCorrectAccountAddressWithdeployCreate2AndInitialize( bytes32 providedSalt, uint256 valueForInitializer, bytes4 initilializerBytes @@ -355,7 +361,7 @@ contract LSP16UniversalProfileTest is Test { ); address expectedAddress = lsp16.computeAddress( - keccak256(type(LSP0ERC725AccountInit).creationCode), + keccak256(type(AccountInit).creationCode), providedSalt, true, initializeCallData @@ -365,7 +371,7 @@ contract LSP16UniversalProfileTest is Test { }( abi.encodeWithSignature( "deployCreate2AndInitialize(bytes,bytes32,bytes,uint256,uint256)", - type(LSP0ERC725AccountInit).creationCode, + type(AccountInit).creationCode, providedSalt, initializeCallData, 0, @@ -383,7 +389,7 @@ contract LSP16UniversalProfileTest is Test { assert(expectedAddress == returnedAddress); } - function testcomputeAddressShouldReturnCorrectUPAddressWithDeployCreate2( + function testcomputeAddressShouldReturnCorrectAccountAddressWithDeployCreate2( bytes32 providedSalt, uint256 valueForConstructor ) public { @@ -393,7 +399,7 @@ contract LSP16UniversalProfileTest is Test { address expectedAddress = lsp16.computeAddress( keccak256( abi.encodePacked( - type(LSP0ERC725Account).creationCode, + type(Account).creationCode, abi.encode(address(this)) ) ), @@ -407,7 +413,7 @@ contract LSP16UniversalProfileTest is Test { abi.encodeWithSignature( "deployCreate2(bytes,bytes32)", abi.encodePacked( - type(LSP0ERC725Account).creationCode, + type(Account).creationCode, abi.encode(address(this)) ), providedSalt @@ -436,7 +442,7 @@ contract LSP16UniversalProfileTest is Test { ); address expectedAddress = lsp16.computeERC1167Address( - address(lsp0Init), + address(accountInit), providedSalt, true, initializeCallData @@ -446,7 +452,7 @@ contract LSP16UniversalProfileTest is Test { }( abi.encodeWithSignature( "deployERC1167ProxyAndInitialize(address,bytes32,bytes)", - address(lsp0Init), + address(accountInit), providedSalt, initializeCallData ) @@ -465,7 +471,7 @@ contract LSP16UniversalProfileTest is Test { bytes32 providedSalt ) public { address expectedAddress = lsp16.computeERC1167Address( - address(lsp0), + address(account), providedSalt, false, "" @@ -473,7 +479,7 @@ contract LSP16UniversalProfileTest is Test { (bool success, bytes memory returnedData) = address(lsp16).call( abi.encodeWithSignature( "deployERC1167Proxy(address,bytes32)", - address(lsp0), + address(account), providedSalt ) ); diff --git a/packages/LSP16UniversalFactory/hardhat.config.ts b/packages/LSP16UniversalFactory/hardhat.config.ts new file mode 100644 index 000000000..79d0fe07a --- /dev/null +++ b/packages/LSP16UniversalFactory/hardhat.config.ts @@ -0,0 +1,134 @@ +import { HardhatUserConfig } from 'hardhat/config'; +import { NetworkUserConfig } from 'hardhat/types'; +import { config as dotenvConfig } from 'dotenv'; +import { resolve } from 'path'; + +/** + * this package includes: + * - @nomiclabs/hardhat-ethers + * - @nomicfoundation/hardhat-chai-matchers + * - @nomicfoundation/hardhat-network-helpers + * - @nomiclabs/hardhat-etherscan + * - hardhat-gas-reporter (is this true? Why do we have it as a separate dependency?) + * - @typechain/hardhat + * - solidity-coverage + */ +import '@nomicfoundation/hardhat-toolbox'; + +// additional hardhat plugins +import 'hardhat-packager'; +import 'hardhat-contract-sizer'; +import 'hardhat-deploy'; + +// custom built hardhat plugins and scripts +// can be imported here (e.g: docs generation, gas benchmark, etc...) + +dotenvConfig({ path: resolve(__dirname, './.env') }); + +function getTestnetChainConfig(): NetworkUserConfig { + const config: NetworkUserConfig = { + live: true, + url: 'https://rpc.testnet.lukso.network', + chainId: 4201, + }; + + if (process.env.CONTRACT_VERIFICATION_TESTNET_PK !== undefined) { + config['accounts'] = [process.env.CONTRACT_VERIFICATION_TESTNET_PK]; + } + + return config; +} + +const config: HardhatUserConfig = { + defaultNetwork: 'hardhat', + networks: { + hardhat: { + live: false, + saveDeployments: false, + allowBlocksWithSameTimestamp: true, + }, + luksoTestnet: getTestnetChainConfig(), + }, + namedAccounts: { + owner: 0, + }, + // uncomment if the contracts from this LSP package must be deployed at deterministic + // // addresses across multiple chains + // deterministicDeployment: { + // luksoTestnet: { + // // Nick Factory. See https://github.com/Arachnid/deterministic-deployment-proxy + // factory: '0x4e59b44847b379578588920ca78fbf26c0b4956c', + // deployer: '0x3fab184622dc19b6109349b94811493bf2a45362', + // funding: '0x0000000000000000000000000000000000000000000000000000000000000000', + // signedTx: + // '0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222', + // }, + // }, + etherscan: { + apiKey: 'no-api-key-needed', + customChains: [ + { + network: 'luksoTestnet', + chainId: 4201, + urls: { + apiURL: 'https://api.explorer.execution.testnet.lukso.network/api', + browserURL: 'https://explorer.execution.testnet.lukso.network/', + }, + }, + ], + }, + gasReporter: { + enabled: true, + currency: 'USD', + gasPrice: 21, + excludeContracts: ['Helpers/'], + src: './contracts', + showMethodSig: true, + }, + solidity: { + version: '0.8.17', + settings: { + optimizer: { + enabled: true, + /** + * Optimize for how many times you intend to run the code. + * Lower values will optimize more for initial deployment cost, higher + * values will optimize more for high-frequency usage. + * @see https://docs.soliditylang.org/en/v0.8.6/internals/optimizer.html#opcode-based-optimizer-module + */ + runs: 9999999, + }, + metadata: { + // do not include the metadata hash, since this is machine dependent + // and we want all generated code to be deterministic + // https://docs.soliditylang.org/en/v0.7.6/metadata.html + bytecodeHash: 'none', + }, + outputSelection: { + '*': { + '*': ['storageLayout'], + }, + }, + }, + }, + packager: { + // What contracts to keep the artifacts and the bindings for. + contracts: ['LSP16UniversalFactory'], + // 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. + includeFactories: true, + }, + paths: { + artifacts: 'artifacts', + tests: 'tests', + }, + typechain: { + outDir: 'types', + target: 'ethers-v5', + }, + mocha: { + timeout: 10000000, + }, +}; + +export default config; diff --git a/packages/LSP16UniversalFactory/package.json b/packages/LSP16UniversalFactory/package.json new file mode 100644 index 000000000..6be4a2e03 --- /dev/null +++ b/packages/LSP16UniversalFactory/package.json @@ -0,0 +1,48 @@ +{ + "name": "lsp16", + "version": "0.12.1", + "description": "Package for the LSP16 Universal Factory standard", + "license": "Apache-2.0", + "author": "", + "keywords": [ + "LUKSO", + "LSP", + "Blockchain", + "Standards", + "Smart Contracts", + "Ethereum", + "EVM", + "Solidity" + ], + "scripts": { + "build": "hardhat compile --show-stack-traces", + "clean": "hardhat clean", + "format": "prettier --write .", + "lint": "eslint . --ext .ts,.js", + "lint:solidity": "solhint 'contracts/**/*.sol' && prettier --check 'contracts/**/*.sol'", + "test": "hardhat test --no-compile tests/*.test.ts", + "test:foundry": "FOUNDRY_PROFILE=lsp16 forge test --no-match-test Skip -vvv" + }, + "dependencies": { + "@openzeppelin/contracts": "^4.9.2" + }, + "devDependencies": { + "@erc725/smart-contracts": "7.0.0", + "@openzeppelin/contracts-upgradeable": "4.9.3", + "@nomicfoundation/hardhat-toolbox": "^2.0.2", + "@typechain/ethers-v5": "^10.2.0", + "dotenv": "^16.0.3", + "eslint-config-custom": "*", + "ethers": "^5.7.2", + "hardhat": "^2.13.0", + "hardhat-contract-sizer": "^2.8.0", + "hardhat-deploy": "^0.11.25", + "hardhat-gas-reporter": "^1.0.9", + "hardhat-packager": "^1.4.2", + "solhint": "^3.3.6", + "ts-node": "^10.2.0", + "tsconfig": "*", + "typechain": "^8.0.0", + "typescript": "^5.3.3" + } +} diff --git a/tests/LSP16UniversalFactory/LSP16UniversalFactory.test.ts b/packages/LSP16UniversalFactory/tests/LSP16UniversalFactory.test.ts similarity index 82% rename from tests/LSP16UniversalFactory/LSP16UniversalFactory.test.ts rename to packages/LSP16UniversalFactory/tests/LSP16UniversalFactory.test.ts index fdbe78429..2dd4589ac 100644 --- a/tests/LSP16UniversalFactory/LSP16UniversalFactory.test.ts +++ b/packages/LSP16UniversalFactory/tests/LSP16UniversalFactory.test.ts @@ -3,31 +3,31 @@ import { expect } from 'chai'; import { FakeContract, smock } from '@defi-wonderland/smock'; import { - LSP1UniversalReceiverDelegateUP__factory, LSP16UniversalFactory, LSP16UniversalFactory__factory, - UniversalProfileInit, - UniversalProfileInit__factory, - LSP1UniversalReceiverDelegateUP, - UniversalProfile, - UniversalProfile__factory, + Account, + Account__factory, + AccountInit, + AccountInit__factory, PayableContract, PayableContract__factory, + NonPayableContract__factory, ImplementationTester, ImplementationTester__factory, FallbackInitializer, FallbackInitializer__factory, - LSP6KeyManager__factory, -} from '../../types'; + ContractNoConstructor__factory, + ContractNoConstructor, +} from '../types'; import web3 from 'web3'; import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { provider, AddressOffset } from '../utils/helpers'; +import { provider, AddressOffset } from '../../../tests/utils/helpers'; -const UniversalProfileBytecode = UniversalProfile__factory.bytecode; -const LSP6KeyManagerBytecode = LSP6KeyManager__factory.bytecode; +const AccountBytecode = Account__factory.bytecode; +const NonPayableConstructorBytecode = NonPayableContract__factory.bytecode; const ImplementationTesterBytecode = ImplementationTester__factory.bytecode; const FallbackInitializerBytecode = FallbackInitializer__factory.bytecode; @@ -60,9 +60,9 @@ describe('UniversalFactory contract', () => { describe('When using LSP16UniversalFactory', () => { let context: UniversalFactoryTestContext; - let universalProfileConstructor: UniversalProfile; - let universalProfileBaseContract: UniversalProfileInit; - let universalReceiverDelegate: LSP1UniversalReceiverDelegateUP; + let accountConstructor: Account; + let accountBaseContract: AccountInit; + let contractNoConstructor: ContractNoConstructor; let payableContract: PayableContract; let fallbackContract: FakeContract; let implementationTester: ImplementationTester; @@ -71,15 +71,13 @@ describe('UniversalFactory contract', () => { before(async () => { context = await buildTestContext(); - universalProfileConstructor = await new UniversalProfile__factory( - context.accounts.random, - ).deploy(ethers.constants.AddressZero); + accountConstructor = await new Account__factory(context.accounts.random).deploy( + context.accounts.random.address, + ); - universalProfileBaseContract = await new UniversalProfileInit__factory( - context.accounts.random, - ).deploy(); + accountBaseContract = await new AccountInit__factory(context.accounts.random).deploy(); - universalReceiverDelegate = await new LSP1UniversalReceiverDelegateUP__factory( + contractNoConstructor = await new ContractNoConstructor__factory( context.accounts.random, ).deploy(); @@ -106,9 +104,10 @@ describe('UniversalFactory contract', () => { it('should calculate the address of a non-initializable contract correctly', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); + const randomAddress = ethers.Wallet.createRandom(); + // Set the Owner as the ZeroAddress - const UPBytecode = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + const UPBytecode = AccountBytecode + AddressOffset + randomAddress.address.substring(2); const bytecodeHash = ethers.utils.solidityKeccak256(['bytes'], [UPBytecode]); @@ -129,8 +128,9 @@ describe('UniversalFactory contract', () => { it('should calculate the same address of a contract if the initializeCalldata changed and the contract is not initializable', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); - const UPBytecode = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + const randomAddress = ethers.Wallet.createRandom(); + + const UPBytecode = AccountBytecode + AddressOffset + randomAddress.address.substr(2); const bytecodeHash = ethers.utils.solidityKeccak256(['bytes'], [UPBytecode]); @@ -157,8 +157,7 @@ describe('UniversalFactory contract', () => { const salt1 = ethers.utils.solidityKeccak256(['string'], ['Salt1']); const salt2 = ethers.utils.solidityKeccak256(['string'], ['Salt2']); - const UPBytecode = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + const UPBytecode = AccountBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); const bytecodeHash = ethers.utils.solidityKeccak256(['bytes'], [UPBytecode]); @@ -185,12 +184,12 @@ describe('UniversalFactory contract', () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); const UPBytecode1 = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + AccountBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); const bytecodeHash1 = ethers.utils.solidityKeccak256(['bytes'], [UPBytecode1]); const UPBytecode2 = - UniversalProfileBytecode + AddressOffset + 'cafecafecafecafecafecafecafecafecafecafe'; + AccountBytecode + AddressOffset + 'cafecafecafecafecafecafecafecafecafecafe'; const bytecodeHash2 = ethers.utils.solidityKeccak256(['bytes'], [UPBytecode2]); @@ -216,8 +215,9 @@ describe('UniversalFactory contract', () => { it('should revert when deploying a non-initializable contract with the same bytecode and salt ', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); - const UPBytecode = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + const randomAddress = ethers.Wallet.createRandom(); + + const UPBytecode = AccountBytecode + AddressOffset + randomAddress.address.substring(2); await context.universalFactory.deployCreate2(UPBytecode, salt); @@ -230,7 +230,7 @@ describe('UniversalFactory contract', () => { const salt = ethers.utils.solidityKeccak256(['string'], ['OtherSalt']); const KMBytecode = - LSP6KeyManagerBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + NonPayableConstructorBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); await expect( context.universalFactory.deployCreate2(KMBytecode, salt, { @@ -264,7 +264,7 @@ describe('UniversalFactory contract', () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); const UPBytecode = - UniversalProfileBytecode + AddressOffset + context.accounts.deployer3.address.substr(2); + AccountBytecode + AddressOffset + context.accounts.deployer3.address.substr(2); const contractCreatedAddress = await context.universalFactory.callStatic.deployCreate2( UPBytecode, @@ -281,7 +281,7 @@ describe('UniversalFactory contract', () => { .to.emit(context.universalFactory, 'ContractCreated') .withArgs(contractCreatedAddress, salt, generatedSalt, false, '0x'); - const universalProfile = universalProfileConstructor.attach(contractCreatedAddress); + const universalProfile = accountConstructor.attach(contractCreatedAddress); const owner = await universalProfile.callStatic.owner(); expect(owner).to.equal(context.accounts.deployer3.address); @@ -325,15 +325,13 @@ describe('UniversalFactory contract', () => { const salt1 = ethers.utils.solidityKeccak256(['string'], ['Salt1']); const salt2 = ethers.utils.solidityKeccak256(['string'], ['Salt2']); - const UPBytecode = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + const UPBytecode = AccountBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); const bytecodeHash = ethers.utils.solidityKeccak256(['bytes'], [UPBytecode]); - const initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); const calulcatedAddressSalt1 = await context.universalFactory.computeAddress( bytecodeHash, @@ -357,15 +355,13 @@ describe('UniversalFactory contract', () => { it('should calculate a different address of a contract if the initializeCalldata changed', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); - const UPBytecode = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + const UPBytecode = AccountBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); const bytecodeHash = ethers.utils.solidityKeccak256(['bytes'], [UPBytecode]); - const initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); const calculatedAddressInitializableFalse = await context.universalFactory.computeAddress( bytecodeHash, @@ -391,17 +387,16 @@ describe('UniversalFactory contract', () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); const UPBytecode1 = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + AccountBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); - const initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); const bytecodeHash1 = ethers.utils.solidityKeccak256(['bytes'], [UPBytecode1]); const UPBytecode2 = - UniversalProfileBytecode + AddressOffset + 'cafecafecafecafecafecafecafecafecafecafe'; + AccountBytecode + AddressOffset + 'cafecafecafecafecafecafecafecafecafecafe'; const bytecodeHash2 = ethers.utils.solidityKeccak256(['bytes'], [UPBytecode2]); @@ -425,24 +420,23 @@ describe('UniversalFactory contract', () => { }); it('should revert when deploying an initializable contract with the same bytecode and salt ', async () => { - const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); + const salt = ethers.utils.solidityKeccak256(['string'], ['Salting']); - const UPBytecode = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + const fallbackInitializerBytecode = FallbackInitializerBytecode; await context.universalFactory.deployCreate2AndInitialize( - UPBytecode, + fallbackInitializerBytecode, salt, - '0x00000000aabbccdd', // send some random data along prepended with `0x00000000` + '0xaabbccdd', 0, 0, ); await expect( context.universalFactory.deployCreate2AndInitialize( - UPBytecode, + fallbackInitializerBytecode, salt, - '0x00000000aabbccdd', // send some random data along prepended with `0x00000000` + '0xaabbccdd', 0, 0, ), @@ -452,13 +446,11 @@ describe('UniversalFactory contract', () => { it('should revert when deploying an initializable contract with sending value unmatched to the msgValue arguments', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); - const UPBytecode = - UniversalProfileBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); + const UPBytecode = AccountBytecode + AddressOffset + ethers.constants.AddressZero.substr(2); - const initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); await expect( context.universalFactory.deployCreate2AndInitialize( @@ -580,7 +572,7 @@ describe('UniversalFactory contract', () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); const calculatedAddress = await context.universalFactory.computeERC1167Address( - universalReceiverDelegate.address, + contractNoConstructor.address, salt, false, '0x', @@ -588,7 +580,7 @@ describe('UniversalFactory contract', () => { const contractCreated = await context.universalFactory .connect(context.accounts.deployer1) - .callStatic.deployERC1167Proxy(universalReceiverDelegate.address, salt); + .callStatic.deployERC1167Proxy(contractNoConstructor.address, salt); expect(calculatedAddress).to.equal(contractCreated); }); @@ -598,14 +590,14 @@ describe('UniversalFactory contract', () => { const salt2 = ethers.utils.solidityKeccak256(['string'], ['Salt2']); const calculatedAddressSalt1 = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt1, false, '0x', ); const calculatedAddressSalt2 = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt2, false, '0x', @@ -619,14 +611,13 @@ describe('UniversalFactory contract', () => { it("should calculate the same address of a proxy if the initializeCalldata changed (because it's not initializable)", async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); - const initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); const calculatedAddressInitializableTrue = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt, false, initializeCallData, @@ -634,7 +625,7 @@ describe('UniversalFactory contract', () => { const calculatedAddressInitializableFalse = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt, false, '0xaabb', @@ -650,14 +641,14 @@ describe('UniversalFactory contract', () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); const calulcatedAddressBaseContract1 = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt, false, '0x', ); const calulcatedAddressBaseContract2 = await context.universalFactory.computeERC1167Address( - universalReceiverDelegate.address, + contractNoConstructor.address, salt, false, '0x', @@ -671,13 +662,10 @@ describe('UniversalFactory contract', () => { it('should revert when deploying a proxy contract with the same `baseContract` and salt ', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); - await context.universalFactory.deployERC1167Proxy( - universalProfileBaseContract.address, - salt, - ); + await context.universalFactory.deployERC1167Proxy(accountBaseContract.address, salt); await expect( - context.universalFactory.deployERC1167Proxy(universalProfileBaseContract.address, salt), + context.universalFactory.deployERC1167Proxy(accountBaseContract.address, salt), ).to.be.revertedWith('ERC1167: create2 failed'); }); @@ -685,7 +673,7 @@ describe('UniversalFactory contract', () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt#2']); const contractCreatedAddress = await context.universalFactory.callStatic.deployERC1167Proxy( - universalProfileBaseContract.address, + accountBaseContract.address, salt, ); @@ -695,13 +683,11 @@ describe('UniversalFactory contract', () => { '0x', ); - await expect( - context.universalFactory.deployERC1167Proxy(universalProfileBaseContract.address, salt), - ) + await expect(context.universalFactory.deployERC1167Proxy(accountBaseContract.address, salt)) .to.emit(context.universalFactory, 'ContractCreated') .withArgs(contractCreatedAddress, salt, generatedSalt, false, '0x'); - const universalProfile = universalProfileBaseContract.attach(contractCreatedAddress); + const universalProfile = accountBaseContract.attach(contractCreatedAddress); const owner = await universalProfile.callStatic.owner(); expect(owner).to.equal(ethers.constants.AddressZero); @@ -712,13 +698,12 @@ describe('UniversalFactory contract', () => { it("should calculate the address of a proxy correctly if it's initializable", async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); - const initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); const calculatedAddress = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt, true, initializeCallData, @@ -727,7 +712,7 @@ describe('UniversalFactory contract', () => { const contractCreated = await context.universalFactory .connect(context.accounts.deployer1) .callStatic.deployERC1167ProxyAndInitialize( - universalProfileBaseContract.address, + accountBaseContract.address, salt, initializeCallData, ); @@ -739,20 +724,19 @@ describe('UniversalFactory contract', () => { const salt1 = ethers.utils.solidityKeccak256(['string'], ['Salt1']); const salt2 = ethers.utils.solidityKeccak256(['string'], ['Salt2']); - const initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); const calculatedAddressSalt1 = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt1, true, initializeCallData, ); const calculatedAddressSalt2 = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt2, true, initializeCallData, @@ -766,19 +750,17 @@ describe('UniversalFactory contract', () => { it('should calculate a different address of a proxy if the `initializeCallData` changed', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); - const initializeCallData1 = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + const initializeCallData1 = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); - const initializeCallData2 = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer2.address], - ); + const initializeCallData2 = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer2.address, + ]); const calulcatedAddressinitializeCallData1 = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt, true, initializeCallData1, @@ -786,7 +768,7 @@ describe('UniversalFactory contract', () => { const calulcatedAddressinitializeCallData2 = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt, true, initializeCallData2, @@ -801,20 +783,19 @@ describe('UniversalFactory contract', () => { it('should calculate a different address of a proxy if the `baseContract` changed', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); - const initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); const calulcatedAddressBaseContract1 = await context.universalFactory.computeERC1167Address( - universalProfileBaseContract.address, + accountBaseContract.address, salt, true, initializeCallData, ); const calulcatedAddressBaseContract2 = await context.universalFactory.computeERC1167Address( - universalReceiverDelegate.address, + contractNoConstructor.address, salt, true, initializeCallData, @@ -828,17 +809,21 @@ describe('UniversalFactory contract', () => { it('should revert when deploying a proxy contract with the same `baseContract` and salt ', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt']); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); + await context.universalFactory.deployERC1167ProxyAndInitialize( - universalProfileBaseContract.address, + accountBaseContract.address, salt, - '0x', + initializeCallData, ); await expect( context.universalFactory.deployERC1167ProxyAndInitialize( - universalProfileBaseContract.address, + accountBaseContract.address, salt, - '0x', + initializeCallData, ), ).to.be.revertedWith('ERC1167: create2 failed'); }); @@ -934,14 +919,13 @@ describe('UniversalFactory contract', () => { it('should deploy an initializable CREATE2 proxy contract and emit the event and get the owner successfully', async () => { const salt = ethers.utils.solidityKeccak256(['string'], ['Salt#3']); - const initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer4.address], - ); + const initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer4.address, + ]); const contractCreatedAddress = await context.universalFactory.callStatic.deployERC1167ProxyAndInitialize( - universalProfileBaseContract.address, + accountBaseContract.address, salt, initializeCallData, ); @@ -954,7 +938,7 @@ describe('UniversalFactory contract', () => { await expect( context.universalFactory.deployERC1167ProxyAndInitialize( - universalProfileBaseContract.address, + accountBaseContract.address, salt, initializeCallData, ), @@ -962,7 +946,7 @@ describe('UniversalFactory contract', () => { .to.emit(context.universalFactory, 'ContractCreated') .withArgs(contractCreatedAddress, salt, generatedSalt, true, initializeCallData); - const universalProfile = universalProfileBaseContract.attach(contractCreatedAddress); + const universalProfile = accountBaseContract.attach(contractCreatedAddress); const owner = await universalProfile.callStatic.owner(); expect(owner).to.equal(context.accounts.deployer4.address); @@ -977,14 +961,13 @@ describe('UniversalFactory contract', () => { before(async () => { salt = ethers.utils.solidityKeccak256(['string'], ['SaltEdge']); - initializeCallData = universalProfileBaseContract.interface.encodeFunctionData( - 'initialize', - [context.accounts.deployer1.address], - ); + initializeCallData = accountBaseContract.interface.encodeFunctionData('initialize', [ + context.accounts.deployer1.address, + ]); contractCreatedWithdeployERC1167ProxyAndInitialize = await context.universalFactory.callStatic.deployERC1167ProxyAndInitialize( - universalProfileBaseContract.address, + accountBaseContract.address, salt, initializeCallData, ); @@ -993,7 +976,7 @@ describe('UniversalFactory contract', () => { it('should result in a different address if deployed without initializing with deployERC1167Proxy function', async () => { const contractCreatedWithdeployERC1167Proxy = await context.universalFactory.callStatic.deployERC1167Proxy( - universalProfileBaseContract.address, + accountBaseContract.address, salt, ); @@ -1011,7 +994,7 @@ describe('UniversalFactory contract', () => { // deploy proxy contract const proxyBytecode = eip1167RuntimeCodeTemplate.replace( 'bebebebebebebebebebebebebebebebebebebebe', - universalProfileBaseContract.address.substr(2), + accountBaseContract.address.substr(2), ); const contractCreatedWithDeployCreate2 = @@ -1029,7 +1012,7 @@ describe('UniversalFactory contract', () => { // deploy proxy contract const proxyBytecode = eip1167RuntimeCodeTemplate.replace( 'bebebebebebebebebebebebebebebebebebebebe', - universalProfileBaseContract.address.substr(2), + accountBaseContract.address.substr(2), ); const contractCreatedWithdeployCreate2AndInitialize = diff --git a/packages/LSP16UniversalFactory/tsconfig.json b/packages/LSP16UniversalFactory/tsconfig.json new file mode 100644 index 000000000..b7a34e03f --- /dev/null +++ b/packages/LSP16UniversalFactory/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "tsconfig/contracts.json", + "include": ["**/*.ts"] +} diff --git a/remappings.txt b/remappings.txt index 33bd80b45..d659f0f56 100644 --- a/remappings.txt +++ b/remappings.txt @@ -11,3 +11,4 @@ lsp2/=packages/LSP2ERC725YJSONSchema/ lsp4/=packages/LSP4DigitalAssetMetadata/ lsp20/=packages/LSP20CallVerification/ lsp25/=packages/LSP25ExecuteRelayCall/ +lsp16/=packages/LSP16UniversalFactory/