From 93c5a2a57be9b22eb1f27497640337b4bc81e1e8 Mon Sep 17 00:00:00 2001 From: "b00ste.lyx" <62855857+b00ste@users.noreply.github.com> Date: Wed, 23 Aug 2023 17:24:09 +0300 Subject: [PATCH 1/3] refactor: add input validations when setting data in `LSP6SetDataModule` (#679) * refactor: add input validations * test: fix some tests with non-standard values * docs: update auto-generated docs --- contracts/LSP6KeyManager/LSP6Errors.sol | 40 ++++++++---- .../LSP6Modules/LSP6SetDataModule.sol | 41 ++++++++++-- .../LSP6KeyManager/LSP6KeyManager.md | 65 +++++++++---------- .../PermissionChangeAddExtensions.test.ts | 2 +- .../PermissionChangeAddController.test.ts | 42 +++--------- .../PermissionChangeAddExtensions.test.ts | 2 +- .../LSP6ControlledToken.test.ts | 4 +- .../PermissionChangeAddController.test.ts | 42 +++--------- 8 files changed, 116 insertions(+), 122 deletions(-) diff --git a/contracts/LSP6KeyManager/LSP6Errors.sol b/contracts/LSP6KeyManager/LSP6Errors.sol index 0625f838a..3126cd933 100644 --- a/contracts/LSP6KeyManager/LSP6Errors.sol +++ b/contracts/LSP6KeyManager/LSP6Errors.sol @@ -91,18 +91,6 @@ error InvalidERC725Function(bytes4 invalidFunction); */ error InvalidEncodedAllowedCalls(bytes allowedCallsValue); -/** - * @notice Could not store `invalidValue` inside the `AddressPermissions[]` Array at index: `dataKey`. - * @dev Reverts when trying to set a value that is not 20 bytes long (not an `address`) under the `AddressPermissions[index]` data key. - * - * @param dataKey The `AddressPermissions[index]` data key, that specify the index in the `AddressPermissions[]` array. - * @param invalidValue The invalid value that was attempted to be set under `AddressPermissions[index]`. - */ -error AddressPermissionArrayIndexValueNotAnAddress( - bytes32 dataKey, - bytes invalidValue -); - /** * @notice The address `from` is not authorised to set data, because it has no ERC725Y Data Key allowed. * @@ -227,6 +215,32 @@ error CannotSendValueToSetData(); error CallingKeyManagerNotAllowed(); /** - * @dev reverts when the address of the Key Manager is being set as extensions for lsp20 functions + * @notice Relay call not valid yet. + * + * @dev Reverts when the start timestamp provided to {executeRelayCall} function is bigger than the current timestamp. + */ +error RelayCallBeforeStartTime(); + +/** + * @notice The date of the relay call expired. + * + * @dev Reverts when the period to execute the relay call has expired. + */ +error RelayCallExpired(); + +/** + * @notice Key Manager cannot be used as an LSP17 extension for LSP20 functions. + * + * @dev Reverts when the address of the Key Manager is being set as extensions for lsp20 functions */ error KeyManagerCannotBeSetAsExtensionForLSP20Functions(); + +/** + * @notice Data value: `dataValue` length is different from the required length for the data key which is set. + * + * @dev Reverts when the data value length is not one of the required lengths for the specific data key. + * + * @param dataKey The data key that has a required length for the data value. + * @param dataValue The data value that has an invalid length. + */ +error InvalidDataValuesForDataKeys(bytes32 dataKey, bytes dataValue); diff --git a/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol b/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol index bceb307b5..78aa6c1a0 100644 --- a/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol +++ b/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol @@ -43,13 +43,13 @@ import { // errors import { NotRecognisedPermissionKey, - AddressPermissionArrayIndexValueNotAnAddress, InvalidEncodedAllowedCalls, InvalidEncodedAllowedERC725YDataKeys, NoERC725YDataKeysAllowed, NotAllowedERC725YDataKey, NotAuthorised, - KeyManagerCannotBeSetAsExtensionForLSP20Functions + KeyManagerCannotBeSetAsExtensionForLSP20Functions, + InvalidDataValuesForDataKeys } from "../LSP6Errors.sol"; abstract contract LSP6SetDataModule { @@ -228,6 +228,14 @@ abstract contract LSP6SetDataModule { bytes12(inputDataKey) == _LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX ) { + // CHECK that `dataValue` contains exactly 32 bytes, which is the required length for a permission BitArray + if (inputDataValue.length != 32 && inputDataValue.length != 0) { + revert InvalidDataValuesForDataKeys( + inputDataKey, + inputDataValue + ); + } + // controller already has the permissions needed. Do not run internal function. if (hasBothAddControllerAndEditPermissions) return (bytes32(0)); @@ -286,6 +294,14 @@ abstract contract LSP6SetDataModule { inputDataKey == _LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY || bytes12(inputDataKey) == _LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX ) { + // CHECK that `dataValue` contains exactly 20 bytes, which corresponds to an address for a LSP1 Delegate contract + if (inputDataValue.length != 20 && inputDataValue.length != 0) { + revert InvalidDataValuesForDataKeys( + inputDataKey, + inputDataValue + ); + } + // same as above. If controller has both permissions, do not read the `target` storage // to save gas by avoiding an extra external `view` call. if ( @@ -305,6 +321,14 @@ abstract contract LSP6SetDataModule { // LSP17Extension: } else if (bytes12(inputDataKey) == _LSP17_EXTENSION_PREFIX) { + // CHECK that `dataValue` contains exactly 20 bytes, which corresponds to an address for a LSP17 Extension + if (inputDataValue.length != 20 && inputDataValue.length != 0) { + revert InvalidDataValuesForDataKeys( + inputDataKey, + inputDataValue + ); + } + // reverts when the address of the Key Manager is being set as extensions for lsp20 functions bytes4 selector = bytes4(inputDataKey << 96); @@ -355,6 +379,14 @@ abstract contract LSP6SetDataModule { ) internal view virtual returns (bytes32) { // AddressPermissions[] -> array length if (inputDataKey == _LSP6KEY_ADDRESSPERMISSIONS_ARRAY) { + // CHECK that `dataValue` is exactly 16 bytes long or `0x` (= not set), as the array length of `AddressPermissions[]` MUST be a `uint128` value. + if (inputDataValue.length != 16 && inputDataValue.length != 0) { + revert InvalidDataValuesForDataKeys( + inputDataKey, + inputDataValue + ); + } + // if the controller already has both permissions from one of the two required, // No permission required as CHECK is already done. We don't need to read `target` storage. if (hasBothAddControllerAndEditPermissions) return bytes32(0); @@ -376,10 +408,7 @@ abstract contract LSP6SetDataModule { // CHECK that we either ADD an address (20 bytes long) or REMOVE an address (0x) if (inputDataValue.length != 0 && inputDataValue.length != 20) { - revert AddressPermissionArrayIndexValueNotAnAddress( - inputDataKey, - inputDataValue - ); + revert InvalidDataValuesForDataKeys(inputDataKey, inputDataValue); } // if the controller already has both permissions from one of the two required below, diff --git a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md index d8b157632..42714efcb 100644 --- a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md +++ b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md @@ -1230,37 +1230,6 @@ Emitted when the LSP6KeyManager contract verified the permissions of the `signer ## Errors -### AddressPermissionArrayIndexValueNotAnAddress - -:::note References - -- Specification details: [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#addresspermissionarrayindexvaluenotanaddress) -- Solidity implementation: [`LSP6KeyManager.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP6KeyManager/LSP6KeyManager.sol) -- Error signature: `AddressPermissionArrayIndexValueNotAnAddress(bytes32,bytes)` -- Error hash: `0x8f4afa38` - -::: - -```solidity -error AddressPermissionArrayIndexValueNotAnAddress( - bytes32 dataKey, - bytes invalidValue -); -``` - -_Could not store `invalidValue` inside the `AddressPermissions[]` Array at index: `dataKey`._ - -Reverts when trying to set a value that is not 20 bytes long (not an `address`) under the `AddressPermissions[index]` data key. - -#### Parameters - -| Name | Type | Description | -| -------------- | :-------: | ----------------------------------------------------------------------------------------------------- | -| `dataKey` | `bytes32` | The `AddressPermissions[index]` data key, that specify the index in the `AddressPermissions[]` array. | -| `invalidValue` | `bytes` | The invalid value that was attempted to be set under `AddressPermissions[index]`. | - -
- ### BatchExecuteParamsLengthMismatch :::note References @@ -1381,7 +1350,35 @@ Reverts when trying to do a `delegatecall` via the ERC725X.execute(uint256,addre error ERC725Y_DataKeysValuesLengthMismatch(); ``` -reverts when there is not the same number of elements in the lists of data keys and data values when calling setDataBatch. +Reverts when there is not the same number of elements in the `datakeys` and `dataValues` array parameters provided when calling the [`setDataBatch`](#setdatabatch) function. + +
+ +### InvalidDataValuesForDataKeys + +:::note References + +- Specification details: [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#invaliddatavaluesfordatakeys) +- Solidity implementation: [`LSP6KeyManager.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `InvalidDataValuesForDataKeys(bytes32,bytes)` +- Error hash: `0x1fa41397` + +::: + +```solidity +error InvalidDataValuesForDataKeys(bytes32 dataKey, bytes dataValue); +``` + +_Data value: `dataValue` length is different from the required length for the data key which is set._ + +Reverts when the data value length is not one of the required lengths for the specific data key. + +#### Parameters + +| Name | Type | Description | +| ----------- | :-------: | ----------------------------------------------------------- | +| `dataKey` | `bytes32` | The data key that has a required length for the data value. | +| `dataValue` | `bytes` | The data value that has an invalid length. |
@@ -1596,7 +1593,9 @@ Reverts when a `from` address has _"any whitelisted call"_ as allowed call set. error KeyManagerCannotBeSetAsExtensionForLSP20Functions(); ``` -reverts when the address of the Key Manager is being set as extensions for lsp20 functions +_Key Manager cannot be used as an LSP17 extension for LSP20 functions._ + +Reverts when the address of the Key Manager is being set as extensions for lsp20 functions
diff --git a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts index 7c7412290..75363c66e 100644 --- a/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts +++ b/tests/LSP20CallVerification/LSP6/Admin/PermissionChangeAddExtensions.test.ts @@ -552,7 +552,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( ], dataValues: [ extensionB, - ethers.utils.hexZeroPad(ethers.utils.hexlify(8), 32), + ethers.utils.hexZeroPad(ethers.utils.hexlify(8), 16), '0xaabb', ], }; diff --git a/tests/LSP20CallVerification/LSP6/SetPermissions/PermissionChangeAddController.test.ts b/tests/LSP20CallVerification/LSP6/SetPermissions/PermissionChangeAddController.test.ts index 4463d3b4d..73c05fbe6 100644 --- a/tests/LSP20CallVerification/LSP6/SetPermissions/PermissionChangeAddController.test.ts +++ b/tests/LSP20CallVerification/LSP6/SetPermissions/PermissionChangeAddController.test.ts @@ -220,10 +220,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( // set some random bytes under AddressPermissions[7] await expect(context.universalProfile.connect(context.owner).setData(key, randomValue)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -236,10 +233,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( // set some random bytes under AddressPermissions[7] await expect(context.universalProfile.connect(context.owner).setData(key, randomValue)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); }); @@ -270,10 +264,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( // set some random bytes under AddressPermissions[7] await expect(context.universalProfile.connect(context.owner).setData(key, randomValue)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -286,10 +277,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( // set some random bytes under AddressPermissions[7] await expect(context.universalProfile.connect(context.owner).setData(key, randomValue)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); }); @@ -434,10 +422,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( await expect( context.universalProfile.connect(canOnlyAddController).setData(key, randomValue), ) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -450,10 +435,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( await expect( context.universalProfile.connect(canOnlyAddController).setData(key, randomValue), ) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); }); @@ -590,7 +572,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( const newLength = ethers.BigNumber.from(currentLength).sub(1).toNumber(); - const value = ethers.utils.hexZeroPad(ethers.utils.hexlify(newLength), 32); + const value = ethers.utils.hexZeroPad(ethers.utils.hexlify(newLength), 16); await context.universalProfile.connect(canOnlyEditPermissions).setData(key, value); @@ -643,10 +625,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( await expect( context.universalProfile.connect(canOnlyEditPermissions).setData(key, randomValue), ) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -661,10 +640,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( await expect( context.universalProfile.connect(canOnlyEditPermissions).setData(key, randomValue), ) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); }); diff --git a/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts b/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts index edf7f091d..dad51e43a 100644 --- a/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts +++ b/tests/LSP6KeyManager/Admin/PermissionChangeAddExtensions.test.ts @@ -684,7 +684,7 @@ export const shouldBehaveLikePermissionChangeOrAddExtensions = ( ], dataValues: [ extensionB, - ethers.utils.hexZeroPad(ethers.utils.hexlify(8), 32), + ethers.utils.hexZeroPad(ethers.utils.hexlify(8), 16), '0xaabb', ], }; diff --git a/tests/LSP6KeyManager/LSP6ControlledToken.test.ts b/tests/LSP6KeyManager/LSP6ControlledToken.test.ts index 2df21cb3e..d81b9e6d8 100644 --- a/tests/LSP6KeyManager/LSP6ControlledToken.test.ts +++ b/tests/LSP6KeyManager/LSP6ControlledToken.test.ts @@ -431,7 +431,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanSetData.address.substring(2); - const value = ARRAY_LENGTH.ZERO; + const value = '0x'; const payload = context.token.interface.encodeFunctionData('setData', [key, value]); await expect(context.keyManager.connect(addressCanAddController).execute(payload)) @@ -443,7 +443,7 @@ describe('When deploying LSP7 with LSP6 as owner', () => { const key = ERC725YDataKeys.LSP6['AddressPermissions:Permissions'] + addressCanSetData.address.substring(2); - const value = ARRAY_LENGTH.ZERO; + const value = '0x'; const payload = context.token.interface.encodeFunctionData('setData', [key, value]); await context.keyManager.connect(context.owner).execute(payload); diff --git a/tests/LSP6KeyManager/SetPermissions/PermissionChangeAddController.test.ts b/tests/LSP6KeyManager/SetPermissions/PermissionChangeAddController.test.ts index 1562fa658..aac7e4dcb 100644 --- a/tests/LSP6KeyManager/SetPermissions/PermissionChangeAddController.test.ts +++ b/tests/LSP6KeyManager/SetPermissions/PermissionChangeAddController.test.ts @@ -256,10 +256,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ]); await expect(context.keyManager.connect(context.owner).execute(setupPayload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -274,10 +271,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ]); await expect(context.keyManager.connect(context.owner).execute(setupPayload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); }); @@ -313,10 +307,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ]); await expect(context.keyManager.connect(context.owner).execute(setupPayload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -331,10 +322,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ]); await expect(context.keyManager.connect(context.owner).execute(setupPayload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); }); @@ -511,10 +499,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ]); await expect(context.keyManager.connect(canOnlyAddController).execute(setupPayload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -529,10 +514,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ]); await expect(context.keyManager.connect(canOnlyAddController).execute(setupPayload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); }); @@ -698,7 +680,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( const newLength = ethers.BigNumber.from(currentLength).sub(1).toNumber(); - const value = ethers.utils.hexZeroPad(ethers.utils.hexlify(newLength), 32); + const value = ethers.utils.hexZeroPad(ethers.utils.hexlify(newLength), 16); const payload = context.universalProfile.interface.encodeFunctionData('setData', [ key, @@ -760,10 +742,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ]); await expect(context.keyManager.connect(canOnlyEditPermissions).execute(setupPayload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); @@ -778,10 +757,7 @@ export const shouldBehaveLikePermissionChangeOrAddController = ( ]); await expect(context.keyManager.connect(canOnlyEditPermissions).execute(setupPayload)) - .to.be.revertedWithCustomError( - context.keyManager, - 'AddressPermissionArrayIndexValueNotAnAddress', - ) + .to.be.revertedWithCustomError(context.keyManager, 'InvalidDataValuesForDataKeys') .withArgs(key, randomValue); }); }); From fc3c7b262c0dc35866412dc505c2842e66abae54 Mon Sep 17 00:00:00 2001 From: Skima Harvey <64636974+skimaharvey@users.noreply.github.com> Date: Wed, 23 Aug 2023 17:16:03 +0200 Subject: [PATCH 2/3] docs: add postdeployment module to docs (#685) --- .../IPostDeploymentModule.sol | 8 +++ .../IPostDeploymentModule.md | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 docs/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.md diff --git a/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol b/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol index 5ed8b2301..dda7d22d6 100644 --- a/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol +++ b/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol @@ -2,6 +2,14 @@ pragma solidity ^0.8.4; interface IPostDeploymentModule { + /** + * @dev Executes post-deployment logic for the primary and secondary contracts. + * @notice This function can be used to perform any additional setup or configuration after the primary and secondary contracts have been deployed. + * + * @param primaryContract The address of the deployed primary contract. + * @param secondaryContract The address of the deployed secondary contract. + * @param calldataToPostDeploymentModule Calldata to be passed for the post-deployment execution. + */ function executePostDeployment( address primaryContract, address secondaryContract, diff --git a/docs/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.md b/docs/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.md new file mode 100644 index 000000000..1850eeeaf --- /dev/null +++ b/docs/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.md @@ -0,0 +1,53 @@ + + + +# IPostDeploymentModule + +:::info Standard Specifications + +[`LSP-23-LinkedContractsDeployment`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md) + +::: +:::info Solidity implementation + +[`IPostDeploymentModule.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol) + +::: + +## 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. + +### executePostDeployment + +:::note References + +- Specification details: [**LSP-23-LinkedContractsDeployment**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-23-LinkedContractsDeployment.md#executepostdeployment) +- Solidity implementation: [`IPostDeploymentModule.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP23LinkedContractsDeployment/IPostDeploymentModule.sol) +- Function signature: `executePostDeployment(address,address,bytes)` +- Function selector: `0x28c4d14e` + +::: + +```solidity +function executePostDeployment( + address primaryContract, + address secondaryContract, + bytes calldataToPostDeploymentModule +) external nonpayable; +``` + +_This function can be used to perform any additional setup or configuration after the primary and secondary contracts have been deployed._ + +Executes post-deployment logic for the primary and secondary contracts. + +#### Parameters + +| Name | Type | Description | +| -------------------------------- | :-------: | -------------------------------------------------------- | +| `primaryContract` | `address` | Address of the deployed primary contract. | +| `secondaryContract` | `address` | Address of the deployed secondary contract. | +| `calldataToPostDeploymentModule` | `bytes` | Calldata to be passed for the post-deployment execution. | + +
From e938d860bd7c8d78e70719c7021f148e40d3590b Mon Sep 17 00:00:00 2001 From: "b00ste.lyx" <62855857+b00ste@users.noreply.github.com> Date: Wed, 23 Aug 2023 19:53:18 +0300 Subject: [PATCH 3/3] refactor!: [C4#95] return when URD has no permission for UP (#672) * refactor: LSP1, LSP5 & LSP10 to not revert * test: add & update tests for refactor * docs: update auto-generated docs --- contracts/LSP10ReceivedVaults/LSP10Utils.sol | 315 ++++----- .../LSP1UniversalReceiverDelegateUP.sol | 326 ++++----- .../LSP1UniversalReceiverDelegateVault.sol | 217 +++--- contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol | 126 ++++ contracts/LSP5ReceivedAssets/LSP5Utils.sol | 323 ++++----- .../GenericExecutorWithBalanceOfFunction.sol | 52 ++ .../LSP1UniversalReceiverDelegateUP.md | 265 +++----- .../LSP1UniversalReceiverDelegateVault.md | 166 +++-- .../LSP10ReceivedVaults/LSP10Utils.md | 166 ++--- .../LSP2ERC725YJSONSchema/LSP2Utils.md | 75 +++ .../libraries/LSP5ReceivedAssets/LSP5Utils.md | 170 ++--- ...P1UniversalReceiverDelegateUP.behaviour.ts | 616 ++++++++++++++---- ...niversalReceiverDelegateVault.behaviour.ts | 180 ++++- 13 files changed, 1713 insertions(+), 1284 deletions(-) create mode 100644 contracts/Mocks/GenericExecutorWithBalanceOfFunction.sol diff --git a/contracts/LSP10ReceivedVaults/LSP10Utils.sol b/contracts/LSP10ReceivedVaults/LSP10Utils.sol index 3a7546a99..869358bc7 100644 --- a/contracts/LSP10ReceivedVaults/LSP10Utils.sol +++ b/contracts/LSP10ReceivedVaults/LSP10Utils.sol @@ -14,37 +14,6 @@ import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; import "../LSP10ReceivedVaults/LSP10Constants.sol"; import "../LSP9Vault/LSP9Constants.sol"; -/** - * @dev Reverts when the value stored under the 'LSP10ReceivedVaults[]' Array data key is not valid. - * The value stored under this data key should be exactly 16 bytes long. - * - * Only possible valid values are: - * - any valid uint128 values - * _e.g: `0x00000000000000000000000000000000` (zero), meaning empty array, no vaults received._ - * _e.g: `0x00000000000000000000000000000005` (non-zero), meaning 5 array elements, 5 vaults received._ - * - * - `0x` (nothing stored under this data key, equivalent to empty array). - * - * @param invalidValueStored The invalid value stored under the `LSP10ReceivedVaults[]` Array data key. - * @param invalidValueLength The invalid number of bytes stored under the `LSP10ReceivedVaults[]` Array data key (MUST be 16 bytes long). - */ -error InvalidLSP10ReceivedVaultsArrayLength( - bytes invalidValueStored, - uint256 invalidValueLength -); - -/** - * @dev Reverts when the `LSP10Vaults[]` Array reaches its maximum limit (`max(uint128)`). - * @param notRegisteredVault The address of the LSP9Vault that could not be registered. - */ -error MaxLSP10VaultsCountReached(address notRegisteredVault); - -/** - * @dev Reverts when the vault index is superior to `max(uint128)`. - * @param index The vault index. - */ -error VaultIndexSuperiorToUint128(uint256 index); - /** * @title LSP10 Utility library. * @author Yamen Merhi , Jean Cavallera @@ -55,200 +24,186 @@ library LSP10Utils { /** * @dev Generate an array of data keys/values pairs to be set on the receiver address after receiving vaults. * + * @custom:warning This function returns empty arrays when encountering errors. Otherwise the arrays will contain 3 data keys and 3 data values. + * * @param receiver The address receiving the vault and where the LSP10 data keys should be added. - * @param vault The address of the vault being received. - * @param vaultMapKey The `LSP10VaultMap:` data key of the vault being received containing the interfaceId of the - * vault and its index in the `LSP10Vaults[]` Array. + * @param vaultAddress The address of the vault being received. * - * @return keys An array of 3 x data keys: `LSP10Vaults[]`, `LSP10Vaults[index]` and `LSP10VaultMap:`. - * @return values An array of 3 x data values: the new length of `LSP10Vaults[]`, the address of the asset under `LSP10Vaults[index]` - * and the interfaceId + index stored under `LSP10VaultsMap:`. + * @return lsp10DataKeys An array data keys used to update the [LSP-10-ReceivedAssets] data. + * @return lsp10DataValues An array data values used to update the [LSP-10-ReceivedAssets] data. */ function generateReceivedVaultKeys( address receiver, - address vault, - bytes32 vaultMapKey - ) internal view returns (bytes32[] memory keys, bytes[] memory values) { - keys = new bytes32[](3); - values = new bytes[](3); - - IERC725Y account = IERC725Y(receiver); - bytes memory encodedArrayLength = getLSP10ReceivedVaultsCount(account); - - // CHECK it's either the first vault received, - // or the storage is already set with a valid `uint128` value - if (encodedArrayLength.length != 0 && encodedArrayLength.length != 16) { - revert InvalidLSP10ReceivedVaultsArrayLength({ - invalidValueStored: encodedArrayLength, - invalidValueLength: encodedArrayLength.length - }); + address vaultAddress + ) + internal + view + returns (bytes32[] memory lsp10DataKeys, bytes[] memory lsp10DataValues) + { + IERC725Y erc725YContract = IERC725Y(receiver); + + /// --- `LSP10Vaults[]` Array --- + + bytes memory currentArrayLengthBytes = getLSP10ArrayLengthBytes( + erc725YContract + ); + + // CHECK that the value of `LSP10Vaults[]` Array length is a valid `uint128` (16 bytes long) + if (!LSP2Utils.isValidLSP2ArrayLengthValue(currentArrayLengthBytes)) { + if (currentArrayLengthBytes.length == 0) { + // if it's the first vault received and nothing is set (= 0x) + // we need to convert it to: `0x00000000000000000000000000000000` + // to safely cast to a uint128 of length 0 + currentArrayLengthBytes = abi.encodePacked(bytes16(0)); + } else { + // otherwise the array length is invalid + return (lsp10DataKeys, lsp10DataValues); + } } - uint128 oldArrayLength = uint128(bytes16(encodedArrayLength)); + uint128 currentArrayLength = uint128(bytes16(currentArrayLengthBytes)); - if (oldArrayLength == type(uint128).max) { - revert MaxLSP10VaultsCountReached({notRegisteredVault: vault}); + // CHECK for potential overflow + if (currentArrayLength == type(uint128).max) { + return (lsp10DataKeys, lsp10DataValues); } - uint128 newArrayLength = oldArrayLength + 1; + // --- `LSP10VaultsMap:` --- + + bytes32 mapDataKey = LSP2Utils.generateMappingKey( + _LSP10_VAULTS_MAP_KEY_PREFIX, + bytes20(vaultAddress) + ); + + // CHECK that the map value is not already set in the storage for the newly received vault + // If that's the case, the vault is already registered. Do not try to update. + if (erc725YContract.getData(mapDataKey).length != 0) { + return (lsp10DataKeys, lsp10DataValues); + } + + /// --- LSP10 Data Keys & Values --- + + lsp10DataKeys = new bytes32[](3); + lsp10DataValues = new bytes[](3); - // store the number of received vaults incremented by 1 - keys[0] = _LSP10_VAULTS_ARRAY_KEY; - values[0] = bytes.concat(bytes16(newArrayLength)); + // Increment `LSP10Vaults[]` Array length + lsp10DataKeys[0] = _LSP10_VAULTS_ARRAY_KEY; + lsp10DataValues[0] = abi.encodePacked(currentArrayLength + 1); - // store the address of the vault under the element key in the array - keys[1] = LSP2Utils.generateArrayElementKeyAtIndex( + // Add asset address to `LSP10Vaults[index]`, where index == previous array length + lsp10DataKeys[1] = LSP2Utils.generateArrayElementKeyAtIndex( _LSP10_VAULTS_ARRAY_KEY, - oldArrayLength + currentArrayLength ); - values[1] = bytes.concat(bytes20(vault)); + lsp10DataValues[1] = abi.encodePacked(vaultAddress); - // store the interfaceId and the location in the array of the asset - // under the LSP5ReceivedAssetMap key - keys[2] = vaultMapKey; - values[2] = bytes.concat(_INTERFACEID_LSP9, bytes16(oldArrayLength)); + // Add interfaceId + index as value under `LSP10VaultsMap:` + lsp10DataKeys[2] = mapDataKey; + lsp10DataValues[2] = bytes.concat( + _INTERFACEID_LSP9, + currentArrayLengthBytes + ); } /** * @dev Generate an array of data key/value pairs to be set on the sender address after sending vaults. * + * @custom:warning Returns empty arrays when encountering errors. Otherwise the arrays must have at least 3 data keys and 3 data values. + * * @param sender The address sending the vault and where the LSP10 data keys should be updated. - * @param vaultMapKey The `LSP10VaultMap:` data key of the vault being sent containing the interfaceId of the - * vault and the index in the `LSP10Vaults[]` Array. - * @param vaultIndex The index at which the vault address is stored under `LSP10Vaults[]` Array. + * @param vaultAddress The address of the vault that is being sent. * - * @return keys An array of 3 x data keys: `LSP10Vaults[]`, `LSP10Vaults[index]` and `LSP10VaultsMap:`. - * @return values An array of 3 x data values: the new length of `LSP10Vaults[]`, the address of the asset under `LSP10Vaults[index]` - * and the interfaceId + index stored under `LSP10VaultsMap:`. + * @return lsp10DataKeys An array data keys used to update the [LSP-10-ReceivedAssets] data. + * @return lsp10DataValues An array data values used to update the [LSP-10-ReceivedAssets] data. */ function generateSentVaultKeys( address sender, - bytes32 vaultMapKey, - uint128 vaultIndex - ) internal view returns (bytes32[] memory keys, bytes[] memory values) { - IERC725Y account = IERC725Y(sender); - bytes memory lsp10VaultsCountValue = getLSP10ReceivedVaultsCount( - account + address vaultAddress + ) + internal + view + returns (bytes32[] memory lsp10DataKeys, bytes[] memory lsp10DataValues) + { + IERC725Y erc725YContract = IERC725Y(sender); + + // --- `LSP10Vaults[]` Array --- + + bytes memory newArrayLengthBytes = getLSP10ArrayLengthBytes( + erc725YContract ); - if (lsp10VaultsCountValue.length != 16) { - revert InvalidLSP10ReceivedVaultsArrayLength({ - invalidValueStored: lsp10VaultsCountValue, - invalidValueLength: lsp10VaultsCountValue.length - }); + // CHECK that the value of `LSP10Vaults[]` Array length is a valid `uint128` (16 bytes long) + if (!LSP2Utils.isValidLSP2ArrayLengthValue(newArrayLengthBytes)) { + return (lsp10DataKeys, lsp10DataValues); } - // Updating the number of the received vaults - uint128 oldArrayLength = uint128(bytes16(lsp10VaultsCountValue)); - - if (oldArrayLength > type(uint128).max) { - revert VaultIndexSuperiorToUint128(oldArrayLength); + // CHECK for potential underflow + if ( + newArrayLengthBytes.length == 0 || + bytes16(newArrayLengthBytes) == bytes16(0) + ) { + return (lsp10DataKeys, lsp10DataValues); } - // Updating the number of the received vaults (decrementing by 1 - uint128 newArrayLength = oldArrayLength - 1; + uint128 newArrayLength = uint128(bytes16(newArrayLengthBytes)) - 1; - // Generate the element key in the array of the vault - bytes32 vaultInArrayKey = LSP2Utils.generateArrayElementKeyAtIndex( - _LSP10_VAULTS_ARRAY_KEY, - vaultIndex - ); + // --- `LSP10VaultssMap:` --- - // If the asset to remove is the last element in the array - if (vaultIndex == newArrayLength) { - /** - * We will be updating/removing 3 keys: - * - Keys[0]: [Update] The arrayLengthKey to contain the new number of the received vaults - * - Keys[1]: [Remove] The element in arrayKey (Remove the address of the vault sent) - * - Keys[2]: [Remove] The mapKey (Remove the interfaceId and the index of the vault sent) - */ - keys = new bytes32[](3); - values = new bytes[](3); - - // store the number of received vaults decremented by 1 - keys[0] = _LSP10_VAULTS_ARRAY_KEY; - values[0] = bytes.concat(bytes16(newArrayLength)); - - // remove the address of the vault from the element key - keys[1] = vaultInArrayKey; - values[1] = ""; - - // remove the interfaceId and the location in the array of the vault - keys[2] = vaultMapKey; - values[2] = ""; - - // Swapping last element in ArrayKey with the element in ArrayKey to remove || {Swap and pop} method; - // check https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/EnumerableSet.sol#L80 - } else if (vaultIndex < newArrayLength) { - /** - * We will be updating/removing 5 keys: - * - Keys[0]: [Update] The arrayLengthKey to contain the new number of the received vaults - * - Keys[1]: [Remove] The mapKey of the vault to remove (Remove the interfaceId and the index of the vault sent) - * - Keys[2]: [Update] The element in arrayKey to remove (Swap with the address of the last element in Array) - * - Keys[3]: [Remove] The last element in arrayKey (Remove (pop) the address of the last element as it's already swapped) - * - Keys[4]: [Update] The mapKey of the last element in array (Update the new index and the interfaceID) - */ - keys = new bytes32[](5); - values = new bytes[](5); - - // store the number of received vaults decremented by 1 - keys[0] = _LSP10_VAULTS_ARRAY_KEY; - values[0] = bytes.concat(bytes16(newArrayLength)); - - // remove the interfaceId and the location in the array of the vault - keys[1] = vaultMapKey; - values[1] = ""; - - // Generate all data Keys/values of the last element in Array to swap - // with data Keys/values of the vault to remove - - // Generate the element key of the last vault in the array - bytes32 lastVaultInArrayKey = LSP2Utils - .generateArrayElementKeyAtIndex( - _LSP10_VAULTS_ARRAY_KEY, - newArrayLength - ); + bytes32 removedElementMapKey = LSP2Utils.generateMappingKey( + _LSP10_VAULTS_MAP_KEY_PREFIX, + bytes20(vaultAddress) + ); - // Get the address of the vault from the element key of the last vault in the array - bytes20 lastVaultInArrayAddress = bytes20( - account.getData(lastVaultInArrayKey) - ); + // Query the ERC725Y storage of the LSP0-ERC725Account + bytes memory mapValue = erc725YContract.getData(removedElementMapKey); - // Generate the map key of the last vault in the array - bytes32 lastVaultInArrayMapKey = LSP2Utils.generateMappingKey( - _LSP10_VAULTS_MAP_KEY_PREFIX, - lastVaultInArrayAddress - ); + // CHECK if no map value was set for the vault to remove. + // If that's the case, there is nothing to remove. Do not try to update. + if (mapValue.length != 20) { + return (lsp10DataKeys, lsp10DataValues); + } - // Set the address of the last vault instead of the asset to be sent - // under the element data key in the array - keys[2] = vaultInArrayKey; - values[2] = bytes.concat(lastVaultInArrayAddress); + // Extract index of vault to remove from the map value + uint128 removedElementIndex = uint128(bytes16(bytes20(mapValue) << 32)); - // Remove the address swapped (last vault in the array) from the last element data key in the array - keys[3] = lastVaultInArrayKey; - values[3] = ""; + bytes32 removedElementIndexKey = LSP2Utils + .generateArrayElementKeyAtIndex( + _LSP10_VAULTS_ARRAY_KEY, + uint128(removedElementIndex) + ); - // Update the index and the interfaceId of the address swapped (last vault in the array) - // to point to the new location in the LSP10Vaults array - keys[4] = lastVaultInArrayMapKey; - values[4] = bytes.concat(_INTERFACEID_LSP9, bytes16(vaultIndex)); + if (removedElementIndex == newArrayLength) { + return + LSP2Utils.removeLastElementFromArrayAndMap( + _LSP10_VAULTS_ARRAY_KEY, + newArrayLength, + removedElementIndexKey, + removedElementMapKey + ); + } else if (removedElementIndex < newArrayLength) { + return + LSP2Utils.removeElementFromArrayAndMap( + erc725YContract, + _LSP10_VAULTS_ARRAY_KEY, + newArrayLength, + removedElementIndexKey, + removedElementIndex, + removedElementMapKey + ); } else { // If index is bigger than the array length, out of bounds - return (keys, values); + return (lsp10DataKeys, lsp10DataValues); } } /** - * @dev Get the total number of vault addresses stored under the `LSP10Vaults[]` Array data key. - * @param account The ERC725Y smart contract to read the storage from. - * @return The raw bytes stored under the `LSP10Vaults[]` data key. - * - * @custom:info This function does not return a number but the raw bytes stored under the `LSP10Vaults[]` Array data key. + * @dev Get the raw bytes value stored under the `_LSP10_VAULTS_ARRAY_KEY`. + * @param erc725YContract The contract to query the ERC725Y storage from. + * @return The raw bytes value stored under this data key. */ - function getLSP10ReceivedVaultsCount( - IERC725Y account + function getLSP10ArrayLengthBytes( + IERC725Y erc725YContract ) internal view returns (bytes memory) { - return account.getData(_LSP10_VAULTS_ARRAY_KEY); + return erc725YContract.getData(_LSP10_VAULTS_ARRAY_KEY); } } diff --git a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol index f509db2c3..eef3894e2 100644 --- a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol +++ b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol @@ -18,6 +18,20 @@ import { import {LSP1Utils} from "../LSP1Utils.sol"; import {LSP2Utils} from "../../LSP2ERC725YJSONSchema/LSP2Utils.sol"; import {LSP5Utils} from "../../LSP5ReceivedAssets/LSP5Utils.sol"; +import { + _TYPEID_LSP7_TOKENSSENDER, + _TYPEID_LSP7_TOKENSRECIPIENT, + _INTERFACEID_LSP7 +} from "../../LSP7DigitalAsset/LSP7Constants.sol"; +import { + _TYPEID_LSP8_TOKENSSENDER, + _TYPEID_LSP8_TOKENSRECIPIENT, + _INTERFACEID_LSP8 +} from "../../LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; +import { + _TYPEID_LSP9_OwnershipTransferred_SenderNotification, + _TYPEID_LSP9_OwnershipTransferred_RecipientNotification +} from "../../LSP9Vault/LSP9Constants.sol"; import {LSP10Utils} from "../../LSP10ReceivedVaults/LSP10Utils.sol"; // constants @@ -59,182 +73,196 @@ contract LSP1UniversalReceiverDelegateUP is ERC165, ILSP1UniversalReceiver { * - This contract should be allowed to use the {setDataBatch(...)} function in order to update the LSP5 and LSP10 Data Keys. * - Cannot accept native tokens * + * @custom:info + * - If some issues occured with generating the `dataKeys` or `dataValues` the `returnedMessage` will be an error message, otherwise it will be empty. + * - If an error occured when trying to use `setDataBatch(dataKeys,dataValues)`, it will return the raw error data back to the caller. + * * @param typeId Unique identifier for a specific notification. - * @return result The result of the reaction for `typeId`. + * @return The result of the reaction for `typeId`. */ function universalReceiver( bytes32 typeId, bytes memory /* data */ - ) public payable virtual returns (bytes memory result) { - if (msg.value != 0) revert NativeTokensNotAccepted(); + ) public payable virtual returns (bytes memory) { + // CHECK that we did not send any native tokens to the LSP1 Delegate, as it cannot transfer them back. + if (msg.value != 0) { + revert NativeTokensNotAccepted(); + } - // This contract acts like a UniversalReceiverDelegate of an LSP0ERC725Account where we append the - // address and the value, sent to the universalReceiver function of the LSP0, to the msg.data - // Check https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-0-ERC725Account.md#universalreceiver address notifier = address(bytes20(msg.data[msg.data.length - 52:])); - // Get the supposed mapPrefix and interfaceId based on the typeID - ( - bool invalid, - bytes10 mapPrefix, - bytes4 interfaceID, - bool isReceiving - ) = LSP1Utils.getTransferDetails(typeId); - - // If it's a typeId different than LSP7/LSP8/LSP9 typeIds - if (invalid) return "LSP1: typeId out of scope"; - // The notifier is supposed to be either the LSP7 or LSP8 or LSP9 contract // If it's EOA we revert to avoid registering the EOA as asset or vault (spam protection) // solhint-disable avoid-tx-origin - if (notifier == tx.origin) revert CannotRegisterEOAsAsAssets(notifier); - - // Generate the LSP5ReceivedAssetsMap/LSP10VaultsMap based on the prefix and the notifier - bytes32 notifierMapKey = LSP2Utils.generateMappingKey( - mapPrefix, - bytes20(notifier) - ); - - // Query the ERC725Y storage of the LSP0-ERC725Account - bytes memory notifierMapValue = IERC725Y(msg.sender).getData( - notifierMapKey - ); - - bool isMapValueSet = bytes20(notifierMapValue) != bytes20(0); - - if (isReceiving) { - // If the mapValue is set, we assume that all other data keys relevant to the asset/vault - // are registered in the account, we don't need to re register the asset being received - if (isMapValueSet) - return "LSP1: asset received is already registered"; - - return - _whenReceiving(typeId, notifier, notifierMapKey, interfaceID); - } else { - // If the mapValue is not set, we assume that all other data keys relevant to the asset/vault - // are not registered in the account, we cannot remove non-existing data keys for the asset being sent - if (!isMapValueSet) return "LSP1: asset sent is not registered"; - - // if the value under the `LSP5ReceivedAssetsMap:` or `LSP10VaultsMap:` - // is not a valid tuple as `(bytes4,uint128)` - if (notifierMapValue.length < 20) - return "LSP1: asset data corrupted"; - - // Identify where the asset/vault is located in the `LSP5ReceivedAssets[]` / `LSP10Vaults[]` Array - // by extracting the index from the tuple value `(bytes4,uint128)` - // fetched under the `LSP5ReceivedAssetsMap` / `LSP10VaultsMap` data key - uint128 arrayIndex = uint128(uint160(bytes20(notifierMapValue))); - - return _whenSending(typeId, notifier, notifierMapKey, arrayIndex); + if (notifier == tx.origin) { + revert CannotRegisterEOAsAsAssets(notifier); + } + + if (typeId == _TYPEID_LSP7_TOKENSSENDER) { + return _tokenSender(notifier); + } + + if (typeId == _TYPEID_LSP7_TOKENSRECIPIENT) { + return _tokenRecipient(notifier, _INTERFACEID_LSP7); + } + + if (typeId == _TYPEID_LSP8_TOKENSSENDER) { + return _tokenSender(notifier); + } + + if (typeId == _TYPEID_LSP8_TOKENSRECIPIENT) { + return _tokenRecipient(notifier, _INTERFACEID_LSP8); } + + if (typeId == _TYPEID_LSP9_OwnershipTransferred_SenderNotification) { + return _vaultSender(notifier); + } + + if (typeId == _TYPEID_LSP9_OwnershipTransferred_RecipientNotification) { + return _vaultRecipient(notifier); + } + + return "LSP1: typeId out of scope"; } - // --- Internal functions + /** + * @dev Handler for LSP7 and LSP8 token sender type id. + * + * @custom:info + * - Tries to generate LSP5 data key/value pairs for removing asset from the ERC725Y storage. + * - Tries to use `setDataBatch(bytes32[],bytes[])` if generated proper LSP5 data key/value pairs. + * - Does not revert. But returns an error message. Use off-chain lib to get even more info. + * + * @param notifier The LSP7 or LSP8 token address. + */ + function _tokenSender(address notifier) internal returns (bytes memory) { + // if the amount sent is not the full balance, then do not update the keys + try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns ( + uint256 balance + ) { + if (balance != 0) { + return "LSP1: full balance is not sent"; + } + } catch { + return "LSP1: `balanceOf(address)` function not found"; + } + + (bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP5Utils + .generateSentAssetKeys(msg.sender, notifier); + + // `generateSentAssetKeys(...)` returns empty arrays when encountering errors + if (dataKeys.length == 0 && dataValues.length == 0) { + return "LSP5: Error generating data key/value pairs"; + } + + // Set the LSP5 generated data keys on the account + return _setDataBatchWithoutReverting(dataKeys, dataValues); + } /** - * @dev To avoid stack too deep error - * Generate the keys/values of the asset/vault received to set and set them - * on the account depending on the type of the transfer (asset/vault) + * @dev Handler for LSP7 and LSP8 token recipient type id. + * + * @custom:info + * - Tries to generate LSP5 data key/value pairs for adding asset to the ERC725Y storage. + * - Tries to use `setDataBatch(bytes32[],bytes[])` if generated proper LSP5 data key/value pairs. + * - Does not revert. But returns an error message. Use off-chain lib to get even more info. + * + * @param notifier The LSP7 or LSP8 token address. + * @param interfaceId The LSP7 or LSP8 interface id. */ - function _whenReceiving( - bytes32 typeId, + function _tokenRecipient( address notifier, - bytes32 notifierMapKey, - bytes4 interfaceID - ) internal virtual returns (bytes memory) { - bytes32[] memory dataKeys; - bytes[] memory dataValues; - - // if it's a token transfer (LSP7/LSP8) - if (typeId != _TYPEID_LSP9_OwnershipTransferred_RecipientNotification) { - // CHECK balance only when the Token contract is already deployed, - // not when tokens are being transferred on deployment through the `constructor` - if (notifier.code.length > 0) { - // if the amount sent is 0, then do not update the keys - uint256 balance = ILSP7DigitalAsset(notifier).balanceOf( - msg.sender - ); - if (balance == 0) return "LSP1: balance not updated"; + bytes4 interfaceId + ) internal returns (bytes memory) { + // CHECK balance only when the Token contract is already deployed, + // not when tokens are being transferred on deployment through the `constructor` + if (notifier.code.length > 0) { + // if the amount sent is 0, then do not update the keys + try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns ( + uint256 balance + ) { + if (balance == 0) { + return "LSP1: balance is zero"; + } + } catch { + return "LSP1: `balanceOf(address)` function not found"; } + } - (dataKeys, dataValues) = LSP5Utils.generateReceivedAssetKeys( - msg.sender, - notifier, - notifierMapKey, - interfaceID - ); + (bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP5Utils + .generateReceivedAssetKeys(msg.sender, notifier, interfaceId); - // Set the LSP5 generated data keys on the account - IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues); - return ""; - } else { - (dataKeys, dataValues) = LSP10Utils.generateReceivedVaultKeys( - msg.sender, - notifier, - notifierMapKey - ); - - // Set the LSP10 generated data keys on the account - IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues); - return ""; + // `generateReceivedAssetKeys(...)` returns empty arrays when encountering errors + if (dataKeys.length == 0 && dataValues.length == 0) { + return "LSP5: Error generating data key/value pairs"; } + + // Set the LSP5 generated data keys on the account + return _setDataBatchWithoutReverting(dataKeys, dataValues); } /** - * @dev To avoid stack too deep error - * Generate the keys/values of the asset/vault sent to set and set them - * on the account depending on the type of the transfer (asset/vault) + * @dev Handler for LSP9 vault sender type id. + * + * @custom:info + * - Tries to generate LSP10 data key/value pairs for removing vault from the ERC725Y storage. + * - Tries to use `setDataBatch(bytes32[],bytes[])` if generated proper LSP10 data key/value pairs. + * - Does not revert. But returns an error message. Use off-chain lib to get even more info. + * + * @param notifier The LSP9 vault address. */ - function _whenSending( - bytes32 typeId, - address notifier, - bytes32 notifierMapKey, - uint128 arrayIndex - ) internal virtual returns (bytes memory) { - bytes32[] memory dataKeys; - bytes[] memory dataValues; - - // if it's a token transfer (LSP7/LSP8) - if (typeId != _TYPEID_LSP9_OwnershipTransferred_SenderNotification) { - // if the amount sent is not the full balance, then do not update the keys - uint256 balance = ILSP7DigitalAsset(notifier).balanceOf(msg.sender); - if (balance != 0) return "LSP1: full balance is not sent"; - - (dataKeys, dataValues) = LSP5Utils.generateSentAssetKeys( - msg.sender, - notifierMapKey, - arrayIndex - ); - - /** - * `generateSentAssetKeys(...)` returns empty arrays in the following cases: - * - the index returned from the data key `notifierMapKey` is bigger than - * the length of the `LSP5ReceivedAssets[]`, meaning, index is out of bounds. - */ - if (dataKeys.length == 0 && dataValues.length == 0) - return "LSP1: asset data corrupted"; - - // Set the LSP5 generated data keys on the account - IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues); - return ""; - } else { - (dataKeys, dataValues) = LSP10Utils.generateSentVaultKeys( - msg.sender, - notifierMapKey, - arrayIndex - ); - - /** - * `generateSentAssetKeys(...)` returns empty arrays in the following cases: - * - the index returned from the data key `notifierMapKey` is bigger than - * the length of the `LSP10Vaults[]`, meaning, index is out of bounds. - */ - if (dataKeys.length == 0 && dataValues.length == 0) - return "LSP1: asset data corrupted"; - - // Set the LSP10 generated data keys on the account - IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues); + function _vaultSender(address notifier) internal returns (bytes memory) { + (bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP10Utils + .generateSentVaultKeys(msg.sender, notifier); + + // `generateSentVaultKeys(...)` returns empty arrays when encountering errors + if (dataKeys.length == 0 && dataValues.length == 0) { + return "LSP10: Error generating data key/value pairs"; + } + + // Set the LSP10 generated data keys on the account + return _setDataBatchWithoutReverting(dataKeys, dataValues); + } + + /** + * @dev Handler for LSP9 vault recipient type id. + * + * @custom:info + * - Tries to generate LSP5 data key/value pairs for adding vault to the ERC725Y storage. + * - Tries to use `setDataBatch(bytes32[],bytes[])` if generated proper LSP5 data key/value pairs. + * - Does not revert. But returns an error message. Use off-chain lib to get even more info. + * + * @param notifier The LSP9 vault address. + */ + function _vaultRecipient(address notifier) internal returns (bytes memory) { + (bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP10Utils + .generateReceivedVaultKeys(msg.sender, notifier); + + // `generateReceivedVaultKeys(...)` returns empty arrays when encountering errors + if (dataKeys.length == 0 && dataValues.length == 0) { + return "LSP10: Error generating data key/value pairs"; + } + + // Set the LSP10 generated data keys on the account + return _setDataBatchWithoutReverting(dataKeys, dataValues); + } + + /** + * @dev Calls `bytes4(keccak256(setDataBatch(bytes32[],bytes[])))` without checking for `bool success`, but it returns all the data back. + * + * @custom:info If an the low-level transaction revert, the returned data will be forwarded. Th contract that uses this function can use the `Address` library to revert with the revert reason. + * + * @param dataKeys Data Keys to be set. + * @param dataValues Data Values to be set. + */ + function _setDataBatchWithoutReverting( + bytes32[] memory dataKeys, + bytes[] memory dataValues + ) internal returns (bytes memory) { + try IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues) { return ""; + } catch (bytes memory errorData) { + return errorData; } } diff --git a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol index 84b463ed4..669049f06 100644 --- a/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol +++ b/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol @@ -18,6 +18,16 @@ import {LSP5Utils} from "../../LSP5ReceivedAssets/LSP5Utils.sol"; // constants import "../LSP1Constants.sol"; +import { + _TYPEID_LSP7_TOKENSSENDER, + _TYPEID_LSP7_TOKENSRECIPIENT, + _INTERFACEID_LSP7 +} from "../../LSP7DigitalAsset/LSP7Constants.sol"; +import { + _TYPEID_LSP8_TOKENSSENDER, + _TYPEID_LSP8_TOKENSRECIPIENT, + _INTERFACEID_LSP8 +} from "../../LSP8IdentifiableDigitalAsset/LSP8Constants.sol"; import "../../LSP9Vault/LSP9Constants.sol"; // errors @@ -43,101 +53,142 @@ contract LSP1UniversalReceiverDelegateVault is ERC165, ILSP1UniversalReceiver { * @notice Reacted on received notification with `typeId`. * * @custom:requirements Cannot accept native tokens. + * @custom:info + * - If some issues occured with generating the `dataKeys` or `dataValues` the `returnedMessage` will be an error message, otherwise it will be empty. + * - If an error occured when trying to use `setDataBatch(dataKeys,dataValues)`, it will return the raw error data back to the caller. * * @param typeId Unique identifier for a specific notification. - * @return result The result of the reaction for `typeId`. + * @return The result of the reaction for `typeId`. */ function universalReceiver( bytes32 typeId, bytes memory /* data */ - ) public payable virtual returns (bytes memory result) { - if (msg.value != 0) revert NativeTokensNotAccepted(); - // This contract acts like a UniversalReceiverDelegate of a Vault where we append the - // address and the value, sent to the universalReceiver function of the LSP9, to the msg.data - // Check https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-9-Vault.md#universalreceiver + ) public payable virtual returns (bytes memory) { + // CHECK that we did not send any native tokens to the LSP1 Delegate, as it cannot transfer them back. + if (msg.value != 0) { + revert NativeTokensNotAccepted(); + } + address notifier = address(bytes20(msg.data[msg.data.length - 52:])); - ( - bool invalid, - bytes10 mapPrefix, - bytes4 interfaceID, - bool isReceiving - ) = LSP1Utils.getTransferDetails(typeId); + // The notifier is supposed to be either the LSP7 or LSP8 contract + // If it's EOA we revert to avoid registering the EOA as asset (spam protection) + // solhint-disable avoid-tx-origin + if (notifier == tx.origin) { + revert CannotRegisterEOAsAsAssets(notifier); + } - if (invalid || interfaceID == _INTERFACEID_LSP9) - return "LSP1: typeId out of scope"; + if (typeId == _TYPEID_LSP7_TOKENSSENDER) { + return _tokenSender(notifier); + } - // solhint-disable avoid-tx-origin - if (notifier == tx.origin) revert CannotRegisterEOAsAsAssets(notifier); - - bytes32 notifierMapKey = LSP2Utils.generateMappingKey( - mapPrefix, - bytes20(notifier) - ); - bytes memory notifierMapValue = IERC725Y(msg.sender).getData( - notifierMapKey - ); - - bytes32[] memory dataKeys; - bytes[] memory dataValues; - - if (isReceiving) { - // if the map value is already set, then do nothing - if (bytes20(notifierMapValue) != bytes20(0)) - return "URD: asset received is already registered"; - - // CHECK balance only when the Token contract is already deployed, - // not when tokens are being transferred on deployment through the `constructor` - if (notifier.code.length > 0) { - // if the amount sent is 0, then do not update the keys - uint256 balance = ILSP7DigitalAsset(notifier).balanceOf( - msg.sender - ); - if (balance == 0) return "LSP1: balance not updated"; + if (typeId == _TYPEID_LSP7_TOKENSRECIPIENT) { + return _tokenRecipient(notifier, _INTERFACEID_LSP7); + } + + if (typeId == _TYPEID_LSP8_TOKENSSENDER) { + return _tokenSender(notifier); + } + + if (typeId == _TYPEID_LSP8_TOKENSRECIPIENT) { + return _tokenRecipient(notifier, _INTERFACEID_LSP8); + } + + return "LSP1: typeId out of scope"; + } + + /** + * @dev Handler for LSP7 and LSP8 token sender type id. + * + * @custom:info + * - Tries to generate LSP5 data key/value pairs for removing asset from the ERC725Y storage. + * - Tries to use `setDataBatch(bytes32[],bytes[])` if generated proper LSP5 data key/value pairs. + * - Does not revert. But returns an error message. Use off-chain lib to get even more info. + * + * @param notifier The LSP7 or LSP8 token address. + */ + function _tokenSender(address notifier) internal returns (bytes memory) { + // if the amount sent is not the full balance, then do not update the keys + try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns ( + uint256 balance + ) { + if (balance != 0) { + return "LSP1: full balance is not sent"; } + } catch { + return "LSP1: `balanceOf(address)` function not found"; + } + + (bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP5Utils + .generateSentAssetKeys(msg.sender, notifier); - (dataKeys, dataValues) = LSP5Utils.generateReceivedAssetKeys( - msg.sender, - notifier, - notifierMapKey, - interfaceID - ); - - IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues); - } else { - // if there is no map value for the asset to remove, then do nothing - if (bytes20(notifierMapValue) == bytes20(0)) - return "LSP1: asset sent is not registered"; - - // if it's a token transfer (LSP7/LSP8) - uint256 balance = ILSP7DigitalAsset(notifier).balanceOf(msg.sender); - if (balance != 0) return "LSP1: full balance is not sent"; - - // if the value under the `LSP5ReceivedAssetsMap:` - // is not a valid tuple as `(bytes4,uint128)` - if (notifierMapValue.length < 20) - return "LSP1: asset data corrupted"; - - // Identify where the asset is located in the `LSP5ReceivedAssets[]` Array - // by extracting the index from the tuple value `(bytes4,uint128)` - // fetched under the LSP5ReceivedAssetsMap/LSP10VaultsMap data key - uint128 assetIndex = uint128(uint160(bytes20(notifierMapValue))); - - (dataKeys, dataValues) = LSP5Utils.generateSentAssetKeys( - msg.sender, - notifierMapKey, - assetIndex - ); - - /** - * `generateSentAssetKeys(...)` returns empty arrays in the following cases: - * - the index returned from the data key `notifierMapKey` is bigger than - * the length of the `LSP5ReceivedAssets[]`, meaning, index is out of bounds. - */ - if (dataKeys.length == 0 && dataValues.length == 0) - return "LSP1: asset data corrupted"; - - IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues); + // `generateSentAssetKeys(...)` returns empty arrays when encountering errors + if (dataKeys.length == 0 && dataValues.length == 0) { + return "LSP5: Error generating data key/value pairs"; + } + + // Set the LSP5 generated data keys on the account + return _setDataBatchWithoutReverting(dataKeys, dataValues); + } + + /** + * @dev Handler for LSP7 and LSP8 token recipient type id. + * + * @custom:info + * - Tries to generate LSP5 data key/value pairs for adding asset to the ERC725Y storage. + * - Tries to use `setDataBatch(bytes32[],bytes[])` if generated proper LSP5 data key/value pairs. + * - Does not revert. But returns an error message. Use off-chain lib to get even more info. + * + * @param notifier The LSP7 or LSP8 token address. + * @param interfaceId The LSP7 or LSP8 interface id. + */ + function _tokenRecipient( + address notifier, + bytes4 interfaceId + ) internal returns (bytes memory) { + // CHECK balance only when the Token contract is already deployed, + // not when tokens are being transferred on deployment through the `constructor` + if (notifier.code.length > 0) { + // if the amount sent is 0, then do not update the keys + try ILSP7DigitalAsset(notifier).balanceOf(msg.sender) returns ( + uint256 balance + ) { + if (balance == 0) { + return "LSP1: balance is zero"; + } + } catch { + return "LSP1: `balanceOf(address)` function not found"; + } + } + + (bytes32[] memory dataKeys, bytes[] memory dataValues) = LSP5Utils + .generateReceivedAssetKeys(msg.sender, notifier, interfaceId); + + // `generateReceivedAssetKeys(...)` returns empty arrays when encountering errors + if (dataKeys.length == 0 && dataValues.length == 0) { + return "LSP5: Error generating data key/value pairs"; + } + + // Set the LSP5 generated data keys on the account + return _setDataBatchWithoutReverting(dataKeys, dataValues); + } + + /** + * @dev Calls `bytes4(keccak256(setDataBatch(bytes32[],bytes[])))` without checking for `bool succes`, but it returns all the data back. + * + * @custom:info If an the low-level transaction revert, the returned data will be forwarded. Th contract that uses this function can use the `Address` library to revert with the revert reason. + * + * @param dataKeys Data Keys to be set. + * @param dataValues Data Values to be set. + */ + function _setDataBatchWithoutReverting( + bytes32[] memory dataKeys, + bytes[] memory dataValues + ) internal returns (bytes memory) { + try IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues) { + return ""; + } catch (bytes memory errorData) { + return errorData; } } diff --git a/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol b/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol index 65440b60f..30fbac727 100644 --- a/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol +++ b/contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol @@ -1,6 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.4; +// interfaces +import { + IERC725Y +} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; + // libraries import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; @@ -415,4 +420,125 @@ library LSP2Utils { if (pointer == compactBytesArray.length) return true; return false; } + + /** + * @dev Validates if the bytes `arrayLength` are exactly 16 bytes long, and are of the exact size of an LSP2 Array length value + * + * @param arrayLength Plain bytes that should be validated. + * + * @return `true` if the value is 16 bytes long, `false` otherwise. + */ + function isValidLSP2ArrayLengthValue( + bytes memory arrayLength + ) internal pure returns (bool) { + if (arrayLength.length == 16) { + return true; + } + return false; + } + + /** + * @dev Generates Data Key/Value pairs for removing the last element from an LSP2 Array and a mapping Data Key. + * + * @param arrayKey The Data Key of Key Type Array. + * @param newArrayLength The new Array Length for the `arrayKey`. + * @param removedElementIndexKey The Data Key of Key Type Array Index for the removed element. + * @param removedElementMapKey The Data Key of a mapping to be removed. + */ + function removeLastElementFromArrayAndMap( + bytes32 arrayKey, + uint128 newArrayLength, + bytes32 removedElementIndexKey, + bytes32 removedElementMapKey + ) + internal + pure + returns (bytes32[] memory dataKeys, bytes[] memory dataValues) + { + dataKeys = new bytes32[](3); + dataValues = new bytes[](3); + + // store the number of received assets decremented by 1 + dataKeys[0] = arrayKey; + dataValues[0] = abi.encodePacked(newArrayLength); + + // remove the data value for the map key of the element + dataKeys[1] = removedElementMapKey; + dataValues[1] = ""; + + // remove the data value for the map key of the element + dataKeys[2] = removedElementIndexKey; + dataValues[2] = ""; + } + + /** + * @dev Generates Data Key/Value pairs for removing an element from an LSP2 Array and a mapping Data Key. + * + * @custom:info The function assumes that the Data Value stored under the mapping Data Key is of length 20 where the last 16 bytes are the index of the element in the array. + * + * @param ERC725YContract The ERC725Y contract. + * @param arrayKey The Data Key of Key Type Array. + * @param newArrayLength The new Array Length for the `arrayKey`. + * @param removedElementIndexKey The Data Key of Key Type Array Index for the removed element. + * @param removedElementIndex the index of the removed element. + * @param removedElementMapKey The Data Key of a mapping to be removed. + */ + function removeElementFromArrayAndMap( + IERC725Y ERC725YContract, + bytes32 arrayKey, + uint128 newArrayLength, + bytes32 removedElementIndexKey, + uint128 removedElementIndex, + bytes32 removedElementMapKey + ) + internal + view + returns (bytes32[] memory dataKeys, bytes[] memory dataValues) + { + dataKeys = new bytes32[](5); + dataValues = new bytes[](5); + + // store the number of received assets decremented by 1 + dataKeys[0] = arrayKey; + dataValues[0] = abi.encodePacked(newArrayLength); + + // remove the data value for the map key of the element + dataKeys[1] = removedElementMapKey; + dataValues[1] = ""; + + // Generate the key of the last element in the array + bytes32 lastElementIndexKey = LSP2Utils.generateArrayElementKeyAtIndex( + arrayKey, + newArrayLength + ); + + // Get the data value from the key of the last element in the array + bytes20 lastElementIndexValue = bytes20( + ERC725YContract.getData(lastElementIndexKey) + ); + + // Set data value of the last element instead of the element from the array that will be removed + dataKeys[2] = removedElementIndexKey; + dataValues[2] = bytes.concat(lastElementIndexValue); + + // Remove the data value for the swapped array element + dataKeys[3] = lastElementIndexKey; + dataValues[3] = ""; + + // Generate mapping key for the swapped array element + bytes32 lastElementMapKey = LSP2Utils.generateMappingKey( + bytes10(removedElementMapKey), + lastElementIndexValue + ); + + // Generate the mapping value for the swapped array element + bytes memory lastElementMapValue = abi.encodePacked( + bytes4(ERC725YContract.getData(lastElementMapKey)), + removedElementIndex + ); + + // Update the map value of the swapped array element to the new index + dataKeys[4] = lastElementMapKey; + dataValues[4] = lastElementMapValue; + } } diff --git a/contracts/LSP5ReceivedAssets/LSP5Utils.sol b/contracts/LSP5ReceivedAssets/LSP5Utils.sol index 5cf9b9f24..cea4fdf93 100644 --- a/contracts/LSP5ReceivedAssets/LSP5Utils.sol +++ b/contracts/LSP5ReceivedAssets/LSP5Utils.sol @@ -14,37 +14,6 @@ import {LSP2Utils} from "../LSP2ERC725YJSONSchema/LSP2Utils.sol"; import "../LSP5ReceivedAssets/LSP5Constants.sol"; import "../LSP7DigitalAsset/LSP7Constants.sol"; -/** - * @dev Reverts when the value stored under the 'LSP5ReceivedAssets[]' Array data key is not valid. - * The value stored under this data key should be exactly 16 bytes long. - * - * Only possible valid values are: - * - any valid uint128 values - * _e.g: `0x00000000000000000000000000000000` (zero), empty array, no assets received._ - * _e.g. `0x00000000000000000000000000000005` (non-zero), 5 array elements, 5 assets received._ - * - * - `0x` (nothing stored under this data key, equivalent to empty array) - * - * @param invalidValueStored The invalid value stored under the `LSP5ReceivedAssets[]` Array data key. - * @param invalidValueLength The invalid number of bytes stored under the `LSP5ReceivedAssets[]` data key (MUST be exactly 16 bytes long). - */ -error InvalidLSP5ReceivedAssetsArrayLength( - bytes invalidValueStored, - uint256 invalidValueLength -); - -/** - * @dev Reverts when the `LSP5ReceivedAssets[]` Array reaches its maximum limit (`max(uint128)`). - * @param notRegisteredAsset The address of the asset that could not be registered. - */ -error MaxLSP5ReceivedAssetsCountReached(address notRegisteredAsset); - -/** - * @dev Reverts when the received assets index is superior to `max(uint128)`. - * @param index The received assets index. - */ -error ReceivedAssetsIndexSuperiorToUint128(uint256 index); - /** * @title LSP5 Utility library. * @author Yamen Merhi , Jean Cavallera @@ -55,211 +24,185 @@ library LSP5Utils { /** * @dev Generate an array of data key/value pairs to be set on the receiver address after receiving assets. * + * @custom:warning Returns empty arrays when encountering errors. Otherwise the arrays must have 3 data keys and 3 data values. + * * @param receiver The address receiving the asset and where the LSP5 data keys should be added. - * @param asset The address of the asset being received (_e.g: an LSP7 or LSP8 token_). - * @param assetMapKey The `LSP5ReceivedAssetMap:` data key of the asset being received containing the interfaceId of the - * asset and its index in the `LSP5ReceivedAssets[]` Array. - * @param interfaceID The interfaceID of the asset being received. + * @param assetAddress The address of the asset being received (_e.g: an LSP7 or LSP8 token_). + * @param assetInterfaceId The interfaceID of the asset being received. * - * @return keys An array of 3 x data keys: `LSP5ReceivedAssets[]`, `LSP5ReceivedAssets[index]` and `LSP5ReceivedAssetsMap:`. - * @return values An array of 3 x data values: the new length of `LSP5ReceivedAssets[]`, the address of the asset under `LSP5ReceivedAssets[index]` - * and the interfaceId + index stored under `LSP5ReceivedAssetsMap:`. + * @return lsp5DataKeys An array Data Keys used to update the [LSP-5-ReceivedAssets] data. + * @return lsp5DataValues An array Data Values used to update the [LSP-5-ReceivedAssets] data. */ function generateReceivedAssetKeys( address receiver, - address asset, - bytes32 assetMapKey, - bytes4 interfaceID - ) internal view returns (bytes32[] memory keys, bytes[] memory values) { - keys = new bytes32[](3); - values = new bytes[](3); + address assetAddress, + bytes4 assetInterfaceId + ) + internal + view + returns (bytes32[] memory lsp5DataKeys, bytes[] memory lsp5DataValues) + { + IERC725Y erc725YContract = IERC725Y(receiver); + + // --- `LSP5ReceivedAssets[]` Array --- + + bytes memory currentArrayLengthBytes = getLSP5ArrayLengthBytes( + erc725YContract + ); - IERC725Y account = IERC725Y(receiver); - bytes memory encodedArrayLength = getLSP5ReceivedAssetsCount(account); + // CHECK that the value of `LSP5ReceivedAssets[]` Array length is a valid `uint128` (16 bytes long) + if (!LSP2Utils.isValidLSP2ArrayLengthValue(currentArrayLengthBytes)) { + if (currentArrayLengthBytes.length == 0) { + // if it's the first asset received and nothing is set (= 0x) + // we need to convert it to: `0x00000000000000000000000000000000` + // to safely cast to a uint128 of length 0 + currentArrayLengthBytes = abi.encodePacked(bytes16(0)); + } else { + // otherwise the array length is invalid + return (lsp5DataKeys, lsp5DataValues); + } + } + + uint128 currentArrayLength = uint128(bytes16(currentArrayLengthBytes)); - // CHECK it's either the first asset received, - // or the storage is already set with a valid `uint128` value - if (encodedArrayLength.length != 0 && encodedArrayLength.length != 16) { - revert InvalidLSP5ReceivedAssetsArrayLength({ - invalidValueStored: encodedArrayLength, - invalidValueLength: encodedArrayLength.length - }); + // CHECK for potential overflow + if (currentArrayLength == type(uint128).max) { + return (lsp5DataKeys, lsp5DataValues); } - uint128 oldArrayLength = uint128(bytes16(encodedArrayLength)); + // --- `LSP5ReceivedAssetsMap:` --- - if (oldArrayLength == type(uint128).max) { - revert MaxLSP5ReceivedAssetsCountReached({ - notRegisteredAsset: asset - }); + bytes32 mapDataKey = LSP2Utils.generateMappingKey( + _LSP5_RECEIVED_ASSETS_MAP_KEY_PREFIX, + bytes20(assetAddress) + ); + + // CHECK that the map value is not already set in the storage for the newly received asset + // If that's the case, the asset is already registered. Do not try to update. + if (erc725YContract.getData(mapDataKey).length != 0) { + return (lsp5DataKeys, lsp5DataValues); } - // store the number of received assets incremented by 1 - keys[0] = _LSP5_RECEIVED_ASSETS_ARRAY_KEY; - values[0] = bytes.concat(bytes16(oldArrayLength + 1)); + // --- LSP5 Data Keys & Values --- + + lsp5DataKeys = new bytes32[](3); + lsp5DataValues = new bytes[](3); - // store the address of the asset under the element key in the array - keys[1] = LSP2Utils.generateArrayElementKeyAtIndex( + // Increment `LSP5ReceivedAssets[]` Array length + lsp5DataKeys[0] = _LSP5_RECEIVED_ASSETS_ARRAY_KEY; + lsp5DataValues[0] = abi.encodePacked(currentArrayLength + 1); + + // Add asset address to `LSP5ReceivedAssets[index]`, where index == previous array length + lsp5DataKeys[1] = LSP2Utils.generateArrayElementKeyAtIndex( _LSP5_RECEIVED_ASSETS_ARRAY_KEY, - oldArrayLength + currentArrayLength ); - values[1] = bytes.concat(bytes20(asset)); + lsp5DataValues[1] = abi.encodePacked(assetAddress); - // store the interfaceId and the location in the array of the asset - // under the LSP5ReceivedAssetMap key - keys[2] = assetMapKey; - values[2] = bytes.concat(interfaceID, bytes16(oldArrayLength)); + // Add interfaceId + index as value under `LSP5ReceivedAssetsMap:` + lsp5DataKeys[2] = mapDataKey; + lsp5DataValues[2] = bytes.concat( + assetInterfaceId, + currentArrayLengthBytes + ); } /** - * @dev Generate an array of data key/value pairs to be set on the sender address after sending assets. + * @dev Generate an array of Data Key/Value pairs to be set on the sender address after sending assets. + * + * @custom:warning Returns empty arrays when encountering errors. Otherwise the arrays must have at least 3 data keys and 3 data values. * * @param sender The address sending the asset and where the LSP5 data keys should be updated. - * @param assetMapKey The `LSP5ReceivedAssetMap:` data key of the asset being sent containing the interfaceId of the - * asset and the index in the `LSP5ReceivedAssets[]` Array. - * @param assetIndex The index at which the asset is stored under the `LSP5ReceivedAssets[]` Array. + * @param assetAddress The address of the asset that is being sent. * - * @return keys An array of 3 x data keys: `LSP5ReceivedAssets[]`, `LSP5ReceivedAssets[index]` and `LSP5ReceivedAssetsMap:`. - * @return values An array of 3 x data values: the new length of `LSP5ReceivedAssets[]`, the address of the asset under `LSP5ReceivedAssets[index]` - * and the interfaceId + index stored under `LSP5ReceivedAssetsMap:`. + * @return lsp5DataKeys An array Data Keys used to update the [LSP-5-ReceivedAssets] data. + * @return lsp5DataValues An array Data Values used to update the [LSP-5-ReceivedAssets] data. */ function generateSentAssetKeys( address sender, - bytes32 assetMapKey, - uint128 assetIndex - ) internal view returns (bytes32[] memory keys, bytes[] memory values) { - IERC725Y account = IERC725Y(sender); - bytes memory lsp5ReceivedAssetsCountValue = getLSP5ReceivedAssetsCount( - account + address assetAddress + ) + internal + view + returns (bytes32[] memory lsp5DataKeys, bytes[] memory lsp5DataValues) + { + IERC725Y erc725YContract = IERC725Y(sender); + + // --- `LSP5ReceivedAssets[]` Array --- + + bytes memory newArrayLengthBytes = getLSP5ArrayLengthBytes( + erc725YContract ); - if (lsp5ReceivedAssetsCountValue.length != 16) { - revert InvalidLSP5ReceivedAssetsArrayLength({ - invalidValueStored: lsp5ReceivedAssetsCountValue, - invalidValueLength: lsp5ReceivedAssetsCountValue.length - }); + // CHECK that the value of `LSP5ReceivedAssets[]` Array length is a valid `uint128` (16 bytes long) + if (!LSP2Utils.isValidLSP2ArrayLengthValue(newArrayLengthBytes)) { + return (lsp5DataKeys, lsp5DataValues); } - uint128 oldArrayLength = uint128(bytes16(lsp5ReceivedAssetsCountValue)); - - // Updating the number of the received assets (decrementing by 1 - uint128 newArrayLength = oldArrayLength - 1; - - // Generate the element key in the array of the asset - bytes32 assetInArrayKey = LSP2Utils.generateArrayElementKeyAtIndex( - _LSP5_RECEIVED_ASSETS_ARRAY_KEY, - assetIndex - ); - - // If the asset to remove is the last element in the array - if (assetIndex == newArrayLength) { - /** - * We will be updating/removing 3 keys: - * - Keys[0]: [Update] The arrayLengthKey to contain the new number of the received assets - * - Keys[1]: [Remove] The element in arrayKey (Remove the address of the asset sent) - * - Keys[2]: [Remove] The mapKey (Remove the interfaceId and the index of the asset sent) - */ - keys = new bytes32[](3); - values = new bytes[](3); - - // store the number of received assets decremented by 1 - keys[0] = _LSP5_RECEIVED_ASSETS_ARRAY_KEY; - values[0] = bytes.concat(bytes16(newArrayLength)); + // CHECK for potential underflow + if (bytes16(newArrayLengthBytes) == bytes16(0)) { + return (lsp5DataKeys, lsp5DataValues); + } - // remove the address of the asset from the element key - keys[1] = assetInArrayKey; - values[1] = ""; + uint128 newArrayLength = uint128(bytes16(newArrayLengthBytes)) - 1; - // remove the interfaceId and the location in the array of the asset - keys[2] = assetMapKey; - values[2] = ""; + // --- `LSP5ReceivedAssetsMap:` --- - // Swapping last element in ArrayKey with the element in ArrayKey to remove || {Swap and pop} method; - // check https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/EnumerableSet.sol#L80 - } else if (assetIndex < newArrayLength) { - /** - * We will be updating/removing 5 keys: - * - Keys[0]: [Update] The arrayLengthKey to contain the new number of the received assets - * - Keys[1]: [Remove] The mapKey of the asset to remove (Remove the interfaceId and the index of the asset sent) - * - Keys[2]: [Update] The element in arrayKey to remove (Swap with the address of the last element in Array) - * - Keys[3]: [Remove] The last element in arrayKey (Remove (pop) the address of the last element as it's already swapped) - * - Keys[4]: [Update] The mapKey of the last element in array (Update the new index and the interfaceID) - */ - keys = new bytes32[](5); - values = new bytes[](5); + bytes32 removedElementMapKey = LSP2Utils.generateMappingKey( + _LSP5_RECEIVED_ASSETS_MAP_KEY_PREFIX, + bytes20(assetAddress) + ); - // store the number of received assets decremented by 1 - keys[0] = _LSP5_RECEIVED_ASSETS_ARRAY_KEY; - values[0] = bytes.concat(bytes16(newArrayLength)); + // Query the ERC725Y storage of the LSP0-ERC725Account + bytes memory mapValue = erc725YContract.getData(removedElementMapKey); - // remove the interfaceId and the location in the array of the asset - keys[1] = assetMapKey; - values[1] = ""; + // CHECK if no map value was set for the asset to remove. + // If that's the case, there is nothing to remove. Do not try to update. + if (mapValue.length != 20) { + return (lsp5DataKeys, lsp5DataValues); + } - if (newArrayLength >= type(uint128).max) { - revert ReceivedAssetsIndexSuperiorToUint128(newArrayLength); - } + // Extract index of asset to remove from the map value + uint128 removedElementIndex = uint128(bytes16(bytes20(mapValue) << 32)); - // Generate all data Keys/values of the last element in Array to swap - // with data Keys/values of the asset to remove + bytes32 removedElementIndexKey = LSP2Utils + .generateArrayElementKeyAtIndex( + _LSP5_RECEIVED_ASSETS_ARRAY_KEY, + removedElementIndex + ); - // Generate the element key of the last asset in the array - bytes32 lastAssetInArrayKey = LSP2Utils - .generateArrayElementKeyAtIndex( + if (removedElementIndex == newArrayLength) { + return + LSP2Utils.removeLastElementFromArrayAndMap( _LSP5_RECEIVED_ASSETS_ARRAY_KEY, - newArrayLength + newArrayLength, + removedElementIndexKey, + removedElementMapKey + ); + } else if (removedElementIndex < newArrayLength) { + return + LSP2Utils.removeElementFromArrayAndMap( + erc725YContract, + _LSP5_RECEIVED_ASSETS_ARRAY_KEY, + newArrayLength, + removedElementIndexKey, + removedElementIndex, + removedElementMapKey ); - - // Get the address of the asset from the element key of the last asset in the array - bytes20 lastAssetInArrayAddress = bytes20( - account.getData(lastAssetInArrayKey) - ); - - // Generate the map key of the last asset in the array - bytes32 lastAssetInArrayMapKey = LSP2Utils.generateMappingKey( - _LSP5_RECEIVED_ASSETS_MAP_KEY_PREFIX, - lastAssetInArrayAddress - ); - - // Get the interfaceId and the location in the array of the last asset - bytes memory lastAssetInterfaceIdAndIndex = account.getData( - lastAssetInArrayMapKey - ); - bytes memory interfaceID = BytesLib.slice( - lastAssetInterfaceIdAndIndex, - 0, - 4 - ); - - // Set the address of the last asset instead of the asset to be sent - // under the element data key in the array - keys[2] = assetInArrayKey; - values[2] = bytes.concat(lastAssetInArrayAddress); - - // Remove the address swapped from the last element data key in the array - keys[3] = lastAssetInArrayKey; - values[3] = ""; - - // Update the index and the interfaceId of the address swapped (last element in the array) - // to point to the new location in the LSP5ReceivedAssets array - keys[4] = lastAssetInArrayMapKey; - values[4] = bytes.concat(interfaceID, bytes16(assetIndex)); } else { // If index is bigger than the array length, out of bounds - return (keys, values); + return (lsp5DataKeys, lsp5DataValues); } } /** - * @dev Get the total number of asset addresses stored under the `LSP5ReceivedAssets[]` Array data key. - * @param account The ERC725Y smart contract to read the storage from. - * @return The raw bytes stored under the `LSP5ReceivedAssets[]` data key. - * - * @custom:info This function does not return a number but the raw bytes stored under the `LSP5ReceivedAssets[]` Array data key. + * @dev Get the raw bytes value stored under the `_LSP5_RECEIVED_ASSETS_ARRAY_KEY`. + * @param erc725YContract The contract to query the ERC725Y storage from. + * @return The raw bytes value stored under this data key. */ - function getLSP5ReceivedAssetsCount( - IERC725Y account + function getLSP5ArrayLengthBytes( + IERC725Y erc725YContract ) internal view returns (bytes memory) { - return account.getData(_LSP5_RECEIVED_ASSETS_ARRAY_KEY); + return erc725YContract.getData(_LSP5_RECEIVED_ASSETS_ARRAY_KEY); } } diff --git a/contracts/Mocks/GenericExecutorWithBalanceOfFunction.sol b/contracts/Mocks/GenericExecutorWithBalanceOfFunction.sol new file mode 100644 index 000000000..17f477694 --- /dev/null +++ b/contracts/Mocks/GenericExecutorWithBalanceOfFunction.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +/** + * @dev This contract is used only for testing purposes + */ +contract GenericExecutorWithBalanceOfFunction { + function call( + address target, + uint256 value, + bytes memory data + ) public returns (bytes memory result) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory returnData) = target.call{value: value}( + data + ); + result = _verifyCallResult(success, returnData, "Unknown Error"); + } + + function balanceOf(address) external pure returns (uint256) { + return 1; + } + + function _verifyCallResult( + bool success, + bytes memory returndata, + string memory errorMessage + ) internal pure returns (bytes memory) { + if (success) { + return returndata; + } else { + _revert(returndata, errorMessage); + } + } + + function _revert( + bytes memory returndata, + string memory errorMessage + ) private pure { + // Look for revert reason and bubble it up if present + if (returndata.length > 0) { + // The easiest way to bubble the revert reason is using memory via assembly + // solhint-disable-next-line no-inline-assembly + assembly { + let returndata_size := mload(returndata) + revert(add(32, returndata), returndata_size) + } + } else { + revert(errorMessage); + } + } +} diff --git a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md index 0fbba0571..04af77971 100644 --- a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md +++ b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.md @@ -69,6 +69,13 @@ See [`IERC165-supportsInterface`](#ierc165-supportsinterface). ::: +:::info + +- If some issues occured with generating the `dataKeys` or `dataValues` the `returnedMessage` will be an error message, otherwise it will be empty. +- If an error occured when trying to use `setDataBatch(dataKeys,dataValues)`, it will return the raw error data back to the caller. + +::: + :::caution Warning When the data stored in the ERC725Y storage of the LSP0 contract is corrupted (\_e.g: ([LSP-5-ReceivedAssets]'s Array length not 16 bytes long, the token received is already registered in `LSP5ReceivetAssets[]`, the token being sent is not sent as full balance, etc...), the function call will still pass and return (**not revert!**) and not modify any data key on the storage of the [LSP-0-ERC725Account]. @@ -79,7 +86,7 @@ When the data stored in the ERC725Y storage of the LSP0 contract is corrupted (\ function universalReceiver( bytes32 typeId, bytes -) external payable returns (bytes result); +) external payable returns (bytes); ``` _Reacted on received notification with `typeId`._ @@ -106,9 +113,9 @@ _Reacted on received notification with `typeId`._ #### Returns -| Name | Type | Description | -| -------- | :-----: | ---------------------------------------- | -| `result` | `bytes` | The result of the reaction for `typeId`. | +| Name | Type | Description | +| ---- | :-----: | ---------------------------------------- | +| `0` | `bytes` | The result of the reaction for `typeId`. |
@@ -118,217 +125,161 @@ Any method labeled as `internal` serves as utility function within the contract. 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. -### \_whenReceiving +### \_tokenSender ```solidity -function _whenReceiving( - bytes32 typeId, - address notifier, - bytes32 notifierMapKey, - bytes4 interfaceID -) internal nonpayable returns (bytes); +function _tokenSender(address notifier) internal nonpayable returns (bytes); ``` -To avoid stack too deep error -Generate the keys/values of the asset/vault received to set and set them -on the account depending on the type of the transfer (asset/vault) +Handler for LSP7 and LSP8 token sender type id. + +#### Parameters + +| Name | Type | Description | +| ---------- | :-------: | ------------------------------- | +| `notifier` | `address` | The LSP7 or LSP8 token address. |
-### \_whenSending +### \_tokenRecipient ```solidity -function _whenSending( - bytes32 typeId, +function _tokenRecipient( address notifier, - bytes32 notifierMapKey, - uint128 arrayIndex + bytes4 interfaceId ) internal nonpayable returns (bytes); ``` -To avoid stack too deep error -Generate the keys/values of the asset/vault sent to set and set them -on the account depending on the type of the transfer (asset/vault) +Handler for LSP7 and LSP8 token recipient type id. -
- -## Events - -### UniversalReceiver +#### Parameters -:::note References +| Name | Type | Description | +| ------------- | :-------: | ------------------------------- | +| `notifier` | `address` | The LSP7 or LSP8 token address. | +| `interfaceId` | `bytes4` | The LSP7 or LSP8 interface id. | -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#universalreceiver) -- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Event signature: `UniversalReceiver(address,uint256,bytes32,bytes,bytes)` -- Event topic hash: `0x9c3ba68eb5742b8e3961aea0afc7371a71bf433c8a67a831803b64c064a178c2` +
-::: +### \_vaultSender ```solidity -event UniversalReceiver(address indexed from, uint256 indexed value, bytes32 indexed typeId, bytes receivedData, bytes returnedValue); +function _vaultSender(address notifier) internal nonpayable returns (bytes); ``` -\*Address `from` called the `universalReceiver(...)` function while sending `value` LYX. Notification type (typeId): `typeId` - -- Data received: `receivedData`.\* - -Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` s +Handler for LSP9 vault sender type id. #### Parameters -| Name | Type | Description | -| ---------------------- | :-------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `from` **`indexed`** | `address` | The address of the EOA or smart contract that called the {universalReceiver(...)} function. | -| `value` **`indexed`** | `uint256` | The amount sent to the {universalReceiver(...)} function. | -| `typeId` **`indexed`** | `bytes32` | A `bytes32` unique identifier (= _"hook"_)that describe the type of notification, information or transaction received by the contract. Can be related to a specific standard or a hook. | -| `receivedData` | `bytes` | Any arbitrary data that was sent to the {universalReceiver(...)} function. | -| `returnedValue` | `bytes` | The value returned by the {universalReceiver(...)} function. | +| Name | Type | Description | +| ---------- | :-------: | ----------------------- | +| `notifier` | `address` | The LSP9 vault address. |
-## Errors - -### CannotRegisterEOAsAsAssets - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#cannotregistereoasasassets) -- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Error signature: `CannotRegisterEOAsAsAssets(address)` -- Error hash: `0xa5295345` - -::: +### \_vaultRecipient ```solidity -error CannotRegisterEOAsAsAssets(address caller); +function _vaultRecipient(address notifier) internal nonpayable returns (bytes); ``` -_EOA: `caller` cannot be registered as an asset._ - -Reverts when EOA calls the [`universalReceiver(..)`](#universalreceiver) function with an asset/vault typeId. +Handler for LSP9 vault recipient type id. #### Parameters -| Name | Type | Description | -| -------- | :-------: | ---------------------- | -| `caller` | `address` | The address of the EOA | +| Name | Type | Description | +| ---------- | :-------: | ----------------------- | +| `notifier` | `address` | The LSP9 vault address. |
-### InvalidLSP10ReceivedVaultsArrayLength +### \_setDataBatchWithoutReverting -:::note References +:::info -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#invalidlsp10receivedvaultsarraylength) -- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Error signature: `InvalidLSP10ReceivedVaultsArrayLength(bytes,uint256)` -- Error hash: `0x12ce1c39` +If an the low-level transaction revert, the returned data will be forwarded. Th contract that uses this function can use the `Address` library to revert with the revert reason. ::: ```solidity -error InvalidLSP10ReceivedVaultsArrayLength( - bytes invalidValueStored, - uint256 invalidValueLength -); +function _setDataBatchWithoutReverting( + bytes32[] dataKeys, + bytes[] dataValues +) internal nonpayable returns (bytes); ``` -Reverts when the value stored under the 'LSP10ReceivedVaults[]' Array data key is not valid. The value stored under this data key should be exactly 16 bytes long. Only possible valid values are: - -- any valid uint128 values _e.g: `0x00000000000000000000000000000000` (zero), meaning empty array, no vaults received._ _e.g: `0x00000000000000000000000000000005` (non-zero), meaning 5 array elements, 5 vaults received._ - -- `0x` (nothing stored under this data key, equivalent to empty array). +Calls `bytes4(keccak256(setDataBatch(bytes32[],bytes[])))` without checking for `bool success`, but it returns all the data back. #### Parameters -| Name | Type | Description | -| -------------------- | :-------: | ------------------------------------------------------------------------------------------------------------ | -| `invalidValueStored` | `bytes` | The invalid value stored under the `LSP10ReceivedVaults[]` Array data key. | -| `invalidValueLength` | `uint256` | The invalid number of bytes stored under the `LSP10ReceivedVaults[]` Array data key (MUST be 16 bytes long). | +| Name | Type | Description | +| ------------ | :---------: | ---------------------- | +| `dataKeys` | `bytes32[]` | Data Keys to be set. | +| `dataValues` | `bytes[]` | Data Values to be set. |
-### InvalidLSP5ReceivedAssetsArrayLength +## Events + +### UniversalReceiver :::note References -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#invalidlsp5receivedassetsarraylength) +- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#universalreceiver) - Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Error signature: `InvalidLSP5ReceivedAssetsArrayLength(bytes,uint256)` -- Error hash: `0xecba7af8` +- Event signature: `UniversalReceiver(address,uint256,bytes32,bytes,bytes)` +- Event topic hash: `0x9c3ba68eb5742b8e3961aea0afc7371a71bf433c8a67a831803b64c064a178c2` ::: ```solidity -error InvalidLSP5ReceivedAssetsArrayLength( - bytes invalidValueStored, - uint256 invalidValueLength -); +event UniversalReceiver(address indexed from, uint256 indexed value, bytes32 indexed typeId, bytes receivedData, bytes returnedValue); ``` -Reverts when the value stored under the 'LSP5ReceivedAssets[]' Array data key is not valid. The value stored under this data key should be exactly 16 bytes long. Only possible valid values are: +\*Address `from` called the `universalReceiver(...)` function while sending `value` LYX. Notification type (typeId): `typeId` -- any valid uint128 values _e.g: `0x00000000000000000000000000000000` (zero), empty array, no assets received._ _e.g. `0x00000000000000000000000000000005` (non-zero), 5 array elements, 5 assets received._ +- Data received: `receivedData`.\* -- `0x` (nothing stored under this data key, equivalent to empty array) +Emitted when the [`universalReceiver`](#universalreceiver) function was called with a specific `typeId` and some `receivedData` s #### Parameters -| Name | Type | Description | -| -------------------- | :-------: | ------------------------------------------------------------------------------------------------------------- | -| `invalidValueStored` | `bytes` | The invalid value stored under the `LSP5ReceivedAssets[]` Array data key. | -| `invalidValueLength` | `uint256` | The invalid number of bytes stored under the `LSP5ReceivedAssets[]` data key (MUST be exactly 16 bytes long). | +| Name | Type | Description | +| ---------------------- | :-------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `from` **`indexed`** | `address` | The address of the EOA or smart contract that called the {universalReceiver(...)} function. | +| `value` **`indexed`** | `uint256` | The amount sent to the {universalReceiver(...)} function. | +| `typeId` **`indexed`** | `bytes32` | A `bytes32` unique identifier (= _"hook"_)that describe the type of notification, information or transaction received by the contract. Can be related to a specific standard or a hook. | +| `receivedData` | `bytes` | Any arbitrary data that was sent to the {universalReceiver(...)} function. | +| `returnedValue` | `bytes` | The value returned by the {universalReceiver(...)} function. |
-### MaxLSP10VaultsCountReached - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#maxlsp10vaultscountreached) -- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Error signature: `MaxLSP10VaultsCountReached(address)` -- Error hash: `0x11610270` - -::: - -```solidity -error MaxLSP10VaultsCountReached(address notRegisteredVault); -``` - -Reverts when the `LSP10Vaults[]` Array reaches its maximum limit (`max(uint128)`). - -#### Parameters - -| Name | Type | Description | -| -------------------- | :-------: | ---------------------------------------------------------- | -| `notRegisteredVault` | `address` | The address of the LSP9Vault that could not be registered. | - -
+## Errors -### MaxLSP5ReceivedAssetsCountReached +### CannotRegisterEOAsAsAssets :::note References -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#maxlsp5receivedassetscountreached) +- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#cannotregistereoasasassets) - Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Error signature: `MaxLSP5ReceivedAssetsCountReached(address)` -- Error hash: `0x0b51a2d0` +- Error signature: `CannotRegisterEOAsAsAssets(address)` +- Error hash: `0xa5295345` ::: ```solidity -error MaxLSP5ReceivedAssetsCountReached(address notRegisteredAsset); +error CannotRegisterEOAsAsAssets(address caller); ``` -Reverts when the `LSP5ReceivedAssets[]` Array reaches its maximum limit (`max(uint128)`). +_EOA: `caller` cannot be registered as an asset._ + +Reverts when EOA calls the [`universalReceiver(..)`](#universalreceiver) function with an asset/vault typeId. #### Parameters -| Name | Type | Description | -| -------------------- | :-------: | ------------------------------------------------------ | -| `notRegisteredAsset` | `address` | The address of the asset that could not be registered. | +| Name | Type | Description | +| -------- | :-------: | ---------------------- | +| `caller` | `address` | The address of the EOA |
@@ -352,53 +303,3 @@ _Cannot send native tokens to [`universalReceiver(...)`](#universalreceiver) fun Reverts when the [`universalReceiver`](#universalreceiver) function in the LSP1 Universal Receiver Delegate contract is called while sending some native tokens along the call (`msg.value` different than `0`)
- -### ReceivedAssetsIndexSuperiorToUint128 - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#receivedassetsindexsuperiortouint128) -- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Error signature: `ReceivedAssetsIndexSuperiorToUint128(uint256)` -- Error hash: `0xe8a4fba0` - -::: - -```solidity -error ReceivedAssetsIndexSuperiorToUint128(uint256 index); -``` - -Reverts when the received assets index is superior to `max(uint128)`. - -#### Parameters - -| Name | Type | Description | -| ------- | :-------: | -------------------------- | -| `index` | `uint256` | The received assets index. | - -
- -### VaultIndexSuperiorToUint128 - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#vaultindexsuperiortouint128) -- Solidity implementation: [`LSP1UniversalReceiverDelegateUP.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol) -- Error signature: `VaultIndexSuperiorToUint128(uint256)` -- Error hash: `0x76f9db1b` - -::: - -```solidity -error VaultIndexSuperiorToUint128(uint256 index); -``` - -Reverts when the vault index is superior to `max(uint128)`. - -#### Parameters - -| Name | Type | Description | -| ------- | :-------: | ---------------- | -| `index` | `uint256` | The vault index. | - -
diff --git a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md index 3fa240854..ddedeaae3 100644 --- a/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md +++ b/docs/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.md @@ -67,11 +67,18 @@ See [`IERC165-supportsInterface`](#ierc165-supportsinterface). ::: +:::info + +- If some issues occured with generating the `dataKeys` or `dataValues` the `returnedMessage` will be an error message, otherwise it will be empty. +- If an error occured when trying to use `setDataBatch(dataKeys,dataValues)`, it will return the raw error data back to the caller. + +::: + ```solidity function universalReceiver( bytes32 typeId, bytes -) external payable returns (bytes result); +) external payable returns (bytes); ``` _Reacted on received notification with `typeId`._ @@ -95,9 +102,77 @@ Handles two cases: Writes the received [LSP-7-DigitalAsset] or [LSP-8-Identifiab #### Returns -| Name | Type | Description | -| -------- | :-----: | ---------------------------------------- | -| `result` | `bytes` | The result of the reaction for `typeId`. | +| Name | Type | Description | +| ---- | :-----: | ---------------------------------------- | +| `0` | `bytes` | The result of the reaction for `typeId`. | + +
+ +## 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. + +### \_tokenSender + +```solidity +function _tokenSender(address notifier) internal nonpayable returns (bytes); +``` + +Handler for LSP7 and LSP8 token sender type id. + +#### Parameters + +| Name | Type | Description | +| ---------- | :-------: | ------------------------------- | +| `notifier` | `address` | The LSP7 or LSP8 token address. | + +
+ +### \_tokenRecipient + +```solidity +function _tokenRecipient( + address notifier, + bytes4 interfaceId +) internal nonpayable returns (bytes); +``` + +Handler for LSP7 and LSP8 token recipient type id. + +#### Parameters + +| Name | Type | Description | +| ------------- | :-------: | ------------------------------- | +| `notifier` | `address` | The LSP7 or LSP8 token address. | +| `interfaceId` | `bytes4` | The LSP7 or LSP8 interface id. | + +
+ +### \_setDataBatchWithoutReverting + +:::info + +If an the low-level transaction revert, the returned data will be forwarded. Th contract that uses this function can use the `Address` library to revert with the revert reason. + +::: + +```solidity +function _setDataBatchWithoutReverting( + bytes32[] dataKeys, + bytes[] dataValues +) internal nonpayable returns (bytes); +``` + +Calls `bytes4(keccak256(setDataBatch(bytes32[],bytes[])))` without checking for `bool succes`, but it returns all the data back. + +#### Parameters + +| Name | Type | Description | +| ------------ | :---------: | ---------------------- | +| `dataKeys` | `bytes32[]` | Data Keys to be set. | +| `dataValues` | `bytes[]` | Data Values to be set. |
@@ -165,64 +240,6 @@ Reverts when EOA calls the [`universalReceiver(..)`](#universalreceiver) functio
-### InvalidLSP5ReceivedAssetsArrayLength - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#invalidlsp5receivedassetsarraylength) -- Solidity implementation: [`LSP1UniversalReceiverDelegateVault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol) -- Error signature: `InvalidLSP5ReceivedAssetsArrayLength(bytes,uint256)` -- Error hash: `0xecba7af8` - -::: - -```solidity -error InvalidLSP5ReceivedAssetsArrayLength( - bytes invalidValueStored, - uint256 invalidValueLength -); -``` - -Reverts when the value stored under the 'LSP5ReceivedAssets[]' Array data key is not valid. The value stored under this data key should be exactly 16 bytes long. Only possible valid values are: - -- any valid uint128 values _e.g: `0x00000000000000000000000000000000` (zero), empty array, no assets received._ _e.g. `0x00000000000000000000000000000005` (non-zero), 5 array elements, 5 assets received._ - -- `0x` (nothing stored under this data key, equivalent to empty array) - -#### Parameters - -| Name | Type | Description | -| -------------------- | :-------: | ------------------------------------------------------------------------------------------------------------- | -| `invalidValueStored` | `bytes` | The invalid value stored under the `LSP5ReceivedAssets[]` Array data key. | -| `invalidValueLength` | `uint256` | The invalid number of bytes stored under the `LSP5ReceivedAssets[]` data key (MUST be exactly 16 bytes long). | - -
- -### MaxLSP5ReceivedAssetsCountReached - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#maxlsp5receivedassetscountreached) -- Solidity implementation: [`LSP1UniversalReceiverDelegateVault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol) -- Error signature: `MaxLSP5ReceivedAssetsCountReached(address)` -- Error hash: `0x0b51a2d0` - -::: - -```solidity -error MaxLSP5ReceivedAssetsCountReached(address notRegisteredAsset); -``` - -Reverts when the `LSP5ReceivedAssets[]` Array reaches its maximum limit (`max(uint128)`). - -#### Parameters - -| Name | Type | Description | -| -------------------- | :-------: | ------------------------------------------------------ | -| `notRegisteredAsset` | `address` | The address of the asset that could not be registered. | - -
- ### NativeTokensNotAccepted :::note References @@ -243,28 +260,3 @@ _Cannot send native tokens to [`universalReceiver(...)`](#universalreceiver) fun Reverts when the [`universalReceiver`](#universalreceiver) function in the LSP1 Universal Receiver Delegate contract is called while sending some native tokens along the call (`msg.value` different than `0`)
- -### ReceivedAssetsIndexSuperiorToUint128 - -:::note References - -- Specification details: [**LSP-1-UniversalReceiver**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-1-UniversalReceiver.md#receivedassetsindexsuperiortouint128) -- Solidity implementation: [`LSP1UniversalReceiverDelegateVault.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault/LSP1UniversalReceiverDelegateVault.sol) -- Error signature: `ReceivedAssetsIndexSuperiorToUint128(uint256)` -- Error hash: `0xe8a4fba0` - -::: - -```solidity -error ReceivedAssetsIndexSuperiorToUint128(uint256 index); -``` - -Reverts when the received assets index is superior to `max(uint128)`. - -#### Parameters - -| Name | Type | Description | -| ------- | :-------: | -------------------------- | -| `index` | `uint256` | The received assets index. | - -
diff --git a/docs/libraries/LSP10ReceivedVaults/LSP10Utils.md b/docs/libraries/LSP10ReceivedVaults/LSP10Utils.md index a7fd193be..7aeca69b5 100644 --- a/docs/libraries/LSP10ReceivedVaults/LSP10Utils.md +++ b/docs/libraries/LSP10ReceivedVaults/LSP10Utils.md @@ -26,172 +26,88 @@ Internal functions cannot be called externally, whether from other smart contrac ### generateReceivedVaultKeys +:::caution Warning + +This function returns empty arrays when encountering errors. Otherwise the arrays will contain 3 data keys and 3 data values. + +::: + ```solidity function generateReceivedVaultKeys( address receiver, - address vault, - bytes32 vaultMapKey -) internal view returns (bytes32[] keys, bytes[] values); + address vaultAddress +) internal view returns (bytes32[] lsp10DataKeys, bytes[] lsp10DataValues); ``` Generate an array of data keys/values pairs to be set on the receiver address after receiving vaults. #### Parameters -| Name | Type | Description | -| ------------- | :-------: | --------------------------------------------------------------------------------------------------------------------- | -| `receiver` | `address` | The address receiving the vault and where the LSP10 data keys should be added. | -| `vault` | `address` | @param vaultMapKey The `LSP10VaultMap:` data key of the vault being received containing the interfaceId of the | -| `vaultMapKey` | `bytes32` | The `LSP10VaultMap:` data key of the vault being received containing the interfaceId of the | +| Name | Type | Description | +| -------------- | :-------: | ------------------------------------------------------------------------------ | +| `receiver` | `address` | The address receiving the vault and where the LSP10 data keys should be added. | +| `vaultAddress` | `address` | The address of the vault being received. | #### Returns -| Name | Type | Description | -| -------- | :---------: | ------------------------------------------------------------------------------------------------------------------- | -| `keys` | `bytes32[]` | An array of 3 x data keys: `LSP10Vaults[]`, `LSP10Vaults[index]` and `LSP10VaultMap:`. | -| `values` | `bytes[]` | An array of 3 x data values: the new length of `LSP10Vaults[]`, the address of the asset under `LSP10Vaults[index]` | +| Name | Type | Description | +| ----------------- | :---------: | --------------------------------------------------------------------- | +| `lsp10DataKeys` | `bytes32[]` | An array data keys used to update the [LSP-10-ReceivedAssets] data. | +| `lsp10DataValues` | `bytes[]` | An array data values used to update the [LSP-10-ReceivedAssets] data. |
### generateSentVaultKeys -```solidity -function generateSentVaultKeys( - address sender, - bytes32 vaultMapKey, - uint128 vaultIndex -) internal view returns (bytes32[] keys, bytes[] values); -``` - -Generate an array of data key/value pairs to be set on the sender address after sending vaults. - -#### Parameters - -| Name | Type | Description | -| ------------- | :-------: | ---------------------------------------------------------------------------------------------- | -| `sender` | `address` | The address sending the vault and where the LSP10 data keys should be updated. | -| `vaultMapKey` | `bytes32` | The `LSP10VaultMap:` data key of the vault being sent containing the interfaceId of the | -| `vaultIndex` | `uint128` | The index at which the vault address is stored under `LSP10Vaults[]` Array. | - -#### Returns +:::caution Warning -| Name | Type | Description | -| -------- | :---------: | ------------------------------------------------------------------------------------------------------------------- | -| `keys` | `bytes32[]` | An array of 3 x data keys: `LSP10Vaults[]`, `LSP10Vaults[index]` and `LSP10VaultsMap:`. | -| `values` | `bytes[]` | An array of 3 x data values: the new length of `LSP10Vaults[]`, the address of the asset under `LSP10Vaults[index]` | - -
- -### getLSP10ReceivedVaultsCount - -:::info - -This function does not return a number but the raw bytes stored under the `LSP10Vaults[]` Array data key. +Returns empty arrays when encountering errors. Otherwise the arrays must have at least 3 data keys and 3 data values. ::: ```solidity -function getLSP10ReceivedVaultsCount(contract IERC725Y account) internal view returns (bytes); +function generateSentVaultKeys( + address sender, + address vaultAddress +) internal view returns (bytes32[] lsp10DataKeys, bytes[] lsp10DataValues); ``` -Get the total number of vault addresses stored under the `LSP10Vaults[]` Array data key. +Generate an array of data key/value pairs to be set on the sender address after sending vaults. #### Parameters -| Name | Type | Description | -| --------- | :-----------------: | ---------------------------------------------------- | -| `account` | `contract IERC725Y` | The ERC725Y smart contract to read the storage from. | +| Name | Type | Description | +| -------------- | :-------: | ------------------------------------------------------------------------------ | +| `sender` | `address` | The address sending the vault and where the LSP10 data keys should be updated. | +| `vaultAddress` | `address` | The address of the vault that is being sent. | #### Returns -| Name | Type | Description | -| ---- | :-----: | -------------------------------------------------------- | -| `0` | `bytes` | The raw bytes stored under the `LSP10Vaults[]` data key. | +| Name | Type | Description | +| ----------------- | :---------: | --------------------------------------------------------------------- | +| `lsp10DataKeys` | `bytes32[]` | An array data keys used to update the [LSP-10-ReceivedAssets] data. | +| `lsp10DataValues` | `bytes[]` | An array data values used to update the [LSP-10-ReceivedAssets] data. |
-## Errors - -### InvalidLSP10ReceivedVaultsArrayLength - -:::note References - -- Specification details: [**LSP-10-ReceivedVaults**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-10-ReceivedVaults.md#,)) -- Solidity implementation: [`LSP10Utils.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP10ReceivedVaults/LSP10Utils.sol) -- Error signature: `,)` -- Error hash: `0x9f47dbd3` - -::: +### getLSP10ArrayLengthBytes ```solidity -InvalidLSP10ReceivedVaultsArrayLength(bytes,uint256); +function getLSP10ArrayLengthBytes(contract IERC725Y erc725YContract) internal view returns (bytes); ``` -Reverts when the value stored under the 'LSP10ReceivedVaults[]' Array data key is not valid. -The value stored under this data key should be exactly 16 bytes long. -Only possible valid values are: - -- any valid uint128 values - _e.g: `0x00000000000000000000000000000000` (zero), meaning empty array, no vaults received._ - _e.g: `0x00000000000000000000000000000005` (non-zero), meaning 5 array elements, 5 vaults received._ - -- `0x` (nothing stored under this data key, equivalent to empty array) - -#### Parameters - -| Name | Type | Description | -| -------------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------- | -| `invalidValueStored` | `bytes` | invalidValueLength The invalid number of bytes stored under the `LSP10ReceivedVaults[]` Array data key (MUST be 16 bytes long). | -| `invalidValueLength` | `uint256` | The invalid number of bytes stored under the `LSP10ReceivedVaults[]` Array data key (MUST be 16 bytes long). | - -
- -### MaxLSP10VaultsCountReached - -:::note References - -- Specification details: [**LSP-10-ReceivedVaults**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-10-ReceivedVaults.md#)) -- Solidity implementation: [`LSP10Utils.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP10ReceivedVaults/LSP10Utils.sol) -- Error signature: `)` -- Error hash: `0x59d76dc3` - -::: - -```solidity -MaxLSP10VaultsCountReached(address); -``` - -Reverts when the `LSP10Vaults[]` Array reaches its maximum limit (`max(uint128)`) +Get the raw bytes value stored under the `_LSP10_VAULTS_ARRAY_KEY`. #### Parameters -| Name | Type | Description | -| -------------------- | :-------: | ---------------------------------------------------------- | -| `notRegisteredVault` | `address` | The address of the LSP9Vault that could not be registered. | - -
- -### VaultIndexSuperiorToUint128 - -:::note References - -- Specification details: [**LSP-10-ReceivedVaults**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-10-ReceivedVaults.md#)) -- Solidity implementation: [`LSP10Utils.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP10ReceivedVaults/LSP10Utils.sol) -- Error signature: `)` -- Error hash: `0x59d76dc3` - -::: - -```solidity -VaultIndexSuperiorToUint128(uint256); -``` +| Name | Type | Description | +| ----------------- | :-----------------: | ----------------------------------------------- | +| `erc725YContract` | `contract IERC725Y` | The contract to query the ERC725Y storage from. | -Reverts when the vault index is superior to `max(uint128)` - -#### Parameters +#### Returns -| Name | Type | Description | -| ------- | :-------: | ---------------- | -| `index` | `uint256` | The vault index. | +| Name | Type | Description | +| ---- | :-----: | ----------------------------------------------- | +| `0` | `bytes` | The raw bytes value stored under this data key. |
diff --git a/docs/libraries/LSP2ERC725YJSONSchema/LSP2Utils.md b/docs/libraries/LSP2ERC725YJSONSchema/LSP2Utils.md index 6ea73d42b..6654ba288 100644 --- a/docs/libraries/LSP2ERC725YJSONSchema/LSP2Utils.md +++ b/docs/libraries/LSP2ERC725YJSONSchema/LSP2Utils.md @@ -428,3 +428,78 @@ Verify if `data` is a valid array of value encoded as a `CompactBytesArray` acco | `0` | `bool` | `true` if the `data` is correctly encoded CompactBytesArray, `false` otherwise. |
+ +### isValidLSP2ArrayLengthValue + +```solidity +function isValidLSP2ArrayLengthValue( + bytes arrayLength +) internal pure returns (bool); +``` + +Validates if the bytes `arrayLength` are exactly 16 bytes long, and are of the exact size of an LSP2 Array length value + +#### Parameters + +| Name | Type | Description | +| ------------- | :-----: | ------------------------------------- | +| `arrayLength` | `bytes` | Plain bytes that should be validated. | + +#### Returns + +| Name | Type | Description | +| ---- | :----: | -------------------------------------------------------- | +| `0` | `bool` | `true` if the value is 16 bytes long, `false` otherwise. | + +
+ +### removeLastElementFromArrayAndMap + +```solidity +function removeLastElementFromArrayAndMap( + bytes32 arrayKey, + uint128 newArrayLength, + bytes32 removedElementIndexKey, + bytes32 removedElementMapKey +) internal pure returns (bytes32[] dataKeys, bytes[] dataValues); +``` + +Generates Data Key/Value pairs for removing the last element from an LSP2 Array and a mapping Data Key. + +#### Parameters + +| Name | Type | Description | +| ------------------------ | :-------: | ------------------------------------------------------------- | +| `arrayKey` | `bytes32` | The Data Key of Key Type Array. | +| `newArrayLength` | `uint128` | The new Array Length for the `arrayKey`. | +| `removedElementIndexKey` | `bytes32` | The Data Key of Key Type Array Index for the removed element. | +| `removedElementMapKey` | `bytes32` | The Data Key of a mapping to be removed. | + +
+ +### removeElementFromArrayAndMap + +:::info + +The function assumes that the Data Value stored under the mapping Data Key is of length 20 where the last 16 bytes are the index of the element in the array. + +::: + +```solidity +function removeElementFromArrayAndMap(contract IERC725Y ERC725YContract, bytes32 arrayKey, uint128 newArrayLength, bytes32 removedElementIndexKey, uint128 removedElementIndex, bytes32 removedElementMapKey) internal view returns (bytes32[] dataKeys, bytes[] dataValues); +``` + +Generates Data Key/Value pairs for removing an element from an LSP2 Array and a mapping Data Key. + +#### Parameters + +| Name | Type | Description | +| ------------------------ | :-----------------: | ------------------------------------------------------------- | +| `ERC725YContract` | `contract IERC725Y` | The ERC725Y contract. | +| `arrayKey` | `bytes32` | The Data Key of Key Type Array. | +| `newArrayLength` | `uint128` | The new Array Length for the `arrayKey`. | +| `removedElementIndexKey` | `bytes32` | The Data Key of Key Type Array Index for the removed element. | +| `removedElementIndex` | `uint128` | the index of the removed element. | +| `removedElementMapKey` | `bytes32` | The Data Key of a mapping to be removed. | + +
diff --git a/docs/libraries/LSP5ReceivedAssets/LSP5Utils.md b/docs/libraries/LSP5ReceivedAssets/LSP5Utils.md index 66016b611..b910fe422 100644 --- a/docs/libraries/LSP5ReceivedAssets/LSP5Utils.md +++ b/docs/libraries/LSP5ReceivedAssets/LSP5Utils.md @@ -26,174 +26,90 @@ Internal functions cannot be called externally, whether from other smart contrac ### generateReceivedAssetKeys +:::caution Warning + +Returns empty arrays when encountering errors. Otherwise the arrays must have 3 data keys and 3 data values. + +::: + ```solidity function generateReceivedAssetKeys( address receiver, - address asset, - bytes32 assetMapKey, - bytes4 interfaceID -) internal view returns (bytes32[] keys, bytes[] values); + address assetAddress, + bytes4 assetInterfaceId +) internal view returns (bytes32[] lsp5DataKeys, bytes[] lsp5DataValues); ``` Generate an array of data key/value pairs to be set on the receiver address after receiving assets. #### Parameters -| Name | Type | Description | -| ------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------- | -| `receiver` | `address` | The address receiving the asset and where the LSP5 data keys should be added. | -| `asset` | `address` | @param assetMapKey The `LSP5ReceivedAssetMap:` data key of the asset being received containing the interfaceId of the | -| `assetMapKey` | `bytes32` | The `LSP5ReceivedAssetMap:` data key of the asset being received containing the interfaceId of the | -| `interfaceID` | `bytes4` | The interfaceID of the asset being received. | +| Name | Type | Description | +| ------------------ | :-------: | ----------------------------------------------------------------------------- | +| `receiver` | `address` | The address receiving the asset and where the LSP5 data keys should be added. | +| `assetAddress` | `address` | The address of the asset being received (_e.g: an LSP7 or LSP8 token_). | +| `assetInterfaceId` | `bytes4` | The interfaceID of the asset being received. | #### Returns -| Name | Type | Description | -| -------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------- | -| `keys` | `bytes32[]` | An array of 3 x data keys: `LSP5ReceivedAssets[]`, `LSP5ReceivedAssets[index]` and `LSP5ReceivedAssetsMap:`. | -| `values` | `bytes[]` | An array of 3 x data values: the new length of `LSP5ReceivedAssets[]`, the address of the asset under `LSP5ReceivedAssets[index]` | +| Name | Type | Description | +| ---------------- | :---------: | -------------------------------------------------------------------- | +| `lsp5DataKeys` | `bytes32[]` | An array Data Keys used to update the [LSP-5-ReceivedAssets] data. | +| `lsp5DataValues` | `bytes[]` | An array Data Values used to update the [LSP-5-ReceivedAssets] data. |
### generateSentAssetKeys -```solidity -function generateSentAssetKeys( - address sender, - bytes32 assetMapKey, - uint128 assetIndex -) internal view returns (bytes32[] keys, bytes[] values); -``` - -Generate an array of data key/value pairs to be set on the sender address after sending assets. - -#### Parameters - -| Name | Type | Description | -| ------------- | :-------: | ----------------------------------------------------------------------------------------------------- | -| `sender` | `address` | The address sending the asset and where the LSP5 data keys should be updated. | -| `assetMapKey` | `bytes32` | The `LSP5ReceivedAssetMap:` data key of the asset being sent containing the interfaceId of the | -| `assetIndex` | `uint128` | The index at which the asset is stored under the `LSP5ReceivedAssets[]` Array. | - -#### Returns - -| Name | Type | Description | -| -------- | :---------: | --------------------------------------------------------------------------------------------------------------------------------- | -| `keys` | `bytes32[]` | An array of 3 x data keys: `LSP5ReceivedAssets[]`, `LSP5ReceivedAssets[index]` and `LSP5ReceivedAssetsMap:`. | -| `values` | `bytes[]` | An array of 3 x data values: the new length of `LSP5ReceivedAssets[]`, the address of the asset under `LSP5ReceivedAssets[index]` | - -
- -### getLSP5ReceivedAssetsCount +:::caution Warning -:::info - -This function does not return a number but the raw bytes stored under the `LSP5ReceivedAssets[]` Array data key. +Returns empty arrays when encountering errors. Otherwise the arrays must have at least 3 data keys and 3 data values. ::: ```solidity -function getLSP5ReceivedAssetsCount(contract IERC725Y account) internal view returns (bytes); +function generateSentAssetKeys( + address sender, + address assetAddress +) internal view returns (bytes32[] lsp5DataKeys, bytes[] lsp5DataValues); ``` -Get the total number of asset addresses stored under the `LSP5ReceivedAssets[]` Array data key. +Generate an array of Data Key/Value pairs to be set on the sender address after sending assets. #### Parameters -| Name | Type | Description | -| --------- | :-----------------: | ---------------------------------------------------- | -| `account` | `contract IERC725Y` | The ERC725Y smart contract to read the storage from. | +| Name | Type | Description | +| -------------- | :-------: | ----------------------------------------------------------------------------- | +| `sender` | `address` | The address sending the asset and where the LSP5 data keys should be updated. | +| `assetAddress` | `address` | The address of the asset that is being sent. | #### Returns -| Name | Type | Description | -| ---- | :-----: | --------------------------------------------------------------- | -| `0` | `bytes` | The raw bytes stored under the `LSP5ReceivedAssets[]` data key. | +| Name | Type | Description | +| ---------------- | :---------: | -------------------------------------------------------------------- | +| `lsp5DataKeys` | `bytes32[]` | An array Data Keys used to update the [LSP-5-ReceivedAssets] data. | +| `lsp5DataValues` | `bytes[]` | An array Data Values used to update the [LSP-5-ReceivedAssets] data. |
-## Errors - -### InvalidLSP5ReceivedAssetsArrayLength - -:::note References - -- Specification details: [**LSP-5-ReceivedAssets**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-5-ReceivedAssets.md#,)) -- Solidity implementation: [`LSP5Utils.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP5ReceivedAssets/LSP5Utils.sol) -- Error signature: `,)` -- Error hash: `0x9f47dbd3` - -::: +### getLSP5ArrayLengthBytes ```solidity -InvalidLSP5ReceivedAssetsArrayLength(bytes,uint256); +function getLSP5ArrayLengthBytes(contract IERC725Y erc725YContract) internal view returns (bytes); ``` -Reverts when the value stored under the 'LSP5ReceivedAssets[]' Array data key is not valid. -The value stored under this data key should be exactly 16 bytes long. -Only possible valid values are: - -- any valid uint128 values - _e.g: `0x00000000000000000000000000000000` (zero), empty array, no assets received._ - _e.g. `0x00000000000000000000000000000005` (non-zero), 5 array elements, 5 assets received._ - -- `0x` (nothing stored under this data key, equivalent to empty array +Get the raw bytes value stored under the `_LSP5_RECEIVED_ASSETS_ARRAY_KEY`. #### Parameters -| Name | Type | Description | -| -------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------- | -| `invalidValueStored` | `bytes` | invalidValueLength The invalid number of bytes stored under the `LSP5ReceivedAssets[]` data key (MUST be exactly 16 bytes long). | -| `invalidValueLength` | `uint256` | The invalid number of bytes stored under the `LSP5ReceivedAssets[]` data key (MUST be exactly 16 bytes long). | +| Name | Type | Description | +| ----------------- | :-----------------: | ----------------------------------------------- | +| `erc725YContract` | `contract IERC725Y` | The contract to query the ERC725Y storage from. | -
- -### MaxLSP5ReceivedAssetsCountReached - -:::note References - -- Specification details: [**LSP-5-ReceivedAssets**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-5-ReceivedAssets.md#)) -- Solidity implementation: [`LSP5Utils.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP5ReceivedAssets/LSP5Utils.sol) -- Error signature: `)` -- Error hash: `0x59d76dc3` - -::: - -```solidity -MaxLSP5ReceivedAssetsCountReached(address); -``` - -Reverts when the `LSP5ReceivedAssets[]` Array reaches its maximum limit (`max(uint128)`) - -#### Parameters - -| Name | Type | Description | -| -------------------- | :-------: | ------------------------------------------------------ | -| `notRegisteredAsset` | `address` | The address of the asset that could not be registered. | - -
- -### ReceivedAssetsIndexSuperiorToUint128 - -:::note References - -- Specification details: [**LSP-5-ReceivedAssets**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-5-ReceivedAssets.md#)) -- Solidity implementation: [`LSP5Utils.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP5ReceivedAssets/LSP5Utils.sol) -- Error signature: `)` -- Error hash: `0x59d76dc3` - -::: - -```solidity -ReceivedAssetsIndexSuperiorToUint128(uint256); -``` - -Reverts when the received assets index is superior to `max(uint128)` - -#### Parameters +#### Returns -| Name | Type | Description | -| ------- | :-------: | -------------------------- | -| `index` | `uint256` | The received assets index. | +| Name | Type | Description | +| ---- | :-----: | ----------------------------------------------- | +| `0` | `bytes` | The raw bytes value stored under this data key. |
diff --git a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts index 593418596..dca738edc 100644 --- a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts +++ b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP.behaviour.ts @@ -22,6 +22,8 @@ import { LSP1UniversalReceiverDelegateUP__factory, LSP7MintWhenDeployed__factory, LSP7MintWhenDeployed, + GenericExecutorWithBalanceOfFunction, + GenericExecutorWithBalanceOfFunction__factory, } from '../../types'; // helpers @@ -104,9 +106,9 @@ export const shouldBehaveLikeLSP1Delegate = ( context = await buildContext(); }); - describe('When testing EOA call to URD through the UR function', () => { + describe.only('When testing EOA call to URD through the UR function', () => { describe('when calling with token/vault typeId', () => { - it('should revert with custom error', async () => { + it('should revrt with custom error', async () => { const URD_TypeIds = [ LSP1_TYPE_IDS.LSP7Tokens_RecipientNotification, LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, @@ -132,18 +134,47 @@ export const shouldBehaveLikeLSP1Delegate = ( }); describe('when calling with random bytes32 typeId', () => { - it('should pass and return typeId out of scope return value', async () => { - const result = await context.universalProfile1 - .connect(context.accounts.any) - .callStatic.universalReceiver(LSP1_HOOK_PLACEHOLDER, '0x'); + describe('when caller is an EOA', () => { + it('should revert with custom error `CannotRegisterEOAsAsAssets`', async () => { + await expect( + context.universalProfile1 + .connect(context.accounts.any) + .callStatic.universalReceiver(LSP1_HOOK_PLACEHOLDER, '0x'), + ) + .to.be.revertedWithCustomError( + context.lsp1universalReceiverDelegateUP, + 'CannotRegisterEOAsAsAssets', + ) + .withArgs(context.accounts.any.address); + }); + }); - const [resultDelegate, resultTypeID] = abiCoder.decode(['bytes', 'bytes'], result); + describe('when caller is a contract', () => { + it("should pass and return 'LSP1: typeId out of scope'", async () => { + const universalReceiverCalldata = context.universalProfile2.interface.encodeFunctionData( + 'universalReceiver', + [LSP1_HOOK_PLACEHOLDER, '0x'], + ); - expect(resultDelegate).to.equal( - ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: typeId out of scope')), - ); + const result = await context.universalProfile1 + .connect(context.accounts.owner1) + .callStatic.execute( + OPERATION_TYPES.CALL, + context.universalProfile2.address, + 0, + universalReceiverCalldata, + ); + + const [decodedResult] = abiCoder.decode(['bytes'], result); - expect(resultTypeID).to.equal('0x'); + const [resultDelegate, resultTypeID] = abiCoder.decode(['bytes', 'bytes'], decodedResult); + + expect(resultDelegate).to.equal( + ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: typeId out of scope')), + ); + + expect(resultTypeID).to.equal('0x'); + }); }); }); }); @@ -889,7 +920,7 @@ export const shouldBehaveLikeLSP1Delegate = ( ); }); - it("should not revert and return 'LSP1: asset sent is not registered' with empty LSP7 token transfer", async () => { + it("should not revert and return 'LSP5: Error generating data key/value pairs' with empty LSP7 token transfer", async () => { const txParams = { from: context.universalProfile1.address, to: context.accounts.random.address, @@ -918,7 +949,9 @@ export const shouldBehaveLikeLSP1Delegate = ( const expectedReturnedValues = abiCoder.encode( ['bytes', 'bytes'], [ - ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: asset sent is not registered')), + ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('LSP5: Error generating data key/value pairs'), + ), '0x', ], ); @@ -946,53 +979,112 @@ export const shouldBehaveLikeLSP1Delegate = ( describe('when a non-LSP7 token contract calls the `universalReceiver(...)` function', () => { let notTokenContract: GenericExecutor; + let notTokenContractWithBalanceOfFunction: GenericExecutorWithBalanceOfFunction; before(async () => { notTokenContract = await new GenericExecutor__factory(context.accounts.random).deploy(); + + notTokenContractWithBalanceOfFunction = + await new GenericExecutorWithBalanceOfFunction__factory(context.accounts.random).deploy(); }); - it("should not revert and return 'LSP1: asset sent is not registered' when calling with typeId == LSP7Tokens_SenderNotification", async () => { - const universalReceiverPayload = context.universalProfile1.interface.encodeFunctionData( - 'universalReceiver', - [LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, '0x'], - ); + describe('when a non-LSP7 token contract has `balanceOf(address)` functions', () => { + it("should not revert and return 'LSP1: full balance is not sent' when calling with typeId == LSP7Tokens_SenderNotification", async () => { + const universalReceiverPayload = context.universalProfile1.interface.encodeFunctionData( + 'universalReceiver', + [LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, '0x'], + ); - // check that it does not revert - await expect( - await notTokenContract.call( - context.universalProfile1.address, - 0, - universalReceiverPayload, - ), - ).to.not.be.reverted; + // check that it does not revert + await expect( + await notTokenContractWithBalanceOfFunction.call( + context.universalProfile1.address, + 0, + universalReceiverPayload, + ), + ).to.not.be.reverted; - // check that it returns the correct string - const universalReceiverResult = await notTokenContract.callStatic.call( - context.universalProfile1.address, - 0, - universalReceiverPayload, - ); + // check that it returns the correct string + const universalReceiverResult = + await notTokenContractWithBalanceOfFunction.callStatic.call( + context.universalProfile1.address, + 0, + universalReceiverPayload, + ); - const [genericExecutorResult] = abiCoder.decode(['bytes'], universalReceiverResult); + const [genericExecutorResult] = abiCoder.decode(['bytes'], universalReceiverResult); - const [resultDelegate] = abiCoder.decode(['bytes', 'bytes'], genericExecutorResult); + const [resultDelegate] = abiCoder.decode(['bytes', 'bytes'], genericExecutorResult); - expect(resultDelegate).to.equal( - ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: asset sent is not registered')), - ); + expect(resultDelegate).to.equal( + ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: full balance is not sent')), + ); - // check that the correct string is emitted in the event - await expect( - notTokenContract.call(context.universalProfile1.address, 0, universalReceiverPayload), - ) - .to.emit(context.universalProfile1, 'UniversalReceiver') - .withArgs( - notTokenContract.address, + // check that the correct string is emitted in the event + await expect( + notTokenContractWithBalanceOfFunction.call( + context.universalProfile1.address, + 0, + universalReceiverPayload, + ), + ) + .to.emit(context.universalProfile1, 'UniversalReceiver') + .withArgs( + notTokenContractWithBalanceOfFunction.address, + 0, + LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, + '0x', + genericExecutorResult, + ); + }); + }); + + describe("when a non-LSP7 token contract doesn't have `balanceOf(address)` functions", () => { + it("should not revert and return 'LSP1: `balanceOf(address)` function not found' when calling with typeId == LSP7Tokens_SenderNotification", async () => { + const universalReceiverPayload = context.universalProfile1.interface.encodeFunctionData( + 'universalReceiver', + [LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, '0x'], + ); + + // check that it does not revert + await expect( + await notTokenContract.call( + context.universalProfile1.address, + 0, + universalReceiverPayload, + ), + ).to.not.be.reverted; + + // check that it returns the correct string + const universalReceiverResult = await notTokenContract.callStatic.call( + context.universalProfile1.address, 0, - LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, - '0x', - genericExecutorResult, + universalReceiverPayload, ); + + const [genericExecutorResult] = abiCoder.decode(['bytes'], universalReceiverResult); + + const [resultDelegate] = abiCoder.decode(['bytes', 'bytes'], genericExecutorResult); + + expect(resultDelegate).to.equal( + ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('LSP1: `balanceOf(address)` function not found'), + ), + ); + + // check that the correct string is emitted in the event + await expect( + notTokenContract.call(context.universalProfile1.address, 0, universalReceiverPayload), + ) + .to.emit(context.universalProfile1, 'UniversalReceiver') + .withArgs( + notTokenContract.address, + 0, + LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, + '0x', + genericExecutorResult, + ); + }); }); }); }); @@ -1040,19 +1132,29 @@ export const shouldBehaveLikeLSP1Delegate = ( }); it('should revert when trying to transfer some tokens to UP but UP cannot register any more tokens', async () => { - const lsp1DelegateAddress = await context.universalProfile1['getData(bytes32)']( - ERC725YDataKeys.LSP1['LSP1UniversalReceiverDelegate'], - ); - - const lsp1DelegateInstance = await new LSP1UniversalReceiverDelegateUP__factory( - context.accounts.random, - ).attach(lsp1DelegateAddress); - // try to transfer (= mint) some tokens to the UP // this should revert because the UP cannot register any more tokens await expect(token.mint(context.universalProfile1.address, 10_000, false, '0x')) - .to.be.revertedWithCustomError(lsp1DelegateInstance, 'MaxLSP5ReceivedAssetsCountReached') - .withArgs(token.address); + .to.emit(context.universalProfile1, 'UniversalReceiver') + .withArgs( + token.address, + 0, + LSP1_TYPE_IDS.LSP7Tokens_RecipientNotification, + ethers.utils.solidityPack( + ['address', 'address', 'uint256', 'bytes'], + [ethers.constants.AddressZero, context.universalProfile1.address, 10_000, '0x'], + ), + abiCoder.encode( + ['bytes', 'bytes'], + [ + ethers.utils.solidityPack( + ['string'], + ['LSP5: Error generating data key/value pairs'], + ), + '0x', + ], + ), + ); }); }); @@ -1181,7 +1283,7 @@ export const shouldBehaveLikeLSP1Delegate = ( const lsp1ReturnedData = ethers.utils.defaultAbiCoder.encode( ['string', 'bytes'], - ['LSP1: asset data corrupted', '0x'], + ['LSP5: Error generating data key/value pairs', '0x'], ); await expect(tokenTransferTx) @@ -1259,8 +1361,8 @@ export const shouldBehaveLikeLSP1Delegate = ( ).toLowerCase(); const lsp1ReturnedData = ethers.utils.defaultAbiCoder.encode( - ['bytes', 'bytes'], - ['0x', '0x'], + ['string', 'bytes'], + ['LSP5: Error generating data key/value pairs', '0x'], ); await expect(tokenTransferTx) @@ -1274,10 +1376,18 @@ export const shouldBehaveLikeLSP1Delegate = ( ); }); - it('should de-register the asset properly', async () => { + it('should not de-register the asset', async () => { + // expect( + // await context.universalProfile1.getDataBatch([arrayKey, arrayIndexKey, assetMapKey]), + // ).to.deep.equal(['0x' + '00'.repeat(16), '0x', '0x']); + expect( await context.universalProfile1.getDataBatch([arrayKey, arrayIndexKey, assetMapKey]), - ).to.deep.equal(['0x' + '00'.repeat(16), '0x', '0x']); + ).to.deep.equal([ + '0x' + '00'.repeat(15) + '01', + token.address.toLowerCase(), + '0xda1f85e400000000000000000000000000000000cafecafe', + ]); }); }); @@ -1335,7 +1445,7 @@ export const shouldBehaveLikeLSP1Delegate = ( const lsp1ReturnedData = ethers.utils.defaultAbiCoder.encode( ['string', 'bytes'], - ['LSP1: asset data corrupted', '0x'], + ['LSP5: Error generating data key/value pairs', '0x'], ); await expect(tokenTransferTx) @@ -1428,7 +1538,7 @@ export const shouldBehaveLikeLSP1Delegate = ( it('it should emit UniversalReceiver event', async () => { const lsp1ReturnedData = ethers.utils.defaultAbiCoder.encode( ['string', 'bytes'], - ['LSP1: asset data corrupted', '0x'], + ['LSP10: Error generating data key/value pairs', '0x'], ); await expect(acceptOwnershipTx) @@ -1503,8 +1613,8 @@ export const shouldBehaveLikeLSP1Delegate = ( it('it should emit UniversalReceiver event', async () => { const lsp1ReturnedData = ethers.utils.defaultAbiCoder.encode( - ['bytes', 'bytes'], - ['0x', '0x'], + ['string', 'bytes'], + ['LSP10: Error generating data key/value pairs', '0x'], ); await expect(acceptOwnershipTx) @@ -1519,9 +1629,20 @@ export const shouldBehaveLikeLSP1Delegate = ( }); it('should de-register the asset properly', async () => { + // expect( + // await context.universalProfile1.getDataBatch([arrayKey, arrayIndexKey, vaultMapKey]), + // ).to.deep.equal(['0x' + '00'.repeat(16), '0x', '0x']); expect( - await context.universalProfile1.getDataBatch([arrayKey, arrayIndexKey, vaultMapKey]), - ).to.deep.equal(['0x' + '00'.repeat(16), '0x', '0x']); + await context.universalProfile1.getDataBatch([ + ERC725YDataKeys.LSP10['LSP10Vaults[]'].length, + ERC725YDataKeys.LSP10['LSP10Vaults[]'].index + '0'.repeat(32), + ERC725YDataKeys.LSP10.LSP10VaultsMap + vault.address.substring(2), + ]), + ).to.deep.equal([ + '0x' + '00'.repeat(15) + '01', + vault.address.toLowerCase(), + '0x28af17e600000000000000000000000000000000cafecafe', + ]); }); }); @@ -1576,7 +1697,7 @@ export const shouldBehaveLikeLSP1Delegate = ( it('it should emit UniversalReceiver event', async () => { const lsp1ReturnedData = ethers.utils.defaultAbiCoder.encode( ['string', 'bytes'], - ['LSP1: asset data corrupted', '0x'], + ['LSP10: Error generating data key/value pairs', '0x'], ); await expect(acceptOwnershipTx) @@ -2087,57 +2208,116 @@ export const shouldBehaveLikeLSP1Delegate = ( describe('when a non-LSP8 token contract calls the `universalReceiver(...)` function', () => { let notTokenContract: GenericExecutor; + let notTokenContractWithBalanceOfFunction: GenericExecutorWithBalanceOfFunction; before(async () => { notTokenContract = await new GenericExecutor__factory(context.accounts.random).deploy(); + + notTokenContractWithBalanceOfFunction = + await new GenericExecutorWithBalanceOfFunction__factory(context.accounts.random).deploy(); }); - it("should not revert and return 'LSP1: asset sent is not registered' when calling with typeId == LSP8Tokens_SenderNotification", async () => { - const universalReceiverPayload = context.universalProfile1.interface.encodeFunctionData( - 'universalReceiver', - [LSP1_TYPE_IDS.LSP8Tokens_SenderNotification, '0x'], - ); + describe('when a non-LSP8 token contract has `balanceOf(address)` functions', () => { + it("should not revert and return 'LSP1: full balance is not sent' when calling with typeId == LSP8Tokens_SenderNotification", async () => { + const universalReceiverPayload = context.universalProfile1.interface.encodeFunctionData( + 'universalReceiver', + [LSP1_TYPE_IDS.LSP8Tokens_SenderNotification, '0x'], + ); - // check that it does not revert - await expect( - await notTokenContract.call( - context.universalProfile1.address, - 0, - universalReceiverPayload, - ), - ).to.not.be.reverted; + // check that it does not revert + await expect( + await notTokenContractWithBalanceOfFunction.call( + context.universalProfile1.address, + 0, + universalReceiverPayload, + ), + ).to.not.be.reverted; - // check that it returns the correct string - const universalReceiverResult = await notTokenContract.callStatic.call( - context.universalProfile1.address, - 0, - universalReceiverPayload, - ); + // check that it returns the correct string + const universalReceiverResult = + await notTokenContractWithBalanceOfFunction.callStatic.call( + context.universalProfile1.address, + 0, + universalReceiverPayload, + ); - const [genericExecutorResult] = abiCoder.decode(['bytes'], universalReceiverResult); + const [genericExecutorResult] = abiCoder.decode(['bytes'], universalReceiverResult); - const [resultDelegate] = abiCoder.decode(['bytes', 'bytes'], genericExecutorResult); + const [resultDelegate] = abiCoder.decode(['bytes', 'bytes'], genericExecutorResult); - expect(resultDelegate).to.equal( - ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: asset sent is not registered')), - ); + expect(resultDelegate).to.equal( + ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: full balance is not sent')), + ); - // check that the correct string is emitted in the event - await expect( - await notTokenContract.call( + // check that the correct string is emitted in the event + await expect( + await notTokenContractWithBalanceOfFunction.call( + context.universalProfile1.address, + 0, + universalReceiverPayload, + ), + ) + .to.emit(context.universalProfile1, 'UniversalReceiver') + .withArgs( + notTokenContractWithBalanceOfFunction.address, + 0, + LSP1_TYPE_IDS.LSP8Tokens_SenderNotification, + '0x', + genericExecutorResult, + ); + }); + }); + + describe("when a non-LSP8 token contract doesn't have `balanceOf(address)` functions", () => { + it("should not revert and return 'LSP1: `balanceOf(address)` function not found' when calling with typeId == LSP8Tokens_SenderNotification", async () => { + const universalReceiverPayload = context.universalProfile1.interface.encodeFunctionData( + 'universalReceiver', + [LSP1_TYPE_IDS.LSP8Tokens_SenderNotification, '0x'], + ); + + // check that it does not revert + await expect( + await notTokenContract.call( + context.universalProfile1.address, + 0, + universalReceiverPayload, + ), + ).to.not.be.reverted; + + // check that it returns the correct string + const universalReceiverResult = await notTokenContract.callStatic.call( context.universalProfile1.address, 0, universalReceiverPayload, - ), - ) - .to.emit(context.universalProfile1, 'UniversalReceiver') - .withArgs( - notTokenContract.address, - 0, - LSP1_TYPE_IDS.LSP8Tokens_SenderNotification, - '0x', - genericExecutorResult, ); + + const [genericExecutorResult] = abiCoder.decode(['bytes'], universalReceiverResult); + + const [resultDelegate] = abiCoder.decode(['bytes', 'bytes'], genericExecutorResult); + + expect(resultDelegate).to.equal( + ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('LSP1: `balanceOf(address)` function not found'), + ), + ); + + // check that the correct string is emitted in the event + await expect( + await notTokenContract.call( + context.universalProfile1.address, + 0, + universalReceiverPayload, + ), + ) + .to.emit(context.universalProfile1, 'UniversalReceiver') + .withArgs( + notTokenContract.address, + 0, + LSP1_TYPE_IDS.LSP8Tokens_SenderNotification, + '0x', + genericExecutorResult, + ); + }); }); }); }); @@ -2420,31 +2600,68 @@ export const shouldBehaveLikeLSP1Delegate = ( }); }); - describe('When renouncing Ownership of VaultC from UP2', () => { + describe('When renouncing ownership of a vault from UP2', () => { let tx: Transaction; + let someVault: LSP9Vault; + let dataKeys: string[]; + let dataValues: string[]; + before(async () => { + let LSP10ArrayLength = await context.universalProfile2.getData( + ERC725YDataKeys.LSP10['LSP10Vaults[]'].length, + ); + + if (LSP10ArrayLength === '0x') { + LSP10ArrayLength = ARRAY_LENGTH.ZERO; + } + + someVault = await new LSP9Vault__factory(context.accounts.random).deploy( + context.universalProfile2.address, + ); + + dataKeys = [ + ERC725YDataKeys.LSP10.LSP10VaultsMap + someVault.address.substring(2), + ERC725YDataKeys.LSP10['LSP10Vaults[]'].length, + ERC725YDataKeys.LSP10['LSP10Vaults[]'].index + LSP10ArrayLength.substring(2), + ]; + + dataValues = [ + INTERFACE_IDS.LSP9Vault + LSP10ArrayLength.substring(2), + `0x${ethers.BigNumber.from(LSP10ArrayLength) + .add(1) + .toHexString() + .substring(2) + .padStart(32, '00')}`, + someVault.address, + ]; + + expect(await context.universalProfile2.getDataBatch(dataKeys)).to.deep.equal(dataValues); + const renounceOwnershipCalldata = - lsp9VaultC.interface.encodeFunctionData('renounceOwnership'); + someVault.interface.encodeFunctionData('renounceOwnership'); + + // Skip 1000 blocks + await network.provider.send('hardhat_mine', [ethers.utils.hexValue(1000)]); // Call renounceOwnership for the first time await context.universalProfile2 .connect(context.accounts.owner2) - .execute(OPERATION_TYPES.CALL, lsp9VaultC.address, 0, renounceOwnershipCalldata); + .execute(OPERATION_TYPES.CALL, someVault.address, 0, renounceOwnershipCalldata); // Skip 199 block to reach the time where renouncing ownership can happen await network.provider.send('hardhat_mine', [ethers.utils.hexValue(199)]); - // Call renounceOwnership for the second time tx = await context.universalProfile2 .connect(context.accounts.owner2) - .execute(OPERATION_TYPES.CALL, lsp9VaultC.address, 0, renounceOwnershipCalldata); + .execute(OPERATION_TYPES.CALL, someVault.address, 0, renounceOwnershipCalldata); }); it('Should emit `UnviersalReceiver` event', async () => { - await expect(tx) + // Call renounceOwnership for the second time + expect(tx) .to.emit(context.universalProfile2, 'UniversalReceiver') .withArgs( - lsp9VaultC.address, + someVault.address, 0, LSP1_TYPE_IDS.LSP9OwnershipTransferred_SenderNotification, '0x', @@ -2452,17 +2669,16 @@ export const shouldBehaveLikeLSP1Delegate = ( ); }); - it('should remove all lsp10keys : arrayLength 0, no map, no VaultC address in UP2', async () => { - const [mapValue, arrayLength, elementAddress] = - await context.universalProfile2.getDataBatch([ - ERC725YDataKeys.LSP10.LSP10VaultsMap + lsp9VaultC.address.substring(2), - ERC725YDataKeys.LSP10['LSP10Vaults[]'].length, - ERC725YDataKeys.LSP10['LSP10Vaults[]'].index + '00000000000000000000000000000000', - ]); - - expect(mapValue).to.equal('0x'); - expect(arrayLength).to.equal(ARRAY_LENGTH.ZERO); - expect(elementAddress).to.equal('0x'); + it('should remove the LSP10 data keys assigned for `someVault`', async () => { + expect(await context.universalProfile2.getDataBatch(dataKeys)).to.deep.equal([ + '0x', + `0x${ethers.BigNumber.from(dataValues[1]) + .sub(1) + .toHexString() + .substring(2) + .padStart(32, '00')}`, + '0x', + ]); }); }); @@ -2508,17 +2724,23 @@ export const shouldBehaveLikeLSP1Delegate = ( // deploy a Vault setting the UP as owner // this should revert because the UP has already the max number of vaults allowed - const lsp1DelegateAddress = await context.universalProfile1['getData(bytes32)']( - ERC725YDataKeys.LSP1['LSP1UniversalReceiverDelegate'], - ); - const lsp1DelegateInstance = await new LSP1UniversalReceiverDelegateUP__factory( - context.accounts.random, - ).attach(lsp1DelegateAddress); + const tx = await new LSP9Vault__factory(context.accounts.random).deploy( + context.universalProfile1.address, + ); - await expect( - new LSP9Vault__factory(context.accounts.random).deploy(context.universalProfile1.address), - ).to.be.revertedWithCustomError(lsp1DelegateInstance, 'MaxLSP10VaultsCountReached'); + await expect(tx.deployTransaction) + .to.emit(context.universalProfile1, 'UniversalReceiver') + .withArgs( + tx.address, + 0, + LSP1_TYPE_IDS.LSP9OwnershipTransferred_RecipientNotification, + '0x', + ethers.utils.defaultAbiCoder.encode( + ['string', 'bytes'], + ['LSP10: Error generating data key/value pairs', '0x'], + ), + ); }); }); }); @@ -2613,7 +2835,9 @@ export const shouldBehaveLikeLSP1Delegate = ( const expectedReturnedValues = abiCoder.encode( ['bytes', 'bytes'], [ - ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: asset sent is not registered')), + ethers.utils.hexlify( + ethers.utils.toUtf8Bytes('LSP10: Error generating data key/value pairs'), + ), '0x', ], ); @@ -2653,6 +2877,7 @@ export const shouldBehaveLikeLSP1Delegate = ( describe("when having a Vault set in LSP10 before it's ownership was transfered", () => { let vault: LSP9Vault; let vaultOwner: SignerWithAddress; + const bytes16Value1 = '0x' + '00'.repeat(15) + '01'; before(async () => { vaultOwner = context.accounts.any; @@ -2666,9 +2891,8 @@ export const shouldBehaveLikeLSP1Delegate = ( ERC725YDataKeys.LSP10['LSP10Vaults[]'].index + '00'.repeat(16), ERC725YDataKeys.LSP10['LSP10VaultsMap'] + vault.address.substring(2), ]; - const lsp10DataValues = [ - abiCoder.encode(['uint256'], ['1']), + bytes16Value1, vault.address, abiCoder.encode(['bytes4', 'uint64'], [INTERFACE_IDS.LSP9Vault, 0]), ]; @@ -2690,7 +2914,7 @@ export const shouldBehaveLikeLSP1Delegate = ( ERC725YDataKeys.LSP10['LSP10Vaults[]'].length, ); - expect(lsp10VaultArrayLengthValue).to.equal(abiCoder.encode(['uint256'], ['1'])); + expect(lsp10VaultArrayLengthValue).to.equal(bytes16Value1); const lsp10VaultArrayIndexValue = await context.universalProfile1['getData(bytes32)']( ERC725YDataKeys.LSP10['LSP10Vaults[]'].index + '00'.repeat(16), @@ -2708,7 +2932,7 @@ export const shouldBehaveLikeLSP1Delegate = ( ); }); - it("should not revert and return the string 'LSP1: asset received is already registered'", async () => { + it("should not revert and return the string 'LSP10: Error generating data key/value pairs'", async () => { // 1. transfer ownership of the vault to the UP await vault.connect(vaultOwner).transferOwnership(context.universalProfile1.address); @@ -2736,7 +2960,7 @@ export const shouldBehaveLikeLSP1Delegate = ( ['bytes', 'bytes'], [ ethers.utils.hexlify( - ethers.utils.toUtf8Bytes('LSP1: asset received is already registered'), + ethers.utils.toUtf8Bytes('LSP10: Error generating data key/value pairs'), ), '0x', ], @@ -2759,7 +2983,7 @@ export const shouldBehaveLikeLSP1Delegate = ( ERC725YDataKeys.LSP10['LSP10Vaults[]'].length, ); - expect(lsp10VaultArrayLengthValue).to.equal(abiCoder.encode(['uint256'], ['1'])); + expect(lsp10VaultArrayLengthValue).to.equal(bytes16Value1); const lsp10VaultArrayIndexValue = await context.universalProfile1['getData(bytes32)']( ERC725YDataKeys.LSP10['LSP10Vaults[]'].index + '00'.repeat(16), @@ -2777,6 +3001,120 @@ export const shouldBehaveLikeLSP1Delegate = ( ); }); }); + + describe('when UP is owner by an EOA', () => { + before('deploying new URD', async () => { + // Transfer ownership of UP1 to EOA1 + await context.universalProfile1 + .connect(context.accounts.owner1) + .transferOwnership(context.accounts.owner1.address); + + await context.universalProfile1.connect(context.accounts.owner1).acceptOwnership(); + + // Transfer ownership of UP2 to EOA2 + await context.universalProfile2 + .connect(context.accounts.owner2) + .transferOwnership(context.accounts.owner2.address); + + await context.universalProfile2.connect(context.accounts.owner2).acceptOwnership(); + }); + + describe('when receiving LSP7', () => { + it('should not revert', async () => { + // Deploy LSP7 (mint on SC level 1000 tokens) + const LSP7 = await new LSP7MintWhenDeployed__factory(context.accounts.owner1).deploy( + 'MyToken', + 'MTK', + context.universalProfile1.address, + ); + + expect(await LSP7.balanceOf(context.universalProfile1.address)).to.equal(1000); + expect(await LSP7.balanceOf(context.universalProfile2.address)).to.equal(0); + + // Encode LSP7 tokens tarnsfer (UP1 to UP2) + const LSP7_TransferCalldata = LSP7.interface.encodeFunctionData('transfer', [ + context.universalProfile1.address, + context.universalProfile2.address, + 1, + false, + '0x', + ]); + + // Transfer LSP7 tokens + await context.universalProfile1 + .connect(context.accounts.owner1) + .execute(OPERATION_TYPES.CALL, LSP7.address, 0, LSP7_TransferCalldata); + + expect(await LSP7.balanceOf(context.universalProfile1.address)).to.equal(999); + expect(await LSP7.balanceOf(context.universalProfile2.address)).to.equal(1); + }); + }); + + describe('when receiving LSP8', () => { + it('should not revert', async () => { + // Deploy LSP8 + const LSP8 = await new LSP8Tester__factory(context.accounts.owner1).deploy( + 'MyToken', + 'MTK', + context.universalProfile1.address, + ); + // Mint token for UP1 + await LSP8.mint(context.universalProfile1.address, '0x' + '0'.repeat(64), true, '0x'); + + expect(await LSP8.tokenOwnerOf('0x' + '0'.repeat(64))).to.equal( + context.universalProfile1.address, + ); + + // Encode LSP8 token tarnsfer (UP1 to UP2) + const LSP8_TransferCalldata = LSP8.interface.encodeFunctionData('transfer', [ + context.universalProfile1.address, + context.universalProfile2.address, + '0x' + '0'.repeat(64), + false, + '0x', + ]); + + // Transfer LSP8 token + await context.universalProfile1 + .connect(context.accounts.owner1) + .execute(OPERATION_TYPES.CALL, LSP8.address, 0, LSP8_TransferCalldata); + + expect(await LSP8.tokenOwnerOf('0x' + '0'.repeat(64))).to.equal( + context.universalProfile2.address, + ); + }); + }); + + describe('when becoming the owner of the LSP9 Vault', () => { + it('should not revert', async () => { + // Deploy LSP9 (UP1 ownwer) + const LSP9 = await new LSP9Vault__factory(context.accounts.owner1).deploy( + context.universalProfile1.address, + ); + + expect(await LSP9.owner()).to.equal(context.universalProfile1.address); + + // Encode LSP9 transfer & accept ownership (UP1 to UP2) + const LSP9_TransferOwnerhsipCalldata = LSP9.interface.encodeFunctionData( + 'transferOwnership', + [context.universalProfile2.address], + ); + const LSP9_AcceptOwnerhsipCalldata = LSP9.interface.encodeFunctionData('acceptOwnership'); + + // Transfer Ownership of LSP9 + await context.universalProfile1 + .connect(context.accounts.owner1) + .execute(OPERATION_TYPES.CALL, LSP9.address, 0, LSP9_TransferOwnerhsipCalldata); + + // Accept Ownership of LSP9 + await context.universalProfile2 + .connect(context.accounts.owner2) + .execute(OPERATION_TYPES.CALL, LSP9.address, 0, LSP9_AcceptOwnerhsipCalldata); + + expect(await LSP9.owner()).to.equal(context.universalProfile2.address); + }); + }); + }); }; export type LSP1DelegateInitializeTestContext = { diff --git a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault.behaviour.ts b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault.behaviour.ts index d743e55ac..0049d78f3 100644 --- a/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault.behaviour.ts +++ b/tests/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateVault.behaviour.ts @@ -13,6 +13,7 @@ import { LSP1UniversalReceiverDelegateVault, LSP7MintWhenDeployed__factory, LSP7MintWhenDeployed, + LSP1UniversalReceiverDelegateVault__factory, } from '../../types'; import { ARRAY_LENGTH, LSP1_HOOK_PLACEHOLDER, abiCoder } from '../utils/helpers'; @@ -81,9 +82,9 @@ export const shouldBehaveLikeLSP1Delegate = (buildContext: () => Promise { + describe.only('When testing EOA call to URD through the UR function', () => { describe('when calling with tokens typeId', () => { - it('should revert with custom error', async () => { + it('should return error message', async () => { const URD_TypeIds = [ LSP1_TYPE_IDS.LSP7Tokens_RecipientNotification, LSP1_TYPE_IDS.LSP7Tokens_SenderNotification, @@ -107,18 +108,30 @@ export const shouldBehaveLikeLSP1Delegate = (buildContext: () => Promise { - it('should pass and return typeId out of scope return value', async () => { + it("should pass and return 'LSP1: typeId out of scope'", async () => { const Vault_TypeIds = [ LSP1_TYPE_IDS.LSP14OwnershipTransferred_RecipientNotification, LSP1_TYPE_IDS.LSP14OwnershipTransferred_SenderNotification, ]; for (let i = 0; i < Vault_TypeIds.length; i++) { - const result = await context.lsp9Vault1 - .connect(context.accounts.any) - .callStatic.universalReceiver(Vault_TypeIds[i], '0x'); + const universalReceiverCalldata = context.lsp9Vault2.interface.encodeFunctionData( + 'universalReceiver', + [Vault_TypeIds[i], '0x'], + ); + + const result = await context.universalProfile + .connect(context.accounts.owner1) + .callStatic.execute( + OPERATION_TYPES.CALL, + context.lsp9Vault1.address, + 0, + universalReceiverCalldata, + ); + + const [decodedResult] = abiCoder.decode(['bytes'], result); - const [resultDelegate, resultTypeID] = abiCoder.decode(['bytes', 'bytes'], result); + const [resultDelegate, resultTypeID] = abiCoder.decode(['bytes', 'bytes'], decodedResult); expect(resultDelegate).to.equal( ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: typeId out of scope')), @@ -130,18 +143,47 @@ export const shouldBehaveLikeLSP1Delegate = (buildContext: () => Promise { - it('should pass and return typeId out of scope return value', async () => { - const result = await context.lsp9Vault1 - .connect(context.accounts.any) - .callStatic.universalReceiver(LSP1_HOOK_PLACEHOLDER, '0x'); + describe('when caller is an EOA', () => { + it('should revert with custom error `CannotRegisterEOAsAsAssets`', async () => { + await expect( + context.lsp9Vault1 + .connect(context.accounts.any) + .callStatic.universalReceiver(LSP1_HOOK_PLACEHOLDER, '0x'), + ) + .to.be.revertedWithCustomError( + context.lsp1universalReceiverDelegateVault, + 'CannotRegisterEOAsAsAssets', + ) + .withArgs(context.accounts.any.address); + }); + }); + + describe('when caller is a contract', () => { + it("should pass and return 'LSP1: typeId out of scope'", async () => { + const universalReceiverCalldata = context.lsp9Vault2.interface.encodeFunctionData( + 'universalReceiver', + [LSP1_HOOK_PLACEHOLDER, '0x'], + ); + + const result = await context.universalProfile + .connect(context.accounts.owner1) + .callStatic.execute( + OPERATION_TYPES.CALL, + context.lsp9Vault2.address, + 0, + universalReceiverCalldata, + ); - const [resultDelegate, resultTypeID] = abiCoder.decode(['bytes', 'bytes'], result); + const [decodedResult] = abiCoder.decode(['bytes'], result); - expect(resultDelegate).to.equal( - ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: typeId out of scope')), - ); + const [resultDelegate, resultTypeID] = abiCoder.decode(['bytes', 'bytes'], decodedResult); + + expect(resultDelegate).to.equal( + ethers.utils.hexlify(ethers.utils.toUtf8Bytes('LSP1: typeId out of scope')), + ); - expect(resultTypeID).to.equal('0x'); + expect(resultTypeID).to.equal('0x'); + }); }); }); }); @@ -892,7 +934,7 @@ export const shouldBehaveLikeLSP1Delegate = (buildContext: () => Promise Promise Promise { + it('should not de-register the asset', async () => { expect( await context.lsp9Vault1.getDataBatch([arrayKey, arrayIndexKey, assetMapKey]), - ).to.deep.equal(['0x' + '00'.repeat(16), '0x', '0x']); + ).to.deep.equal([ + '0x' + '00'.repeat(15) + '01', + token.address.toLowerCase(), + '0xda1f85e400000000000000000000000000000000cafecafe', + ]); }); }); @@ -1060,7 +1106,7 @@ export const shouldBehaveLikeLSP1Delegate = (buildContext: () => Promise Promise { + before('deploying new URD', async () => { + const newURD = await new LSP1UniversalReceiverDelegateVault__factory( + context.accounts.owner1, + ).deploy(); + + const LSP9_setDataCalldata = context.lsp9Vault1.interface.encodeFunctionData('setData', [ + ERC725YDataKeys.LSP1.LSP1UniversalReceiverDelegate, + newURD.address, + ]); + + await context.universalProfile + .connect(context.accounts.owner1) + .execute(OPERATION_TYPES.CALL, context.lsp9Vault1.address, 0, LSP9_setDataCalldata); + + await context.universalProfile + .connect(context.accounts.owner1) + .execute(OPERATION_TYPES.CALL, context.lsp9Vault2.address, 0, LSP9_setDataCalldata); + }); + + describe('when receiving LSP7', () => { + it('should not revert', async () => { + const LSP7 = await new LSP7MintWhenDeployed__factory(context.accounts.owner1).deploy( + 'MyToken', + 'MTK', + context.lsp9Vault1.address, + ); + + const LSP7_TransferCalldata = LSP7.interface.encodeFunctionData('transfer', [ + context.lsp9Vault1.address, + context.lsp9Vault2.address, + 1, + false, + '0x', + ]); + + const LSP9_ExecuteCalldata = context.lsp9Vault1.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + LSP7.address, + 0, + LSP7_TransferCalldata, + ]); + + expect(await LSP7.balanceOf(context.lsp9Vault1.address)).to.equal(1000); + expect(await LSP7.balanceOf(context.lsp9Vault2.address)).to.equal(0); + + await context.universalProfile + .connect(context.accounts.owner1) + .execute(OPERATION_TYPES.CALL, context.lsp9Vault1.address, 0, LSP9_ExecuteCalldata); + + expect(await LSP7.balanceOf(context.lsp9Vault1.address)).to.equal(999); + expect(await LSP7.balanceOf(context.lsp9Vault2.address)).to.equal(1); + }); + }); + + describe('when receiving LSP8', () => { + it('should not revert', async () => { + const LSP8 = await new LSP8Tester__factory(context.accounts.owner1).deploy( + 'MyToken', + 'MTK', + context.lsp9Vault1.address, + ); + await LSP8.mint(context.lsp9Vault1.address, '0x' + '0'.repeat(64), false, '0x'); + + const LSP8_TransferCalldata = LSP8.interface.encodeFunctionData('transfer', [ + context.lsp9Vault1.address, + context.lsp9Vault2.address, + '0x' + '0'.repeat(64), + false, + '0x', + ]); + + const LSP9_ExecuteCalldata = context.lsp9Vault1.interface.encodeFunctionData('execute', [ + OPERATION_TYPES.CALL, + LSP8.address, + 0, + LSP8_TransferCalldata, + ]); + + expect(await LSP8.tokenOwnerOf('0x' + '0'.repeat(64))).to.equal(context.lsp9Vault1.address); + + await context.universalProfile + .connect(context.accounts.owner1) + .execute(OPERATION_TYPES.CALL, context.lsp9Vault1.address, 0, LSP9_ExecuteCalldata); + + expect(await LSP8.tokenOwnerOf('0x' + '0'.repeat(64))).to.equal(context.lsp9Vault2.address); + }); + }); + }); };