From c1e1587d823ac9686a5b3f4b3eb5e4c1f146a911 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Fri, 7 Jul 2023 14:26:47 +0100 Subject: [PATCH 1/7] docs: update Natspec comments for LSP6 + add auto-generated doc --- contracts/LSP6KeyManager/ILSP6KeyManager.sol | 72 +- contracts/LSP6KeyManager/LSP6Errors.sol | 139 ++- contracts/LSP6KeyManager/LSP6KeyManager.sol | 11 +- .../LSP6KeyManager/LSP6KeyManagerCore.sol | 35 +- .../LSP6KeyManager/LSP6KeyManagerInit.sol | 7 +- .../LSP6KeyManager/LSP6KeyManager.md | 986 ++++++++++++++++++ dodoc/config.ts | 4 +- 7 files changed, 1164 insertions(+), 90 deletions(-) create mode 100644 docs/contracts/LSP6KeyManager/LSP6KeyManager.md diff --git a/contracts/LSP6KeyManager/ILSP6KeyManager.sol b/contracts/LSP6KeyManager/ILSP6KeyManager.sol index 0b8220ff2..8e290b426 100644 --- a/contracts/LSP6KeyManager/ILSP6KeyManager.sol +++ b/contracts/LSP6KeyManager/ILSP6KeyManager.sol @@ -12,10 +12,10 @@ interface ILSP6KeyManager is /* is ERC165 */ { /** - * @dev Emitted when a calldata payload that includes `selector` and `value` as msg.value was verified for `signer` - * @param signer the address of the controller that executed the calldata payload. + * @dev Emitted when the LSP6KeyManager contract verified the permissions of the `signer` successfully. + * @param signer the address of the controller that executed the calldata payload (either directly via {execute} or via meta transaction using {executeRelayCall}). * @param value the amount of native token to be transferred in the calldata payload. - * @param selector the bytes4 function of the function to run in the calldata payload. + * @param selector the bytes4 function of the function that was executed on the linked {target} */ event VerifiedCall( address indexed signer, @@ -24,21 +24,20 @@ interface ILSP6KeyManager is ); /** - * @notice returns the address of the account linked to this KeyManager - * @dev this can be a contract that implements - * - ERC725X only - * - ERC725Y only - * - any ERC725 based contract (so implementing both ERC725X and ERC725Y) - * + * @dev Get the address of the contract linked to this Key Manager. * @return the address of the linked account */ function target() external view returns (address); /** - * @notice get latest nonce for `from` in channel ID: `channelId` - * @dev use channel ID = 0 for sequential nonces, any other number for out-of-order execution (= execution in parallel) - * @param from the caller or signer address - * @param channelId the channel id to retrieve the nonce from + * @notice Get latest nonce for `from` in channel ID: `channelId`. + * + * @dev Get the nonce for a specific controller `from` address that can be used for signing relay transaction. + * + * @param from the address of the signer of the transaction. + * @param channelId the channel id that the signer wants to use for executing the transaction. + * + * @return the current nonce on a specific `channelId` */ function getNonce( address from, @@ -46,17 +45,26 @@ interface ILSP6KeyManager is ) external view returns (uint256); /** - * @notice execute the following payload on the ERC725Account: `payload` - * @dev the ERC725Account will return some data on successful call, or revert on failure - * @param payload the payload to execute. Obtained in web3 via encodeABI() - * @return the data being returned by the ERC725 Account + * @notice execute the following payload on the linked contract: `payload` + * + * @dev execute a `payload` on the linked {target} after having verified the permissions associated with the function being run. + * The `payload` MUST be a valid abi-encoded function call of one of the functions present in the linked {target}, otherwise the call will fail. + * The linked {target} will return some data on successful execution, or revert on failure. + * + * @param payload the abi-encoded function call to execute on the linked {target}. + * @return the abi-decoded data returned by the function called on the linked {target}. */ function execute( bytes calldata payload ) external payable returns (bytes memory); /** - * @dev batch `execute(bytes)` + * @dev Same as {execute} but execute a batch of payloads (abi-encoded function calls) in a single transaction. + * + * @param values An array of amount of native tokens to be transferred for each `payload`. + * @param payloads An array of abi-encoded function calls to execute successively on the linked {target}. + * + * @return An array of abi-decoded of return data returned by the functions called on the linked {target}. */ function executeBatch( uint256[] calldata values, @@ -64,12 +72,16 @@ interface ILSP6KeyManager is ) external payable returns (bytes[] memory); /** - * @dev allows anybody to execute given they have a signed message from an executor - * @param signature bytes32 ethereum signature - * @param nonce the address' nonce (in a specific `_channel`), obtained via `getNonce(...)`. Used to prevent replay attack - * @param validityTimestamps two `uint128` timestamps concatenated, the first timestamp determines from when the payload can be executed, the second timestamp delimits the end of the validity of the payload. If `validityTimestamps` is 0, the checks regardin the timestamps are skipped - * @param payload obtained via encodeABI() in web3 - * @return the data being returned by the ERC725 Account + * @dev Allows any address (executor) to execute a payload (= abi-encoded function call) in the linked {target} given they have a signed message from + * a controller with some permissions. + * + * @param signature a 65 bytes long signature for a meta transaction according to LSP6. + * @param nonce the nonce of the address that signed the calldata (in a specific `_channel`), obtained via {getNonce}. Used to prevent replay attack. + * @param validityTimestamps * Two `uint128` timestamps concatenated together that describes + * when the relay transaction is valid "from" (left `uint128`) and "until" as a deadline (right `uint128`). + * @param payload the abi-encoded function call to execute on the linked {target}. + * + * @return the data being returned by the function called on the linked {target}. */ function executeRelayCall( bytes calldata signature, @@ -79,7 +91,17 @@ interface ILSP6KeyManager is ) external payable returns (bytes memory); /** - * @dev batch `executeRelayCall(...)` + * @dev Same as {executeRelayCall} but execute a batch of signed calldata payloads (abi-encoded function calls) in a single transaction. + * The signed transactions can be from multiple controllers, not necessarely the same controller signer, as long as each of these controllers + * that signed have the right permissions related to the calldata `payload` they signed. + * + * @param signatures An array of 65 bytes long signatures for meta transactions according to LSP6. + * @param nonces An array of nonces of the addresses that signed the calldata payloads (in specific channels). Obtained via {getNonce}. Used to prevent replay attack. + * @param validityTimestamps An array of two `uint128` concatenated timestamps that describe when the relay transaction is valid "from" (left `uint128`) and "until" (right `uint128`). + * @param values An array of amount of native tokens to be transferred for each calldata `payload`. + * @param payloads An array of abi-encoded function calls to execute successively on the linked {target}. + * + * @return An array of abi-decoded return data returned by the functions called on the linked {target}. */ function executeRelayCallBatch( bytes[] calldata signatures, diff --git a/contracts/LSP6KeyManager/LSP6Errors.sol b/contracts/LSP6KeyManager/LSP6Errors.sol index 745231633..5ba32530d 100644 --- a/contracts/LSP6KeyManager/LSP6Errors.sol +++ b/contracts/LSP6KeyManager/LSP6Errors.sol @@ -2,74 +2,87 @@ pragma solidity ^0.8.4; /** - * @dev reverts when address `from` does not have any permissions set - * on the account linked to this Key Manager + * @dev Reverts when address `from` does not have any permissions set on the account linked to this Key Manager + * * @param from the address that does not have permissions */ error NoPermissionsSet(address from); /** - * @dev reverts when address `from` is not authorised to perform `permission` on the linked account - * @param permission permission required - * @param from address not-authorised + * @dev Reverts when address `from` is not authorised and does not have `permission` on the linked {target} + * + * @param from address The address that was not authorised. + * @param permission permission The permission required (_e.g: `SETDATA`, `CALL`, `TRANSFERVALUE`) */ error NotAuthorised(address from, string permission); /** - * @dev reverts when `from` is not authorised to make the call because of a not allowed standard, address or function. - * @param from address making the request - * @param to the address of an EOA or contract that `from` is trying to interact with - * @param selector if `to` is a contract, the bytes4 selector of the function that `from` is trying to call. + * @dev Reverts when `from` is not authorised to call the `execute(uint256,address,uint256,bytes)` function because of + * a not allowed callType, address, standard or function. + * + * @param from address The controller that tried to call the `execute(uint256,address,uint256,bytes)` function. + * @param to The address of an EOA or contract that `from` tried to call using the linked {target} + * @param selector If `to` is a contract, the bytes4 selector of the function that `from` is trying to call. * If no function is called (e.g: a native token transfer), selector = 0x00000000 */ error NotAllowedCall(address from, address to, bytes4 selector); /** - * @dev reverts when address `from` is not authorised to set the key `disallowedKey` on the linked account - * @param from address making the request - * @param disallowedKey a bytes32 key that `from` is not authorised to set on the ERC725Y storage + * @dev Reverts when address `from` is not authorised to set the key `disallowedKey` on the linked {target}. + * + * @param from address The controller that tried to `setData` on the linked {target}. + * @param disallowedKey A bytes32 data key that `from` is not authorised to set on the ERC725Y storage of the linked {target}. */ error NotAllowedERC725YDataKey(address from, bytes32 disallowedKey); /** - * @dev reverts when `dataKey` is a bytes32 that does not adhere to any of the - * permission data keys specified by the LSP6 standard + * @dev Reverts when `dataKey` is a bytes32 value that does not adhere to any of the + * permission data keys defined by the LSP6 standard * - * @param dataKey the dataKey that does not match with any of the standard LSP6 permission data keys + * @param dataKey The dataKey that does not match any of the standard LSP6 permission data keys. */ error NotRecognisedPermissionKey(bytes32 dataKey); /** - * @dev reverts when the address provided as a target (= account) linked to this KeyManager is invalid - * e.g. address(0) + * @dev Reverts when the address provided to set as the {target} linked to this KeyManager is invalid (_e.g. `address(0)`_). */ error InvalidLSP6Target(); /** - * @dev reverts when the `signer` address retrieved from the `signature` has an invalid nonce: `invalidNonce`. - * @param signer the address of the signer - * @param invalidNonce the nonce retrieved for the `signer` address - * @param signature the signature used to retrieve the `signer` address + * @dev Reverts when the `signer` address retrieved from the `signature` has an invalid nonce: `invalidNonce`. + * + * @param signer The address of the signer + * @param invalidNonce The nonce retrieved for the `signer` address + * @param signature The signature used to retrieve the `signer` address */ error InvalidRelayNonce(address signer, uint256 invalidNonce, bytes signature); /** - * @dev reverts when trying to run an invalid function on the linked target account via the Key Manager. - * @param invalidFunction the bytes4 selector of the invalid function + * @dev Reverts when trying to call a function on the linked {target}, that is not any of the following: + * - `setData(bytes32,bytes)` (ERC725Y) + * - `setDataBatch(bytes32[],bytes[])` (ERC725Y) + * - `execute(uint256,address,uint256,bytes)` (ERC725X) + * - `transferOwnership(address)` + * - `acceptOwnership()` (LSP14) + * + * @param invalidFunction The `bytes4` selector of the function selector that was attempted + * to be called on the linked {target} but not recognised. */ error InvalidERC725Function(bytes4 invalidFunction); /** - * @dev reverts when `allowedCallsValue` is not properly encoded as a bytes28[CompactBytesArray] - * (CompactBytesArray of bytes28 entries). See LSP2 value type `CompactBytesArray` for details. - * @param allowedCallsValue the list of allowedCalls + * @dev Reverts when `allowedCallsValue` is not properly encoded as a `(bytes4,address,bytes4,bytes4)[CompactBytesArray]` + * (CompactBytesArray made of tuples that are 32 bytes long each). See LSP2 value type `CompactBytesArray` for more infos. + * + * @param allowedCallsValue The list of allowedCalls that are not encoded correctly as a `(bytes4,address,bytes4,bytes4)[CompactBytesArray]`. */ error InvalidEncodedAllowedCalls(bytes allowedCallsValue); /** - * @dev reverts when trying to set a value that is not 20 bytes long under AddressPermissions[index] - * @param dataKey the AddressPermissions[index] data key - * @param invalidValue the invalid value that was attempted to be set under AddressPermissions[index] + * @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, @@ -77,56 +90,69 @@ error AddressPermissionArrayIndexValueNotAnAddress( ); /** - * @dev reverts if there are no AllowedERC725YDataKeys set for the caller - * @param from the address that has no AllowedERC725YDataKeys + * @dev Reverts when the `from` address has no AllowedERC725YDataKeys set and cannot set + * any ERC725Y data key on the ERC725Y storage of the linked {target}. + * + * @param from The address that has no `AllowedERC725YDataKeys` set. */ error NoERC725YDataKeysAllowed(address from); /** - * @dev reverts if there are no allowed calls set for `from` - * @param from the address that has no AllowedCalls + * @dev Reverts when the `from` address has no `AllowedCalls` set and cannot interact with any address + * using the linked {target}. + * + * @param from The address that has no AllowedCalls. */ error NoCallsAllowed(address from); /** - * @dev reverts when `value` is not encoded properly using the CompactBytesArray - * @param value the value to check for an CompactBytesArray - * @param context a brief description of where the error occured + * @dev Reverts when `value` is not encoded properly as a `bytes32[CompactBytesArray]`. The `context` string provides context + * on when this error occured (_e.g: when fetching the `AllowedERC725YDataKeys` to verify the permissions of a controller, + * or when validating the `AllowedERC725YDataKeys` when setting them for a controller). + * + * @param value The value that is not a valid `bytes32[CompactBytesArray]` + * @param context A brief description of where the error occured. */ error InvalidEncodedAllowedERC725YDataKeys(bytes value, string context); /** - * @dev a `from` address is not allowed to have 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff - * in its list of AddressPermissions:AllowedCalls:
, as this allows any STANDARD:ADDRESS:FUNCTION. + * @dev Reverts when verifying the permissions of a `from` address for its allowed calls, and has a "any whitelisted call" allowed call set. + * A `from` address is not allowed to have 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff + * in its list of `AddressPermissions:AllowedCalls:
`, as this allows any STANDARD:ADDRESS:FUNCTION. * This is equivalent to granting the SUPER permission and should never be valid. * - * @param from the address that has any allowed calls whitelisted. + * @param from The controller address that has any allowed calls whitelisted set. */ error InvalidWhitelistedCall(address from); /** - * @dev reverts when providing array parameters of different sizes to `executeRelayCall(bytes[],uint256[],bytes[])` + * @dev Reverts when providing array parameters of different sizes to `executeRelayCall(bytes[],uint256[],bytes[])` */ error BatchExecuteRelayCallParamsLengthMismatch(); /** - * @dev there should be the same number of elements for each array parameters - * in the following batch functions: - * - execute(uint256[],bytes[]) - * - executeRelayCall(bytes[],uint256[],uint256[],bytes[]) + * @dev Reverts when the array parameters `uint256[] value` and `bytes[] payload` have different sizes. + * There should be the same number of elements for each array parameters. */ error BatchExecuteParamsLengthMismatch(); /** - * @dev the `msg.value` sent is not enough to cover the sum of all the values being - * forwarded on each payloads (`values[]` parameter) in the following batch functions: - * - execute(uint256[],bytes[]) - * - executeRelayCall(bytes[],uint256[],uint256[],bytes[]) + * @dev This error occurs when there was not enough funds sent to the batch functions `execute(uint256[],bytes[])` or + * `executeRelayCall(bytes[],uint256[],uint256[],bytes[])` to cover the sum of all the values forwarded on + * each payloads (`values[]` parameter from the batch functions above). + * + * This mean that `msg.value` is less than the sum of all the values being forwarded on each payloads (`values[]` parameters). + * + * @param totalValues The sum of all the values forwarded on each payloads (`values[]` parameter from the batch functions above). + * @param msgValue The amount of native tokens sent to the batch functions `execute(uint256[],bytes[])` or `executeRelayCall(bytes[],uint256[],uint256[],bytes[])`. */ error LSP6BatchInsufficientValueSent(uint256 totalValues, uint256 msgValue); /** - * @dev reverts to avoid the KeyManager to holds some remaining funds sent + * @dev This error occurs when there was too much funds sent to the batch functions `execute(uint256[],bytes[])` or + * `executeRelayCall(bytes[],uint256[],uint256[],bytes[])` to cover the sum of all the values forwarded on + * + * Reverts to avoid the KeyManager to holds some remaining funds sent * to the following batch functions: * - execute(uint256[],bytes[]) * - executeRelayCall(bytes[],uint256[],uint256[],bytes[]) @@ -137,32 +163,35 @@ error LSP6BatchInsufficientValueSent(uint256 totalValues, uint256 msgValue); error LSP6BatchExcessiveValueSent(uint256 totalValues, uint256 msgValue); /** - * @dev ERC725X operation type 4 (DELEGATECALL) is disallowed by default + * @dev Reverts when trying to do a `delegatecall` via the ERC725X.execute(uint256,address,uint256,bytes) (operation type 4) + * function of the linked {target}. + * `DELEGATECALL` is disallowed by default on the LSP6KeyManager. */ error DelegateCallDisallowedViaKeyManager(); /** - * @dev reverts when the payload is invalid. + * @dev Reverst when the payload is invalid. */ error InvalidPayload(bytes payload); /** - * @dev reverts when sending value to the `setData(..)` functions + * @dev Reverts when trying to call to the `setData(byte32,bytes)` or `setData(bytes32[],bytes[]) functions + * on the linked {target} while sending value. */ error CannotSendValueToSetData(); /** - * @dev reverts when calling the KeyManager through execute(..) + * @dev Reverts when calling the KeyManager through `execute(uint256,address,uint256,bytes)`. */ error CallingKeyManagerNotAllowed(); /** - * @dev reverts when relay call start timestamp is bigger than the current timestamp + * @dev Reverts when the start timestamp provided to {executeRelayCall} function is bigger than the current timestamp. */ error RelayCallBeforeStartTime(); /** - * @dev reverts when the period to execute the relay call has expired + * @dev Reverts when the period to execute the relay call has expired. */ error RelayCallExpired(); diff --git a/contracts/LSP6KeyManager/LSP6KeyManager.sol b/contracts/LSP6KeyManager/LSP6KeyManager.sol index 477a35d7e..5f046310f 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManager.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManager.sol @@ -6,14 +6,17 @@ import {LSP6KeyManagerCore} from "./LSP6KeyManagerCore.sol"; import {InvalidLSP6Target} from "./LSP6Errors.sol"; /** - * @title Implementation of a contract acting as a controller of an ERC725 Account, using permissions stored in the ERC725Y storage + * @title Implementation of a contract acting as a controller of an ERC725 Account, using permissions stored in the ERC725Y storage. * @author Fabian Vogelsteller , Jean Cavallera (CJ42), Yamen Merhi (YamenMerhi) - * @dev all the permissions can be set on the ERC725 Account using `setData(...)` with the keys constants below + * @dev All the permissions can be set on the ERC725 Account using `setData(bytes32,bytes)` or `setData(bytes32[],bytes[])`. */ contract LSP6KeyManager is LSP6KeyManagerCore { /** - * @notice Initiate the account with the address of the ERC725Account contract and sets LSP6KeyManager InterfaceId - * @param target_ The address of the ER725Account to control + * @notice Deploying a LSP6KeyManager linked to contract at address `target_`. + * @dev Deploy a Key Manager and set the `target_` address in the contract storage, + * making this Key Manager linked to this `target_` contract. + * + * @param target_ The address of the contract to control and forward calldata payloads to. */ constructor(address target_) { if (target_ == address(0)) revert InvalidLSP6Target(); diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index 4ac83fafa..0d92496ea 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -61,6 +61,10 @@ import "../LSP20CallVerification/LSP20Constants.sol"; * @author Fabian Vogelsteller , Jean Cavallera (CJ42), Yamen Merhi (YamenMerhi) * @dev This contract acts as a controller for an ERC725 Account. * Permissions for controllers are stored in the ERC725Y storage of the ERC725 Account and can be updated using `setData(...)`. + * + * @custom:danger Because of its potential malicious impact on the linked contract, the current implementation of the Key Manager + * disallows the operation type **[DELEGATECALL](../universal-profile/lsp6-key-manager.md#permissions-value)** operation via the + * `execute(...)` function of the linked contract. */ abstract contract LSP6KeyManagerCore is ERC165, @@ -82,12 +86,15 @@ abstract contract LSP6KeyManagerCore is mapping(address => mapping(uint256 => uint256)) internal _nonceStore; - function target() public view virtual returns (address) { + /** + * @inheritdoc ILSP6KeyManager + */ + function target() public view returns (address) { return _target; } /** - * @dev See {IERC165-supportsInterface}. + * @inheritdoc ERC165 */ function supportsInterface( bytes4 interfaceId @@ -101,6 +108,9 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc ILSP6KeyManager + * + * @custom:info A signer can choose its channel number arbitrarily. Channel ID = 0 can be used for sequential nonces (transactions + * that are order dependant), any other channel ID for out-of-order execution (= execution in parallel). */ function getNonce( address from, @@ -112,6 +122,11 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc IERC1271 + * + * @dev Checks if a signature was signed by a controller that has the permission `SIGN`. + * If the signer is a controller with the permission `SIGN`, it will return the ERC1271 magic value. + * + * @return magicValue `0x1626ba7e` on success, or `0xffffffff` on failure. */ function isValidSignature( bytes32 dataHash, @@ -137,6 +152,8 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc ILSP6KeyManager + * + * @custom:events VerifiedCall event when the permissions related to `payload` have been verified successfully. */ function execute( bytes calldata payload @@ -146,6 +163,8 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc ILSP6KeyManager + * + * @custom:events VerifiedCall event for each permissions related to each `payload` that have been verified successfully. */ function executeBatch( uint256[] calldata values, @@ -179,6 +198,13 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc ILSP6KeyManager + * + * @custom:events {VerifiedCall} event when the permissions related to `payload` have been verified successfully. + * + * @custom:hint If you are looking to learn how to sign and execute relay transactions via the Key Manager, + * see our Javascript step by step guide [_"Execute Relay Transactions"_](../../guides/key-manager/execute-relay-transactions.md). + * See the LSP6 Standard page for more details on how to + * [generate a valid signature for Execute Relay Call](../universal-profile/lsp6-key-manager.md#how-to-sign-relay-transactions). */ function executeRelayCall( bytes memory signature, @@ -198,6 +224,11 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc ILSP6KeyManager + * + * @custom:requirements + * - the length of `signatures`, `nonces`, `validityTimestamps`, `values` and `payloads` MUST be the same. + * - the value sent to this function (`msg.value`) MUST be equal to the sum of all `values` in the batch. + * There should not be any excess value sent to this function. */ function executeRelayCallBatch( bytes[] memory signatures, diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol b/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol index d89cc16bc..701139217 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol @@ -18,8 +18,11 @@ contract LSP6KeyManagerInit is LSP6KeyManagerInitAbstract { } /** - * @notice Initiate the account with the address of the ERC725Account contract and sets LSP6KeyManager InterfaceId - * @param target_ The address of the ER725Account to control + * @notice Deploying a LSP6KeyManager linked to contract at address `target_`. + * @dev Deploy a Key Manager and set the `target_` address in the contract storage, + * making this Key Manager linked to this `target_` contract. + * + * @param target_ The address of the contract to control and forward calldata payloads to. */ function initialize(address target_) external virtual initializer { LSP6KeyManagerInitAbstract._initialize(target_); diff --git a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md new file mode 100644 index 000000000..14bec456c --- /dev/null +++ b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md @@ -0,0 +1,986 @@ +# LSP6KeyManager + +:::info Solidity contract + +[`LSP6KeyManager.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) + +::: + +> Implementation of a contract acting as a controller of an ERC725 Account, using permissions stored in the ERC725Y storage. + +All the permissions can be set on the ERC725 Account using `setData(bytes32,bytes)` or `setData(bytes32[],bytes[])`. + +## Methods + +### constructor + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#constructor) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) + +::: + +```solidity +constructor(address target_); +``` + +_Deploying a LSP6KeyManager linked to contract at address `target_`._ + +Deploy a Key Manager and set the `target_` address in the contract storage, making this Key Manager linked to this `target_` contract. + +#### Parameters + +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------ | +| `target_` | `address` | The address of the contract to control and forward calldata payloads to. | + +### execute + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#execute) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `execute(bytes)` +- Function selector: `0x09c5eabe` + +::: + +```solidity +function execute(bytes payload) external payable returns (bytes); +``` + +_execute the following payload on the linked contract: `payload`_ + +execute a `payload` on the linked [`target`](#target) after having verified the permissions associated with the function being run. The `payload` MUST be a valid abi-encoded function call of one of the functions present in the linked [`target`](#target), otherwise the call will fail. The linked [`target`](#target) will return some data on successful execution, or revert on failure. + +
+ +**Emitted events:** + +- VerifiedCall event when the permissions related to `payload` have been verified successfully. + +
+ +#### Parameters + +| Name | Type | Description | +| --------- | :-----: | ---------------------------------------------------------------- | +| `payload` | `bytes` | the abi-encoded function call to execute on the linked {target}. | + +#### Returns + +| Name | Type | Description | +| ---- | :-----: | ---------------------------------------------------------------------------- | +| `0` | `bytes` | the abi-decoded data returned by the function called on the linked {target}. | + +### executeBatch + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#executebatch) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `executeBatch(uint256[],bytes[])` +- Function selector: `0xbf0176ff` + +::: + +```solidity +function executeBatch( + uint256[] values, + bytes[] payloads +) external payable returns (bytes[]); +``` + +Same as [`execute`](#execute) but execute a batch of payloads (abi-encoded function calls) in a single transaction. + +
+ +**Emitted events:** + +- VerifiedCall event for each permissions related to each `payload` that have been verified successfully. + +
+ +#### Parameters + +| Name | Type | Description | +| ---------- | :---------: | -------------------------------------------------------------------------------------- | +| `values` | `uint256[]` | An array of amount of native tokens to be transferred for each `payload`. | +| `payloads` | `bytes[]` | An array of abi-encoded function calls to execute successively on the linked {target}. | + +#### Returns + +| Name | Type | Description | +| ---- | :-------: | ----------------------------------------------------------------------------------------------- | +| `0` | `bytes[]` | An array of abi-decoded of return data returned by the functions called on the linked {target}. | + +### executeRelayCall + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#executerelaycall) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `executeRelayCall(bytes,uint256,uint256,bytes)` +- Function selector: `0x4c8a4e74` + +::: + +:::tip Hint + +If you are looking to learn how to sign and execute relay transactions via the Key Manager, see our Javascript step by step guide [_"Execute Relay Transactions"_](../../guides/key-manager/execute-relay-transactions.md). See the LSP6 Standard page for more details on how to [generate a valid signature for Execute Relay Call](../universal-profile/lsp6-key-manager.md#how-to-sign-relay-transactions). + +::: + +```solidity +function executeRelayCall( + bytes signature, + uint256 nonce, + uint256 validityTimestamps, + bytes payload +) external payable returns (bytes); +``` + +Allows any address (executor) to execute a payload (= abi-encoded function call) in the linked [`target`](#target) given they have a signed message from a controller with some permissions. + +
+ +**Emitted events:** + +- [`VerifiedCall`](#verifiedcall) event when the permissions related to `payload` have been verified successfully. + +
+ +#### Parameters + +| Name | Type | Description | +| -------------------- | :-------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `signature` | `bytes` | a 65 bytes long signature for a meta transaction according to LSP6. | +| `nonce` | `uint256` | the nonce of the address that signed the calldata (in a specific `_channel`), obtained via {getNonce}. Used to prevent replay attack. | +| `validityTimestamps` | `uint256` | \* Two `uint128` timestamps concatenated together that describes when the relay transaction is valid "from" (left `uint128`) and "until" as a deadline (right `uint128`). | +| `payload` | `bytes` | the abi-encoded function call to execute on the linked {target}. | + +#### Returns + +| Name | Type | Description | +| ---- | :-----: | ---------------------------------------------------------------------- | +| `0` | `bytes` | the data being returned by the function called on the linked {target}. | + +### executeRelayCallBatch + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#executerelaycallbatch) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `executeRelayCallBatch(bytes[],uint256[],uint256[],uint256[],bytes[])` +- Function selector: `0xa20856a5` + +::: + +```solidity +function executeRelayCallBatch( + bytes[] signatures, + uint256[] nonces, + uint256[] validityTimestamps, + uint256[] values, + bytes[] payloads +) external payable returns (bytes[]); +``` + +Same as [`executeRelayCall`](#executerelaycall) but execute a batch of signed calldata payloads (abi-encoded function calls) in a single transaction. The signed transactions can be from multiple controllers, not necessarely the same controller signer, as long as each of these controllers that signed have the right permissions related to the calldata `payload` they signed. + +
+ +**Requirements:** + +- the length of `signatures`, `nonces`, `validityTimestamps`, `values` and `payloads` MUST be the same. +- the value sent to this function (`msg.value`) MUST be equal to the sum of all `values` in the batch. There should not be any excess value sent to this function. + +
+ +#### Parameters + +| Name | Type | Description | +| -------------------- | :---------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `signatures` | `bytes[]` | An array of 65 bytes long signatures for meta transactions according to LSP6. | +| `nonces` | `uint256[]` | An array of nonces of the addresses that signed the calldata payloads (in specific channels). Obtained via {getNonce}. Used to prevent replay attack. | +| `validityTimestamps` | `uint256[]` | An array of two `uint128` concatenated timestamps that describe when the relay transaction is valid "from" (left `uint128`) and "until" (right `uint128`). | +| `values` | `uint256[]` | An array of amount of native tokens to be transferred for each calldata `payload`. | +| `payloads` | `bytes[]` | An array of abi-encoded function calls to execute successively on the linked {target}. | + +#### Returns + +| Name | Type | Description | +| ---- | :-------: | -------------------------------------------------------------------------------------------- | +| `0` | `bytes[]` | An array of abi-decoded return data returned by the functions called on the linked {target}. | + +### getNonce + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#getnonce) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `getNonce(address,uint128)` +- Function selector: `0xb44581d9` + +::: + +:::info + +A signer can choose its channel number arbitrarily. Channel ID = 0 can be used for sequential nonces (transactions that are order dependant), any other channel ID for out-of-order execution (= execution in parallel). + +::: + +```solidity +function getNonce( + address from, + uint128 channelId +) external view returns (uint256); +``` + +_Get latest nonce for `from` in channel ID: `channelId`._ + +Get the nonce for a specific controller `from` address that can be used for signing relay transaction. + +#### Parameters + +| Name | Type | Description | +| ----------- | :-------: | -------------------------------------------------------------------------- | +| `from` | `address` | the address of the signer of the transaction. | +| `channelId` | `uint128` | the channel id that the signer wants to use for executing the transaction. | + +#### Returns + +| Name | Type | Description | +| ---- | :-------: | ------------------------------------------- | +| `0` | `uint256` | the current nonce on a specific `channelId` | + +### isValidSignature + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#isvalidsignature) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `isValidSignature(bytes32,bytes)` +- Function selector: `0x1626ba7e` + +::: + +```solidity +function isValidSignature( + bytes32 dataHash, + bytes signature +) external view returns (bytes4 magicValue); +``` + +Checks if a signature was signed by a controller that has the permission `SIGN`. If the signer is a controller with the permission `SIGN`, it will return the ERC1271 magic value. + +#### Parameters + +| Name | Type | Description | +| ----------- | :-------: | ------------------------------------------- | +| `dataHash` | `bytes32` | - | +| `signature` | `bytes` | Signature byte array associated with \_data | + +#### Returns + +| Name | Type | Description | +| ------------ | :------: | ---------------------------------------------------- | +| `magicValue` | `bytes4` | `0x1626ba7e` on success, or `0xffffffff` on failure. | + +### lsp20VerifyCall + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#lsp20verifycall) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `lsp20VerifyCall(address,uint256,bytes)` +- Function selector: `0x9bf04b11` + +::: + +```solidity +function lsp20VerifyCall( + address caller, + uint256 msgValue, + bytes data +) external nonpayable returns (bytes4); +``` + +#### Parameters + +| Name | Type | Description | +| ---------- | :-------: | ----------------------------------------------------- | +| `caller` | `address` | The address who called the function on the msg.sender | +| `msgValue` | `uint256` | - | +| `data` | `bytes` | - | + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | `bytes4` | MUST return the first 3 bytes of `lsp20VerifyCall(address,uint256,bytes)` function selector if the call to the function is allowed, concatened with a byte that determines if the lsp20VerifyCallResult function should be called after the original function call. The byte that invoke the lsp20VerifyCallResult function is strictly `0x01`. | + +### lsp20VerifyCallResult + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#,)) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `,)` +- Function selector: `0x9f47dbd3` + +::: + +```solidity +function lsp20VerifyCallResult( + bytes32, + bytes +) external nonpayable returns (bytes4); +``` + +#### Parameters + +| Name | Type | Description | +| ---- | :-------: | ----------- | +| `_0` | `bytes32` | - | +| `_1` | `bytes` | - | + +#### Returns + +| Name | Type | Description | +| ---- | :------: | ---------------------------------------------------------------------------------------------- | +| `0` | `bytes4` | MUST return the lsp20VerifyCallResult function selector if the call to the function is allowed | + +### supportsInterface + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#supportsinterface) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `supportsInterface(bytes4)` +- Function selector: `0x01ffc9a7` + +::: + +```solidity +function supportsInterface(bytes4 interfaceId) external view returns (bool); +``` + +See [`IERC165-supportsInterface`](#ierc165-supportsinterface). + +#### Parameters + +| Name | Type | Description | +| ------------- | :------: | ----------- | +| `interfaceId` | `bytes4` | - | + +#### Returns + +| Name | Type | Description | +| ---- | :----: | ----------- | +| `0` | `bool` | - | + +### target + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#target) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Function signature: `target()` +- Function selector: `0xd4b83992` + +::: + +```solidity +function target() external view returns (address); +``` + +Get the address of the contract linked to this Key Manager. + +#### Returns + +| Name | Type | Description | +| ---- | :-------: | --------------------------------- | +| `0` | `address` | the address of the linked account | + +--- + +## Events + +### VerifiedCall + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#verifiedcall) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Event signature: `VerifiedCall(address,uint256,bytes4)` +- Event hash: `0xa54458b75709e42f79700ffb6cfc57c7e224d8a77a52c457ee7ecb8e22636280` + +::: + +```solidity +event VerifiedCall(address indexed signer, uint256 indexed value, bytes4 indexed selector); +``` + +Emitted when the LSP6KeyManager contract verified the permissions of the `signer` successfully. + +#### Parameters + +| Name | Type | Description | +| ------------------------ | :-------: | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `signer` **`indexed`** | `address` | the address of the controller that executed the calldata payload (either directly via {execute} or via meta transaction using {executeRelayCall}). | +| `value` **`indexed`** | `uint256` | the amount of native token to be transferred in the calldata payload. | +| `selector` **`indexed`** | `bytes4` | the bytes4 function of the function that was executed on the linked {target} | + +--- + +## Errors + +### AddressPermissionArrayIndexValueNotAnAddress + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#addresspermissionarrayindexvaluenotanaddress) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `AddressPermissionArrayIndexValueNotAnAddress(bytes32,bytes)` +- Error hash: `0x8f4afa38` + +::: + +```solidity +error AddressPermissionArrayIndexValueNotAnAddress( + bytes32 dataKey, + bytes invalidValue +); +``` + +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 Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#batchexecuteparamslengthmismatch) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `BatchExecuteParamsLengthMismatch()` +- Error hash: `0x55a187db` + +::: + +```solidity +error BatchExecuteParamsLengthMismatch(); +``` + +Reverts when the array parameters `uint256[] value` and `bytes[] payload` have different sizes. There should be the same number of elements for each array parameters. + +### BatchExecuteRelayCallParamsLengthMismatch + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#batchexecuterelaycallparamslengthmismatch) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `BatchExecuteRelayCallParamsLengthMismatch()` +- Error hash: `0xb4d50d21` + +::: + +```solidity +error BatchExecuteRelayCallParamsLengthMismatch(); +``` + +Reverts when providing array parameters of different sizes to `executeRelayCall(bytes[],uint256[],bytes[])` + +### CallingKeyManagerNotAllowed + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#callingkeymanagernotallowed) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `CallingKeyManagerNotAllowed()` +- Error hash: `0xa431b236` + +::: + +```solidity +error CallingKeyManagerNotAllowed(); +``` + +Reverts when calling the KeyManager through `execute(uint256,address,uint256,bytes)`. + +### CannotSendValueToSetData + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#cannotsendvaluetosetdata) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `CannotSendValueToSetData()` +- Error hash: `0x59a529fc` + +::: + +```solidity +error CannotSendValueToSetData(); +``` + +Reverts when trying to call to the `setData(byte32,bytes)` or `setData(bytes32[],bytes[]) functions on the linked [`target`](#target) while sending value. + +### DelegateCallDisallowedViaKeyManager + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#delegatecalldisallowedviakeymanager) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `DelegateCallDisallowedViaKeyManager()` +- Error hash: `0x80d6ebae` + +::: + +```solidity +error DelegateCallDisallowedViaKeyManager(); +``` + +Reverts when trying to do a `delegatecall` via the ERC725X.execute(uint256,address,uint256,bytes) (operation type 4) function of the linked [`target`](#target). `DELEGATECALL` is disallowed by default on the LSP6KeyManager. + +### ERC725Y_DataKeysValuesLengthMismatch + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#erc725y_datakeysvalueslengthmismatch) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `ERC725Y_DataKeysValuesLengthMismatch()` +- Error hash: `0x3bcc8979` + +::: + +```solidity +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. + +### InvalidERC725Function + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#invaliderc725function) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `InvalidERC725Function(bytes4)` +- Error hash: `0x2ba8851c` + +::: + +```solidity +error InvalidERC725Function(bytes4 invalidFunction); +``` + +Reverts when trying to call a function on the linked [`target`](#target), that is not any of the following: + +- `setData(bytes32,bytes)` (ERC725Y) + +- `setDataBatch(bytes32[],bytes[])` (ERC725Y) + +- `execute(uint256,address,uint256,bytes)` (ERC725X) + +- `transferOwnership(address)` + +- `acceptOwnership()` (LSP14) + +#### Parameters + +| Name | Type | Description | +| ----------------- | :------: | ------------------------------------------------------------------------------------------------------------------------- | +| `invalidFunction` | `bytes4` | The `bytes4` selector of the function selector that was attempted to be called on the linked {target} but not recognised. | + +### InvalidEncodedAllowedCalls + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#invalidencodedallowedcalls) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `InvalidEncodedAllowedCalls(bytes)` +- Error hash: `0x187e77ab` + +::: + +```solidity +error InvalidEncodedAllowedCalls(bytes allowedCallsValue); +``` + +Reverts when `allowedCallsValue` is not properly encoded as a `(bytes4,address,bytes4,bytes4)[CompactBytesArray]` (CompactBytesArray made of tuples that are 32 bytes long each). See LSP2 value type `CompactBytesArray` for more infos. + +#### Parameters + +| Name | Type | Description | +| ------------------- | :-----: | ----------------------------------------------------------------------------------------------------------------- | +| `allowedCallsValue` | `bytes` | The list of allowedCalls that are not encoded correctly as a `(bytes4,address,bytes4,bytes4)[CompactBytesArray]`. | + +### InvalidEncodedAllowedERC725YDataKeys + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#invalidencodedallowederc725ydatakeys) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `InvalidEncodedAllowedERC725YDataKeys(bytes,string)` +- Error hash: `0xae6cbd37` + +::: + +```solidity +error InvalidEncodedAllowedERC725YDataKeys(bytes value, string context); +``` + +Reverts when `value` is not encoded properly as a `bytes32[CompactBytesArray]`. The `context` string provides context on when this error occured (\_e.g: when fetching the `AllowedERC725YDataKeys` to verify the permissions of a controller, or when validating the `AllowedERC725YDataKeys` when setting them for a controller). + +#### Parameters + +| Name | Type | Description | +| --------- | :------: | ---------------------------------------------------------- | +| `value` | `bytes` | The value that is not a valid `bytes32[CompactBytesArray]` | +| `context` | `string` | A brief description of where the error occured. | + +### InvalidLSP6Target + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#invalidlsp6target) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `InvalidLSP6Target()` +- Error hash: `0xfc854579` + +::: + +```solidity +error InvalidLSP6Target(); +``` + +Reverts when the address provided to set as the [`target`](#target) linked to this KeyManager is invalid (_e.g. `address(0)`_). + +### InvalidPayload + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#invalidpayload) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `InvalidPayload(bytes)` +- Error hash: `0x3621bbcc` + +::: + +```solidity +error InvalidPayload(bytes payload); +``` + +Reverst when the payload is invalid. + +#### Parameters + +| Name | Type | Description | +| --------- | :-----: | ----------- | +| `payload` | `bytes` | - | + +### InvalidRelayNonce + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#invalidrelaynonce) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `InvalidRelayNonce(address,uint256,bytes)` +- Error hash: `0xc9bd9eb9` + +::: + +```solidity +error InvalidRelayNonce(address signer, uint256 invalidNonce, bytes signature); +``` + +Reverts when the `signer` address retrieved from the `signature` has an invalid nonce: `invalidNonce`. + +#### Parameters + +| Name | Type | Description | +| -------------- | :-------: | --------------------------------------------------- | +| `signer` | `address` | The address of the signer | +| `invalidNonce` | `uint256` | The nonce retrieved for the `signer` address | +| `signature` | `bytes` | The signature used to retrieve the `signer` address | + +### InvalidWhitelistedCall + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#invalidwhitelistedcall) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `InvalidWhitelistedCall(address)` +- Error hash: `0x6fd203c5` + +::: + +```solidity +error InvalidWhitelistedCall(address from); +``` + +Reverts when verifying the permissions of a `from` address for its allowed calls, and has a "any whitelisted call" allowed call set. A `from` address is not allowed to have 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff in its list of `AddressPermissions:AllowedCalls:
`, as this allows any STANDARD:ADDRESS:FUNCTION. This is equivalent to granting the SUPER permission and should never be valid. + +#### Parameters + +| Name | Type | Description | +| ------ | :-------: | ------------------------------------------------------------------ | +| `from` | `address` | The controller address that has any allowed calls whitelisted set. | + +### LSP6BatchExcessiveValueSent + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#lsp6batchexcessivevaluesent) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `LSP6BatchExcessiveValueSent(uint256,uint256)` +- Error hash: `0xa51868b6` + +::: + +```solidity +error LSP6BatchExcessiveValueSent(uint256 totalValues, uint256 msgValue); +``` + +This error occurs when there was too much funds sent to the batch functions `execute(uint256[],bytes[])` or `executeRelayCall(bytes[],uint256[],uint256[],bytes[])` to cover the sum of all the values forwarded on Reverts to avoid the KeyManager to holds some remaining funds sent to the following batch functions: + +- execute(uint256[],bytes[]) + +- executeRelayCall(bytes[],uint256[],uint256[],bytes[]) This error occurs when `msg.value` is more than the sum of all the values being forwarded on each payloads (`values[]` parameter from the batch functions above). + +#### Parameters + +| Name | Type | Description | +| ------------- | :-------: | ----------- | +| `totalValues` | `uint256` | - | +| `msgValue` | `uint256` | - | + +### LSP6BatchInsufficientValueSent + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#lsp6batchinsufficientvaluesent) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `LSP6BatchInsufficientValueSent(uint256,uint256)` +- Error hash: `0x30a324ac` + +::: + +```solidity +error LSP6BatchInsufficientValueSent(uint256 totalValues, uint256 msgValue); +``` + +This error occurs when there was not enough funds sent to the batch functions `execute(uint256[],bytes[])` or `executeRelayCall(bytes[],uint256[],uint256[],bytes[])` to cover the sum of all the values forwarded on each payloads (`values[]` parameter from the batch functions above). This mean that `msg.value` is less than the sum of all the values being forwarded on each payloads (`values[]` parameters). + +#### Parameters + +| Name | Type | Description | +| ------------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `totalValues` | `uint256` | The sum of all the values forwarded on each payloads (`values[]` parameter from the batch functions above). | +| `msgValue` | `uint256` | The amount of native tokens sent to the batch functions `execute(uint256[],bytes[])` or `executeRelayCall(bytes[],uint256[],uint256[],bytes[])`. | + +### NoCallsAllowed + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#nocallsallowed) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `NoCallsAllowed(address)` +- Error hash: `0x6cb60587` + +::: + +```solidity +error NoCallsAllowed(address from); +``` + +Reverts when the `from` address has no `AllowedCalls` set and cannot interact with any address using the linked [`target`](#target). + +#### Parameters + +| Name | Type | Description | +| ------ | :-------: | ------------------------------------- | +| `from` | `address` | The address that has no AllowedCalls. | + +### NoERC725YDataKeysAllowed + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#noerc725ydatakeysallowed) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `NoERC725YDataKeysAllowed(address)` +- Error hash: `0xed7fa509` + +::: + +```solidity +error NoERC725YDataKeysAllowed(address from); +``` + +Reverts when the `from` address has no AllowedERC725YDataKeys set and cannot set any ERC725Y data key on the ERC725Y storage of the linked [`target`](#target). + +#### Parameters + +| Name | Type | Description | +| ------ | :-------: | ----------------------------------------------------- | +| `from` | `address` | The address that has no `AllowedERC725YDataKeys` set. | + +### NoPermissionsSet + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#nopermissionsset) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `NoPermissionsSet(address)` +- Error hash: `0xf292052a` + +::: + +```solidity +error NoPermissionsSet(address from); +``` + +Reverts when address `from` does not have any permissions set on the account linked to this Key Manager + +#### Parameters + +| Name | Type | Description | +| ------ | :-------: | ------------------------------------------ | +| `from` | `address` | the address that does not have permissions | + +### NotAllowedCall + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#notallowedcall) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `NotAllowedCall(address,address,bytes4)` +- Error hash: `0x45147bce` + +::: + +```solidity +error NotAllowedCall(address from, address to, bytes4 selector); +``` + +Reverts when `from` is not authorised to call the `execute(uint256,address,uint256,bytes)` function because of a not allowed callType, address, standard or function. + +#### Parameters + +| Name | Type | Description | +| ---------- | :-------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `from` | `address` | address The controller that tried to call the `execute(uint256,address,uint256,bytes)` function. | +| `to` | `address` | The address of an EOA or contract that `from` tried to call using the linked {target} | +| `selector` | `bytes4` | If `to` is a contract, the bytes4 selector of the function that `from` is trying to call. If no function is called (e.g: a native token transfer), selector = 0x00000000 | + +### NotAllowedERC725YDataKey + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#notallowederc725ydatakey) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `NotAllowedERC725YDataKey(address,bytes32)` +- Error hash: `0x557ae079` + +::: + +```solidity +error NotAllowedERC725YDataKey(address from, bytes32 disallowedKey); +``` + +Reverts when address `from` is not authorised to set the key `disallowedKey` on the linked [`target`](#target). + +#### Parameters + +| Name | Type | Description | +| --------------- | :-------: | ------------------------------------------------------------------------------------------------------ | +| `from` | `address` | address The controller that tried to `setData` on the linked {target}. | +| `disallowedKey` | `bytes32` | A bytes32 data key that `from` is not authorised to set on the ERC725Y storage of the linked {target}. | + +### NotAuthorised + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#notauthorised) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `NotAuthorised(address,string)` +- Error hash: `0x3bdad6e6` + +::: + +```solidity +error NotAuthorised(address from, string permission); +``` + +Reverts when address `from` is not authorised and does not have `permission` on the linked [`target`](#target) + +#### Parameters + +| Name | Type | Description | +| ------------ | :-------: | ------------------------------------------------------------------------------ | +| `from` | `address` | address The address that was not authorised. | +| `permission` | `string` | permission The permission required (\_e.g: `SETDATA`, `CALL`, `TRANSFERVALUE`) | + +### NotRecognisedPermissionKey + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#notrecognisedpermissionkey) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `NotRecognisedPermissionKey(bytes32)` +- Error hash: `0x0f7d735b` + +::: + +```solidity +error NotRecognisedPermissionKey(bytes32 dataKey); +``` + +Reverts when `dataKey` is a bytes32 value that does not adhere to any of the permission data keys defined by the LSP6 standard + +#### Parameters + +| Name | Type | Description | +| --------- | :-------: | ------------------------------------------------------------------------------ | +| `dataKey` | `bytes32` | The dataKey that does not match any of the standard LSP6 permission data keys. | + +### RelayCallBeforeStartTime + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#relaycallbeforestarttime) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `RelayCallBeforeStartTime()` +- Error hash: `0x00de4b8a` + +::: + +```solidity +error RelayCallBeforeStartTime(); +``` + +Reverts when the start timestamp provided to [`executeRelayCall`](#executerelaycall) function is bigger than the current timestamp. + +### RelayCallExpired + +:::note Links + +- Specification details in [**LSP-6-KeyManager**](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-6-KeyManager.md#relaycallexpired) +- Solidity implementation in [**LSP6KeyManager**](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/contracts/LSP6KeyManager/LSP6KeyManager.sol) +- Error signature: `RelayCallExpired()` +- Error hash: `0x5c53a98c` + +::: + +```solidity +error RelayCallExpired(); +``` + +Reverts when the period to execute the relay call has expired. diff --git a/dodoc/config.ts b/dodoc/config.ts index 73f8edc24..6630bdeda 100644 --- a/dodoc/config.ts +++ b/dodoc/config.ts @@ -17,7 +17,7 @@ export const dodocConfig = { 'contracts/LSP17ContractExtension/LSP17Extension.sol', 'contracts/LSP20CallVerification/LSP20CallVerification.sol', - // tokens -------------------- + // tokens 'contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol', 'contracts/LSP4DigitalAssetMetadata/LSP4DigitalAssetMetadata.sol', 'contracts/LSP7DigitalAsset/LSP7DigitalAsset.sol', @@ -34,7 +34,7 @@ export const dodocConfig = { 'contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol', 'contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol', - // libraries + // libraries -------------------- 'contracts/LSP0ERC725Account/LSP0Utils.sol', 'contracts/LSP1UniversalReceiver/LSP1Utils.sol', 'contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol', From 083a31d98c350ff5830ad75fa881714d68a1e6dd Mon Sep 17 00:00:00 2001 From: CJ42 Date: Thu, 10 Aug 2023 08:00:50 +0100 Subject: [PATCH 2/7] feat: create LSP25 interface + implementation for multi channel nonces --- .../ILSP25ExecuteRelayCall.sol | 59 ++++++++++++ .../LSP25ExecuteRelayCall/LSP25Constants.sol | 5 + .../LSP25ExecuteRelayCall/LSP25Errors.sol | 21 +++++ .../LSP25MultiChannelNonce.sol | 91 +++++++++++++++++++ contracts/LSP6KeyManager/ILSP6KeyManager.sol | 55 ----------- contracts/LSP6KeyManager/LSP6Constants.sol | 2 - .../LSP6KeyManager/LSP6KeyManagerCore.sol | 57 +----------- 7 files changed, 178 insertions(+), 112 deletions(-) create mode 100644 contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol create mode 100644 contracts/LSP25ExecuteRelayCall/LSP25Constants.sol create mode 100644 contracts/LSP25ExecuteRelayCall/LSP25Errors.sol create mode 100644 contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol diff --git a/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol b/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol new file mode 100644 index 000000000..3ba378889 --- /dev/null +++ b/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: Apache 2.0 +pragma solidity ^0.8.0; + +interface ILSP25ExecuteRelayCall { + /** + * @notice Get latest nonce for `from` in channel ID: `channelId`. + * + * @dev Get the nonce for a specific controller `from` address that can be used for signing relay transaction. + * + * @param from the address of the signer of the transaction. + * @param channelId the channel id that the signer wants to use for executing the transaction. + * + * @return the current nonce on a specific `channelId` + */ + function getNonce( + address from, + uint128 channelId + ) external view returns (uint256); + + /** + * @dev Allows any address (executor) to execute a payload (= abi-encoded function call) in the linked {target} given they have a signed message from + * a controller with some permissions. + * + * @param signature a 65 bytes long signature for a meta transaction according to LSP6. + * @param nonce the nonce of the address that signed the calldata (in a specific `_channel`), obtained via {getNonce}. Used to prevent replay attack. + * @param validityTimestamps * Two `uint128` timestamps concatenated together that describes + * when the relay transaction is valid "from" (left `uint128`) and "until" as a deadline (right `uint128`). + * @param payload the abi-encoded function call to execute on the linked {target}. + * + * @return the data being returned by the function called on the linked {target}. + */ + function executeRelayCall( + bytes calldata signature, + uint256 nonce, + uint256 validityTimestamps, + bytes calldata payload + ) external payable returns (bytes memory); + + /** + * @dev Same as {executeRelayCall} but execute a batch of signed calldata payloads (abi-encoded function calls) in a single transaction. + * The signed transactions can be from multiple controllers, not necessarely the same controller signer, as long as each of these controllers + * that signed have the right permissions related to the calldata `payload` they signed. + * + * @param signatures An array of 65 bytes long signatures for meta transactions according to LSP6. + * @param nonces An array of nonces of the addresses that signed the calldata payloads (in specific channels). Obtained via {getNonce}. Used to prevent replay attack. + * @param validityTimestamps An array of two `uint128` concatenated timestamps that describe when the relay transaction is valid "from" (left `uint128`) and "until" (right `uint128`). + * @param values An array of amount of native tokens to be transferred for each calldata `payload`. + * @param payloads An array of abi-encoded function calls to execute successively on the linked {target}. + * + * @return An array of abi-decoded return data returned by the functions called on the linked {target}. + */ + function executeRelayCallBatch( + bytes[] calldata signatures, + uint256[] calldata nonces, + uint256[] calldata validityTimestamps, + uint256[] calldata values, + bytes[] calldata payloads + ) external payable returns (bytes[] memory); +} diff --git a/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol b/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol new file mode 100644 index 000000000..cc3e7c960 --- /dev/null +++ b/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: CC0-1.0 +pragma solidity ^0.8.4; + +// version number used to validate signed relayed call +uint256 constant LSP25_VERSION = 25; diff --git a/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol b/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol new file mode 100644 index 000000000..01f249cbc --- /dev/null +++ b/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +/** + * @dev Reverts when the `signer` address retrieved from the `signature` has an invalid nonce: `invalidNonce`. + * + * @param signer The address of the signer + * @param invalidNonce The nonce retrieved for the `signer` address + * @param signature The signature used to retrieve the `signer` address + */ +error InvalidRelayNonce(address signer, uint256 invalidNonce, bytes signature); + +/** + * @dev Reverts when the start timestamp provided to {executeRelayCall} function is bigger than the current timestamp. + */ +error RelayCallBeforeStartTime(); + +/** + * @dev Reverts when the period to execute the relay call has expired. + */ +error RelayCallExpired(); diff --git a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol new file mode 100644 index 000000000..d00549a06 --- /dev/null +++ b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +import {LSP25_VERSION} from "./LSP25Constants.sol"; + +// libraries +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +// errors +import { + InvalidRelayNonce, + RelayCallBeforeStartTime, + RelayCallExpired +} from "./LSP25Errors.sol"; + +abstract contract LSP25MultiChannelNonce { + using ECDSA for *; + + // Mapping of signer -> channelId -> nonce in channel + mapping(address => mapping(uint256 => uint256)) internal _nonceStore; + + function getNonce( + address from, + uint128 channelId + ) public view virtual returns (uint256) { + uint256 nonceInChannel = _nonceStore[from][channelId]; + return (uint256(channelId) << 128) | nonceInChannel; + } + + function _validateExecuteRelayCall( + bytes memory signature, + uint256 nonce, + uint256 validityTimestamps, + uint256 msgValue, + bytes calldata callData + ) internal returns (address signerAddress) { + bytes memory encodedMessage = abi.encodePacked( + LSP25_VERSION, + block.chainid, + nonce, + validityTimestamps, + msgValue, + callData + ); + + signerAddress = address(this) + .toDataWithIntendedValidatorHash(encodedMessage) + .recover(signature); + + if (!_isValidNonce(signerAddress, nonce)) { + revert InvalidRelayNonce(signerAddress, nonce, signature); + } + + // increase nonce after successful verification + _nonceStore[signerAddress][nonce >> 128]++; + + if (validityTimestamps != 0) { + uint128 startingTimestamp = uint128(validityTimestamps >> 128); + uint128 endingTimestamp = uint128(validityTimestamps); + + // solhint-disable not-rely-on-time + if (block.timestamp < startingTimestamp) { + revert RelayCallBeforeStartTime(); + } + if (block.timestamp > endingTimestamp) { + revert RelayCallExpired(); + } + } + + return signerAddress; + } + + /** + * @notice verify the nonce `_idx` for `_from` (obtained via `getNonce(...)`) + * @dev "idx" is a 256bits (unsigned) integer, where: + * - the 128 leftmost bits = channelId + * and - the 128 rightmost bits = nonce within the channel + * @param from caller address + * @param idx (channel id + nonce within the channel) + */ + function _isValidNonce( + address from, + uint256 idx + ) internal view virtual returns (bool) { + uint256 mask = ~uint128(0); + // Alternatively: + // uint256 mask = (1<<128)-1; + // uint256 mask = 0xffffffffffffffffffffffffffffffff; + return (idx & mask) == (_nonceStore[from][idx >> 128]); + } +} diff --git a/contracts/LSP6KeyManager/ILSP6KeyManager.sol b/contracts/LSP6KeyManager/ILSP6KeyManager.sol index 8e290b426..6547c5bc3 100644 --- a/contracts/LSP6KeyManager/ILSP6KeyManager.sol +++ b/contracts/LSP6KeyManager/ILSP6KeyManager.sol @@ -29,21 +29,6 @@ interface ILSP6KeyManager is */ function target() external view returns (address); - /** - * @notice Get latest nonce for `from` in channel ID: `channelId`. - * - * @dev Get the nonce for a specific controller `from` address that can be used for signing relay transaction. - * - * @param from the address of the signer of the transaction. - * @param channelId the channel id that the signer wants to use for executing the transaction. - * - * @return the current nonce on a specific `channelId` - */ - function getNonce( - address from, - uint128 channelId - ) external view returns (uint256); - /** * @notice execute the following payload on the linked contract: `payload` * @@ -70,44 +55,4 @@ interface ILSP6KeyManager is uint256[] calldata values, bytes[] calldata payloads ) external payable returns (bytes[] memory); - - /** - * @dev Allows any address (executor) to execute a payload (= abi-encoded function call) in the linked {target} given they have a signed message from - * a controller with some permissions. - * - * @param signature a 65 bytes long signature for a meta transaction according to LSP6. - * @param nonce the nonce of the address that signed the calldata (in a specific `_channel`), obtained via {getNonce}. Used to prevent replay attack. - * @param validityTimestamps * Two `uint128` timestamps concatenated together that describes - * when the relay transaction is valid "from" (left `uint128`) and "until" as a deadline (right `uint128`). - * @param payload the abi-encoded function call to execute on the linked {target}. - * - * @return the data being returned by the function called on the linked {target}. - */ - function executeRelayCall( - bytes calldata signature, - uint256 nonce, - uint256 validityTimestamps, - bytes calldata payload - ) external payable returns (bytes memory); - - /** - * @dev Same as {executeRelayCall} but execute a batch of signed calldata payloads (abi-encoded function calls) in a single transaction. - * The signed transactions can be from multiple controllers, not necessarely the same controller signer, as long as each of these controllers - * that signed have the right permissions related to the calldata `payload` they signed. - * - * @param signatures An array of 65 bytes long signatures for meta transactions according to LSP6. - * @param nonces An array of nonces of the addresses that signed the calldata payloads (in specific channels). Obtained via {getNonce}. Used to prevent replay attack. - * @param validityTimestamps An array of two `uint128` concatenated timestamps that describe when the relay transaction is valid "from" (left `uint128`) and "until" (right `uint128`). - * @param values An array of amount of native tokens to be transferred for each calldata `payload`. - * @param payloads An array of abi-encoded function calls to execute successively on the linked {target}. - * - * @return An array of abi-decoded return data returned by the functions called on the linked {target}. - */ - function executeRelayCallBatch( - bytes[] calldata signatures, - uint256[] calldata nonces, - uint256[] calldata validityTimestamps, - uint256[] calldata values, - bytes[] calldata payloads - ) external payable returns (bytes[] memory); } diff --git a/contracts/LSP6KeyManager/LSP6Constants.sol b/contracts/LSP6KeyManager/LSP6Constants.sol index 492515f96..6a470b6fc 100644 --- a/contracts/LSP6KeyManager/LSP6Constants.sol +++ b/contracts/LSP6KeyManager/LSP6Constants.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.4; -uint256 constant LSP6_VERSION = 6; - // --- ERC165 interface ids bytes4 constant _INTERFACEID_LSP6 = 0x38bb3cdb; diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index 0d92496ea..23bb3b17c 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -35,12 +35,9 @@ import { LSP6BatchExcessiveValueSent, LSP6BatchInsufficientValueSent, InvalidPayload, - InvalidRelayNonce, NoPermissionsSet, InvalidERC725Function, - CannotSendValueToSetData, - RelayCallBeforeStartTime, - RelayCallExpired + CannotSendValueToSetData } from "./LSP6Errors.sol"; import { @@ -382,18 +379,7 @@ abstract contract LSP6KeyManagerCore is revert InvalidPayload(payload); } - bytes memory encodedMessage = abi.encodePacked( - LSP6_VERSION, - block.chainid, - nonce, - validityTimestamps, - msgValue, - payload - ); - - address signer = address(this) - .toDataWithIntendedValidatorHash(encodedMessage) - .recover(signature); + // TODO inherit LSP25 and implement verification function bool isSetData = false; if ( @@ -405,26 +391,6 @@ abstract contract LSP6KeyManagerCore is bool isReentrantCall = _nonReentrantBefore(isSetData, signer); - if (!_isValidNonce(signer, nonce)) { - revert InvalidRelayNonce(signer, nonce, signature); - } - - // increase nonce after successful verification - _nonceStore[signer][nonce >> 128]++; - - if (validityTimestamps != 0) { - uint128 startingTimestamp = uint128(validityTimestamps >> 128); - uint128 endingTimestamp = uint128(validityTimestamps); - - // solhint-disable not-rely-on-time - if (block.timestamp < startingTimestamp) { - revert RelayCallBeforeStartTime(); - } - if (block.timestamp > endingTimestamp) { - revert RelayCallExpired(); - } - } - _verifyPermissions(signer, msgValue, payload); emit VerifiedCall(signer, msgValue, bytes4(payload)); @@ -459,25 +425,6 @@ abstract contract LSP6KeyManagerCore is return result.length != 0 ? abi.decode(result, (bytes)) : result; } - /** - * @notice verify the nonce `_idx` for `_from` (obtained via `getNonce(...)`) - * @dev "idx" is a 256bits (unsigned) integer, where: - * - the 128 leftmost bits = channelId - * and - the 128 rightmost bits = nonce within the channel - * @param from caller address - * @param idx (channel id + nonce within the channel) - */ - function _isValidNonce( - address from, - uint256 idx - ) internal view virtual returns (bool) { - uint256 mask = ~uint128(0); - // Alternatively: - // uint256 mask = (1<<128)-1; - // uint256 mask = 0xffffffffffffffffffffffffffffffff; - return (idx & mask) == (_nonceStore[from][idx >> 128]); - } - /** * @dev verify if the `from` address is allowed to execute the `payload` on the `target`. * @param from either the caller of `execute(...)` or the signer of `executeRelayCall(...)`. From fae3147336804da694b466d9fb2e0d1d5a2b270f Mon Sep 17 00:00:00 2001 From: CJ42 Date: Thu, 10 Aug 2023 12:25:01 +0100 Subject: [PATCH 3/7] refactor!: update LSP6 interface ID + add `LSP25MultiChannelNonce` in LSP6 Core implementation --- constants.ts | 18 +++++---- .../LSP25ExecuteRelayCall/LSP25Constants.sol | 3 ++ contracts/LSP6KeyManager/LSP6Constants.sol | 2 +- .../LSP6KeyManager/LSP6KeyManagerCore.sol | 37 ++++++++++++++----- contracts/Mocks/ERC165Interfaces.sol | 19 +++++++++- tests/Mocks/ERC165Interfaces.test.ts | 5 +++ 6 files changed, 66 insertions(+), 18 deletions(-) diff --git a/constants.ts b/constants.ts index 058efd364..31d1154b6 100644 --- a/constants.ts +++ b/constants.ts @@ -26,7 +26,7 @@ export const INTERFACE_IDS = { ERC725Y: '0x629aa694', LSP0ERC725Account: '0x3e89ad98', LSP1UniversalReceiver: '0x6bb56a14', - LSP6KeyManager: '0x38bb3cdb', + LSP6KeyManager: '0x627ca5d3', LSP7DigitalAsset: '0xda1f85e4', LSP8IdentifiableDigitalAsset: '0x622e7a01', LSP9Vault: '0x28af17e6', @@ -36,6 +36,7 @@ export const INTERFACE_IDS = { LSP17Extension: '0xcee78b40', LSP20CallVerification: '0x1a0eb6a5', LSP20CallVerifier: '0x480c0ec2', + LSP25ExecuteRelayCall: '0x5ac79908', }; // ERC1271 @@ -209,12 +210,6 @@ export const ERC725YDataKeys = { // LSP6 // ---------- -/** - * @dev LSP6 version number for signing `executeRelayCall(...)` transaction using EIP191 - * @see for details see: https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-6-KeyManager.md#executerelaycall - */ -export const LSP6_VERSION = 6; - /** * @dev The types of calls for an AllowedCall */ @@ -319,3 +314,12 @@ export const LSP1_TYPE_IDS = { LSP14OwnershipTransferred_RecipientNotification: '0xe32c7debcb817925ba4883fdbfc52797187f28f73f860641dab1a68d9b32902c', }; + +// LSP25 +// ---------- + +/** + * @dev LSP25 version number for signing `executeRelayCall(...)` transaction using EIP191 + * @see for details see: https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-25-ExecuteRelayCall.md#executerelaycall + */ +export const LSP25_VERSION = 25; diff --git a/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol b/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol index cc3e7c960..5b0a4b5df 100644 --- a/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol +++ b/contracts/LSP25ExecuteRelayCall/LSP25Constants.sol @@ -1,5 +1,8 @@ // SPDX-License-Identifier: CC0-1.0 pragma solidity ^0.8.4; +// --- ERC165 interface ids +bytes4 constant _INTERFACEID_LSP25 = 0x5ac79908; + // version number used to validate signed relayed call uint256 constant LSP25_VERSION = 25; diff --git a/contracts/LSP6KeyManager/LSP6Constants.sol b/contracts/LSP6KeyManager/LSP6Constants.sol index 6a470b6fc..b28617a48 100644 --- a/contracts/LSP6KeyManager/LSP6Constants.sol +++ b/contracts/LSP6KeyManager/LSP6Constants.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.4; // --- ERC165 interface ids -bytes4 constant _INTERFACEID_LSP6 = 0x38bb3cdb; +bytes4 constant _INTERFACEID_LSP6 = 0x627ca5d3; // --- ERC725Y Data Keys diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index 23bb3b17c..62377a5b6 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -13,6 +13,9 @@ import {ILSP6KeyManager} from "./ILSP6KeyManager.sol"; import { ILSP20CallVerifier as ILSP20 } from "../LSP20CallVerification/ILSP20CallVerifier.sol"; +import { + ILSP25ExecuteRelayCall as ILSP25 +} from "../LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol"; // modules import {ILSP14Ownable2Step} from "../LSP14Ownable2Step/ILSP14Ownable2Step.sol"; @@ -21,6 +24,9 @@ import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import {LSP6SetDataModule} from "./LSP6Modules/LSP6SetDataModule.sol"; import {LSP6ExecuteModule} from "./LSP6Modules/LSP6ExecuteModule.sol"; import {LSP6OwnershipModule} from "./LSP6Modules/LSP6OwnershipModule.sol"; +import { + LSP25MultiChannelNonce +} from "../LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol"; // libraries import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol"; @@ -46,12 +52,12 @@ import { _ERC1271_FAILVALUE } from "../LSP0ERC725Account/LSP0Constants.sol"; import { - LSP6_VERSION, _INTERFACEID_LSP6, _PERMISSION_SIGN, _PERMISSION_REENTRANCY } from "./LSP6Constants.sol"; import "../LSP20CallVerification/LSP20Constants.sol"; +import {_INTERFACEID_LSP25} from "../LSP25ExecuteRelayCall/LSP25Constants.sol"; /** * @title Core implementation of the LSP6 Key Manager standard. @@ -67,9 +73,11 @@ abstract contract LSP6KeyManagerCore is ERC165, ILSP6KeyManager, ILSP20, + ILSP25, LSP6SetDataModule, LSP6ExecuteModule, - LSP6OwnershipModule + LSP6OwnershipModule, + LSP25MultiChannelNonce { using LSP6Utils for *; using ECDSA for *; @@ -81,8 +89,6 @@ abstract contract LSP6KeyManagerCore is // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.8/contracts/security/ReentrancyGuard.sol bool internal _reentrancyStatus; - mapping(address => mapping(uint256 => uint256)) internal _nonceStore; - /** * @inheritdoc ILSP6KeyManager */ @@ -100,11 +106,12 @@ abstract contract LSP6KeyManagerCore is interfaceId == _INTERFACEID_LSP6 || interfaceId == _INTERFACEID_ERC1271 || interfaceId == _INTERFACEID_LSP20_CALL_VERIFIER || + interfaceId == _INTERFACEID_LSP25 || super.supportsInterface(interfaceId); } /** - * @inheritdoc ILSP6KeyManager + * @inheritdoc ILSP25 * * @custom:info A signer can choose its channel number arbitrarily. Channel ID = 0 can be used for sequential nonces (transactions * that are order dependant), any other channel ID for out-of-order execution (= execution in parallel). @@ -112,7 +119,13 @@ abstract contract LSP6KeyManagerCore is function getNonce( address from, uint128 channelId - ) public view returns (uint256) { + ) + public + view + virtual + override(LSP25MultiChannelNonce, ILSP25) + returns (uint256) + { uint256 nonceInChannel = _nonceStore[from][channelId]; return (uint256(channelId) << 128) | nonceInChannel; } @@ -194,7 +207,7 @@ abstract contract LSP6KeyManagerCore is } /** - * @inheritdoc ILSP6KeyManager + * @inheritdoc ILSP25 * * @custom:events {VerifiedCall} event when the permissions related to `payload` have been verified successfully. * @@ -220,7 +233,7 @@ abstract contract LSP6KeyManagerCore is } /** - * @inheritdoc ILSP6KeyManager + * @inheritdoc ILSP25 * * @custom:requirements * - the length of `signatures`, `nonces`, `validityTimestamps`, `values` and `payloads` MUST be the same. @@ -379,7 +392,13 @@ abstract contract LSP6KeyManagerCore is revert InvalidPayload(payload); } - // TODO inherit LSP25 and implement verification function + address signer = LSP25MultiChannelNonce._validateExecuteRelayCall( + signature, + nonce, + validityTimestamps, + msgValue, + payload + ); bool isSetData = false; if ( diff --git a/contracts/Mocks/ERC165Interfaces.sol b/contracts/Mocks/ERC165Interfaces.sol index 3e9a07621..fed3817bd 100644 --- a/contracts/Mocks/ERC165Interfaces.sol +++ b/contracts/Mocks/ERC165Interfaces.sol @@ -47,6 +47,9 @@ import { import { ILSP20CallVerifier as ILSP20 } from "../LSP20CallVerification/ILSP20CallVerifier.sol"; +import { + ILSP25ExecuteRelayCall as ILSP25 +} from "../LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol"; // constants import {_INTERFACEID_LSP0} from "../LSP0ERC725Account/LSP0Constants.sol"; @@ -69,9 +72,9 @@ import { _INTERFACEID_LSP20_CALL_VERIFICATION, _INTERFACEID_LSP20_CALL_VERIFIER } from "../LSP20CallVerification/LSP20Constants.sol"; +import {_INTERFACEID_LSP25} from "../LSP25ExecuteRelayCall/LSP25Constants.sol"; // libraries - import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; @@ -235,6 +238,20 @@ contract CalculateLSPInterfaces { return interfaceId; } + + function calculateInterfaceLSP25ExecuteRelayCall() + public + pure + returns (bytes4) + { + bytes4 interfaceId = type(ILSP25).interfaceId; + require( + interfaceId == _INTERFACEID_LSP25, + "hardcoded _INTERFACEID_LSP25 does not match type(ILSP25).interfaceId" + ); + + return interfaceId; + } } /** diff --git a/tests/Mocks/ERC165Interfaces.test.ts b/tests/Mocks/ERC165Interfaces.test.ts index 9532a8205..07bb2be05 100644 --- a/tests/Mocks/ERC165Interfaces.test.ts +++ b/tests/Mocks/ERC165Interfaces.test.ts @@ -88,6 +88,11 @@ describe('Calculate LSP interfaces', () => { const result = await contract.calculateInterfaceLSP20CallVerifier(); expect(result).to.equal(INTERFACE_IDS.LSP20CallVerifier); }); + + it('LSP25ExecuteRelayCall', async () => { + const result = await contract.calculateInterfaceLSP25ExecuteRelayCall(); + expect(result).to.equal(INTERFACE_IDS.LSP25ExecuteRelayCall); + }); }); describe('Calculate ERC interfaces', () => { From a742a7fd2c3c8ad30454f2e968ecae3a6328f642 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Thu, 10 Aug 2023 12:25:34 +0100 Subject: [PATCH 4/7] test: update tests for `executeRelayCall` (single + batch) --- .../Reentrancy/BatchReentrancyRelayer.sol | 6 ++-- .../Reentrancy/SingleReentrancyRelayer.sol | 6 ++-- .../Admin/PermissionChangeOwner.test.ts | 4 +-- .../Interactions/AllowedFunctions.test.ts | 6 ++-- .../Interactions/PermissionCall.test.ts | 16 ++++----- .../Interactions/PermissionDeploy.test.ts | 10 +++--- .../PermissionTransferValue.test.ts | 6 ++-- .../LSP6KeyManager.behaviour.ts | 7 ++++ .../Relay/ExecuteRelayCall.test.ts | 34 +++++++++---------- .../Relay/MultiChannelNonce.test.ts | 18 +++++----- tests/Reentrancy/LSP6/LSP6Reentrancy.test.ts | 4 +-- tests/utils/helpers.ts | 6 ++-- 12 files changed, 67 insertions(+), 56 deletions(-) diff --git a/contracts/Mocks/Reentrancy/BatchReentrancyRelayer.sol b/contracts/Mocks/Reentrancy/BatchReentrancyRelayer.sol index 2f2c33929..32a01c962 100644 --- a/contracts/Mocks/Reentrancy/BatchReentrancyRelayer.sol +++ b/contracts/Mocks/Reentrancy/BatchReentrancyRelayer.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.4; // interfaces -import {ILSP6KeyManager} from "../../LSP6KeyManager/ILSP6KeyManager.sol"; +import { + ILSP25ExecuteRelayCall +} from "../../LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol"; contract BatchReentrancyRelayer { bytes[] private _signatures; @@ -31,7 +33,7 @@ contract BatchReentrancyRelayer { address keyManagerAddress ) external returns (bytes[] memory) { return - ILSP6KeyManager(keyManagerAddress).executeRelayCallBatch( + ILSP25ExecuteRelayCall(keyManagerAddress).executeRelayCallBatch( _signatures, _nonces, _validityTimestamps, diff --git a/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol b/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol index df01354ed..f1e067636 100644 --- a/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol +++ b/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.4; // interfaces -import {ILSP6KeyManager} from "../../LSP6KeyManager/ILSP6KeyManager.sol"; +import { + ILSP25ExecuteRelayCall +} from "../../LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol"; contract SingleReentrancyRelayer { bytes private _signature; @@ -28,7 +30,7 @@ contract SingleReentrancyRelayer { address keyManagerAddress ) external returns (bytes memory) { return - ILSP6KeyManager(keyManagerAddress).executeRelayCall( + ILSP25ExecuteRelayCall(keyManagerAddress).executeRelayCall( _signature, _nonce, _validityTimestamps, diff --git a/tests/LSP6KeyManager/Admin/PermissionChangeOwner.test.ts b/tests/LSP6KeyManager/Admin/PermissionChangeOwner.test.ts index 2e8f66352..bbd3852fc 100644 --- a/tests/LSP6KeyManager/Admin/PermissionChangeOwner.test.ts +++ b/tests/LSP6KeyManager/Admin/PermissionChangeOwner.test.ts @@ -4,7 +4,7 @@ import { BigNumber } from 'ethers'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; // constants -import { ERC725YDataKeys, PERMISSIONS, OPERATION_TYPES, LSP6_VERSION } from '../../../constants'; +import { ERC725YDataKeys, PERMISSIONS, OPERATION_TYPES, LSP25_VERSION } from '../../../constants'; import { LSP6KeyManager, LSP6KeyManager__factory } from '../../../types'; @@ -355,7 +355,7 @@ export const shouldBehaveLikePermissionChangeOwner = ( const encodedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], - [LSP6_VERSION, HARDHAT_CHAINID, nonce, validityTimestamps, valueToSend, payload], + [LSP25_VERSION, HARDHAT_CHAINID, nonce, validityTimestamps, valueToSend, payload], ); const eip191Signer = new EIP191Signer(); diff --git a/tests/LSP6KeyManager/Interactions/AllowedFunctions.test.ts b/tests/LSP6KeyManager/Interactions/AllowedFunctions.test.ts index 9ec445355..b1d45754d 100644 --- a/tests/LSP6KeyManager/Interactions/AllowedFunctions.test.ts +++ b/tests/LSP6KeyManager/Interactions/AllowedFunctions.test.ts @@ -16,7 +16,7 @@ import { import { ERC725YDataKeys, OPERATION_TYPES, - LSP6_VERSION, + LSP25_VERSION, PERMISSIONS, INTERFACE_IDS, CALLTYPE, @@ -219,7 +219,7 @@ export const shouldBehaveLikeAllowedFunctions = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise Promise Promise Promise { + const result = await context.keyManager.supportsInterface( + INTERFACE_IDS.LSP25ExecuteRelayCall, + ); + expect(result).to.be.true; + }); + it('should be linked to the right ERC725 account contract', async () => { const account = await context.keyManager.target(); expect(account).to.equal(context.universalProfile.address); diff --git a/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts b/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts index 119e86bef..eba981a43 100644 --- a/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts +++ b/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts @@ -17,7 +17,7 @@ import { ALL_PERMISSIONS, ERC725YDataKeys, OPERATION_TYPES, - LSP6_VERSION, + LSP25_VERSION, PERMISSIONS, CALLTYPE, INTERFACE_IDS, @@ -102,7 +102,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const valueToSign = 5; const signedMessageParams = { - lsp6Version: LSP6_VERSION, + lsp25Version: LSP25_VERSION, chainId: 31337, // HARDHAT_CHAINID nonce: latestNonce, validityTimestamps, @@ -115,7 +115,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const encodedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], [ - signedMessageParams.lsp6Version, + signedMessageParams.lsp25Version, signedMessageParams.chainId, signedMessageParams.nonce, signedMessageParams.validityTimestamps, @@ -160,7 +160,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const valueToSign = 5; const signedMessageParams = { - lsp6Version: LSP6_VERSION, + lsp25Version: LSP25_VERSION, chainId: 31337, // HARDHAT_CHAINID nonce: latestNonce, validityTimestamps, @@ -173,7 +173,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const encodedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], [ - signedMessageParams.lsp6Version, + signedMessageParams.lsp25Version, signedMessageParams.chainId, signedMessageParams.nonce, signedMessageParams.validityTimestamps, @@ -218,7 +218,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const valueToSendFromRelayer = 10; const signedMessageParams = { - lsp6Version: LSP6_VERSION, + lsp25Version: LSP25_VERSION, chainId: 31337, // HARDHAT_CHAINID nonce: latestNonce, validityTimestamps, @@ -229,7 +229,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const encodedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], [ - signedMessageParams.lsp6Version, + signedMessageParams.lsp25Version, signedMessageParams.chainId, signedMessageParams.nonce, signedMessageParams.validityTimestamps, @@ -287,7 +287,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const valueToSendFromRelayer = 10; const signedMessageParams = { - lsp6Version: LSP6_VERSION, + lsp25Version: LSP25_VERSION, chainId: 31337, // HARDHAT_CHAINID nonce: latestNonce, validityTimestamps, @@ -298,7 +298,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const encodedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], [ - signedMessageParams.lsp6Version, + signedMessageParams.lsp25Version, signedMessageParams.chainId, signedMessageParams.nonce, signedMessageParams.validityTimestamps, @@ -359,7 +359,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const valueToSendFromRelayer = 0; const signedMessageParams = { - lsp6Version: LSP6_VERSION, + lsp25Version: LSP25_VERSION, chainId: 31337, // HARDHAT_CHAINID nonce: latestNonce, validityTimestamps, @@ -370,7 +370,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const encodedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], [ - signedMessageParams.lsp6Version, + signedMessageParams.lsp25Version, signedMessageParams.chainId, signedMessageParams.nonce, signedMessageParams.validityTimestamps, @@ -436,7 +436,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const valueToSendFromRelayer = 51; const signedMessageParams = { - lsp6Version: LSP6_VERSION, + lsp25Version: LSP25_VERSION, chainId: 31337, // HARDHAT_CHAINID nonce: latestNonce, validityTimestamps, @@ -447,7 +447,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const encodedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], [ - signedMessageParams.lsp6Version, + signedMessageParams.lsp25Version, signedMessageParams.chainId, signedMessageParams.nonce, signedMessageParams.validityTimestamps, @@ -507,7 +507,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const valueToSendFromRelayer = 51; const signedMessageParams = { - lsp6Version: LSP6_VERSION, + lsp25Version: LSP25_VERSION, chainId: 31337, // HARDHAT_CHAINID nonce: latestNonce, validityTimestamps, @@ -518,7 +518,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const encodedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], [ - signedMessageParams.lsp6Version, + signedMessageParams.lsp25Version, signedMessageParams.chainId, signedMessageParams.nonce, signedMessageParams.validityTimestamps, @@ -1100,7 +1100,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( // the Key Manager recovers the address based on: // - the signature provided // - the encoded message generated internally using: - // `abi.encodePacked(LSP6_VERSION, block.chainid,nonce, msg.value, payload);` + // `abi.encodePacked(LSP25_VERSION, block.chainid,nonce, msg.value, payload);` // // the address is recovered as follow: // `address signer = address(this).toDataWithIntendedValidator(encodedMessage).recover(signature);` @@ -1116,7 +1116,7 @@ export const shouldBehaveLikeExecuteRelayCall = ( const encodedMessage = ethers.utils.solidityPack( ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], - [6, 31337, ownerNonce.add(1), validityTimestamps, 0, transferLyxPayload], + [LSP25_VERSION, 31337, ownerNonce.add(1), validityTimestamps, 0, transferLyxPayload], ); const hashedDataWithIntendedValidator = eip191.hashDataWithIntendedValidator( diff --git a/tests/LSP6KeyManager/Relay/MultiChannelNonce.test.ts b/tests/LSP6KeyManager/Relay/MultiChannelNonce.test.ts index b7e33df91..bfa7cb071 100644 --- a/tests/LSP6KeyManager/Relay/MultiChannelNonce.test.ts +++ b/tests/LSP6KeyManager/Relay/MultiChannelNonce.test.ts @@ -10,7 +10,7 @@ import { ALL_PERMISSIONS, ERC725YDataKeys, OPERATION_TYPES, - LSP6_VERSION, + LSP25_VERSION, PERMISSIONS, CALLTYPE, } from '../../../constants'; @@ -108,7 +108,7 @@ export const shouldBehaveLikeMultiChannelNonce = (buildContext: () => Promise Promise Promise Promise Promise Promise Promise Promise Date: Tue, 15 Aug 2023 17:42:17 +0100 Subject: [PATCH 5/7] docs: update LSP6 docs + add LSP25 docs --- README.md | 1 + .../ILSP25ExecuteRelayCall.sol | 37 +++--- .../LSP25MultiChannelNonce.sol | 67 ++++++---- .../LSP6KeyManager/LSP6KeyManagerCore.sol | 37 +++--- .../LSP6KeyManager/LSP6KeyManagerInit.sol | 7 +- .../LSP25MultiChannelNonce.md | 94 +++++++++++++ .../LSP6KeyManager/LSP6KeyManager.md | 124 +++++++++++------- dodoc/config.ts | 1 + 8 files changed, 264 insertions(+), 104 deletions(-) create mode 100644 docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md diff --git a/README.md b/README.md index 18567535f..f79b2666e 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ import { ERC725YDataKeys, PERMISSIONS, ALL_PERMISSIONS, + LSP25_VERSION, ErrorSelectors, EventSigHashes, FunctionSelectors, diff --git a/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol b/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol index 14f0ea3e5..b75e053c7 100644 --- a/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol +++ b/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.0; interface ILSP25ExecuteRelayCall { /** - * @notice Get latest nonce for `from` in channel ID: `channelId`. + * @notice Reading the latest nonce of address `from` in the channel ID `channelId`. * - * @dev Get the nonce for a specific controller `from` address that can be used for signing relay transaction. + * @dev Get the nonce for a specific controller `from` address that can be used for signing relay transactions via {executeRelayCall}. * - * @param from the address of the signer of the transaction. - * @param channelId the channel id that the signer wants to use for executing the transaction. + * @param from The address of the signer of the transaction. + * @param channelId The channel id that the signer wants to use for executing the transaction. * - * @return the current nonce on a specific `channelId` + * @return The current nonce on a specific `channelId`. */ function getNonce( address from, @@ -18,18 +18,25 @@ interface ILSP25ExecuteRelayCall { ) external view returns (uint256); /** - * @notice Executing a relay call (= meta-transaction). + * @notice Executing the following payload given the nonce `nonce` and signature `signature`. Payload: `payload` * - * @dev Allows any address (executor) to execute a payload (= abi-encoded function call) in the linked {target} given they have a signed message from - * a controller with some permissions. + * @dev Allows any address (executor) to execute a payload (= abi-encoded function call), given they have a valid signature from a signer address and a valid `nonce` for this signer. + * The signature MUST be generated according to the signature format defined by the LSP25 standard. * - * @param signature A 65 bytes long signature for a meta transaction according to LSP6. + * @param signature A 65 bytes long signature for a meta transaction according to LSP25. * @param nonce The nonce of the address that signed the calldata (in a specific `_channel`), obtained via {getNonce}. Used to prevent replay attack. * @param validityTimestamps Two `uint128` timestamps concatenated together that describes * when the relay transaction is valid "from" (left `uint128`) and "until" as a deadline (right `uint128`). - * @param payload The abi-encoded function call to execute on the linked {target}. + * @param payload The abi-encoded function call to execute. * - * @return The data being returned by the function called on the linked {target}. + * @return The data being returned by the function executed. + * + * @custom:requirements + * - `nonce` MUST be a valid nonce nonce provided (see {getNonce} function). + * - The transaction MUST be submitted within a valid time period defined by the `validityTimestamp`. + * + * @custom:hint You can use `validityTimestamps == 0` to define an `executeRelayCall` transaction that is indefinitely valid, + * meaning that does not require to start from a specific date/time, or that has an expiration date/time. */ function executeRelayCall( bytes calldata signature, @@ -42,16 +49,14 @@ interface ILSP25ExecuteRelayCall { * @notice Executing a batch of relay calls (= meta-transactions). * * @dev Same as {executeRelayCall} but execute a batch of signed calldata payloads (abi-encoded function calls) in a single transaction. - * The signed transactions can be from multiple controllers, not necessarely the same controller signer, as long as each of these controllers - * that signed have the right permissions related to the calldata `payload` they signed. * - * @param signatures An array of 65 bytes long signatures for meta transactions according to LSP6. + * @param signatures An array of 65 bytes long signatures for meta transactions according to LSP25. * @param nonces An array of nonces of the addresses that signed the calldata payloads (in specific channels). Obtained via {getNonce}. Used to prevent replay attack. * @param validityTimestamps An array of two `uint128` concatenated timestamps that describe when the relay transaction is valid "from" (left `uint128`) and "until" (right `uint128`). * @param values An array of amount of native tokens to be transferred for each calldata `payload`. - * @param payloads An array of abi-encoded function calls to be executed successively on the linked {target}. + * @param payloads An array of abi-encoded function calls to be executed successively. * - * @return An array of abi-decoded data returned by the functions called on the linked {target}. + * @return An array of abi-decoded data returned by the functions executed. */ function executeRelayCallBatch( bytes[] calldata signatures, diff --git a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol index d00549a06..be23f67f2 100644 --- a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol +++ b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol @@ -1,11 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.4; -import {LSP25_VERSION} from "./LSP25Constants.sol"; - // libraries import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +// constants +import {LSP25_VERSION} from "./LSP25Constants.sol"; + // errors import { InvalidRelayNonce, @@ -13,27 +14,45 @@ import { RelayCallExpired } from "./LSP25Errors.sol"; +/** + * @title Implementation of the multi channel nonce and the signature verification defined in the LSP25 standard. + * @author Jean Cavallera (CJ42) + * @dev This contract can be used as a backbone for other smart contracts to implement meta-transactions via the LSP25 Execute Relay Call interface. + * + * It contains a storage of nonces for signer addresses across various channel IDs, enabling these signers to submit signed transactions that order-independant. + * (transactions that do not need to be submitted one after the other in a specific order). + * + * Finally, it contains internal functions to verify signatures for specific calldata according the signature format specified in the LSP25 standard. + */ abstract contract LSP25MultiChannelNonce { using ECDSA for *; // Mapping of signer -> channelId -> nonce in channel mapping(address => mapping(uint256 => uint256)) internal _nonceStore; - function getNonce( - address from, - uint128 channelId - ) public view virtual returns (uint256) { - uint256 nonceInChannel = _nonceStore[from][channelId]; - return (uint256(channelId) << 128) | nonceInChannel; - } - + /** + * @dev Validate that the `nonce` given for the `signature` signed and the `payload` to execute is valid + * and conform to the signature format according to the LSP25 standard. + * + * @param signature A valid signature for a signer, generated according to the signature format specified in the LSP25 standard. + * @param nonce The nonce that the signer used to generate the `signature`. + * @param validityTimestamps Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, + * and the right-most `uint128` represents the timestamp after which the transaction expire. + * @param callData The abi-encoded function call to execute. + * + * @return recoveredSignerAddress The address of the signer recovered, for which the signature was validated. + * + * @custom:warning Be aware that this function can also throw an error if the `callData` was signed incorrectly (not conforming to the signature format defined in the LSP25 standard). + * The contract cannot distinguish if the data is signed correctly or not. Instead, it will recover an incorrect signer address from the signature + * and throw an {InvalidRelayNonce} error with the incorrect signer address as the first parameter. + */ function _validateExecuteRelayCall( bytes memory signature, uint256 nonce, uint256 validityTimestamps, uint256 msgValue, bytes calldata callData - ) internal returns (address signerAddress) { + ) internal returns (address recoveredSignerAddress) { bytes memory encodedMessage = abi.encodePacked( LSP25_VERSION, block.chainid, @@ -43,16 +62,16 @@ abstract contract LSP25MultiChannelNonce { callData ); - signerAddress = address(this) + recoveredSignerAddress = address(this) .toDataWithIntendedValidatorHash(encodedMessage) .recover(signature); - if (!_isValidNonce(signerAddress, nonce)) { - revert InvalidRelayNonce(signerAddress, nonce, signature); + if (!_isValidNonce(recoveredSignerAddress, nonce)) { + revert InvalidRelayNonce(recoveredSignerAddress, nonce, signature); } // increase nonce after successful verification - _nonceStore[signerAddress][nonce >> 128]++; + _nonceStore[recoveredSignerAddress][nonce >> 128]++; if (validityTimestamps != 0) { uint128 startingTimestamp = uint128(validityTimestamps >> 128); @@ -67,16 +86,20 @@ abstract contract LSP25MultiChannelNonce { } } - return signerAddress; + return recoveredSignerAddress; } /** - * @notice verify the nonce `_idx` for `_from` (obtained via `getNonce(...)`) - * @dev "idx" is a 256bits (unsigned) integer, where: - * - the 128 leftmost bits = channelId - * and - the 128 rightmost bits = nonce within the channel - * @param from caller address - * @param idx (channel id + nonce within the channel) + * @dev Verify that the nonce `_idx` for `_from` (obtained via {getNonce}) is valid in its channel ID. + * + * The "idx" is a 256bits (unsigned) integer, where: + * - the 128 leftmost bits = channelId + * - and the 128 rightmost bits = nonce within the channel + + * @param from The signer's address. + * @param idx The concatenation of the `channelId` + `nonce` within a specific channel ID. + * + * @return true if the nonce is the latest nonce for the `signer`, false otherwise. */ function _isValidNonce( address from, diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index 903a68d90..6f94ae9b6 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -119,13 +119,7 @@ abstract contract LSP6KeyManagerCore is function getNonce( address from, uint128 channelId - ) - public - view - virtual - override(LSP25MultiChannelNonce, ILSP25) - returns (uint256) - { + ) public view virtual returns (uint256) { uint256 nonceInChannel = _nonceStore[from][channelId]; return (uint256(channelId) << 128) | nonceInChannel; } @@ -209,10 +203,13 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc ILSP25 * - * @custom:events {VerifiedCall} event when the permissions related to `payload` have been verified successfully. + * @dev Allows any address (executor) to execute a payload (= abi-encoded function call), given they have a valid signature from a signer address and a valid `nonce` for this signer. + * The signature MUST be generated according to the signature format defined by the LSP25 standard. + * + * The signer that generated the `signature` MUST be a controller with some permissions on the linked {target}. + * The `payload` will be executed on the {target} contract once the LSP25 signature and the permissions of the signer have been verified. * - * @custom:hint You can use `validityTimestamps == 0` to define an `executeRelayCall` transaction that is indefinitely valid, - * meaning that does not require to start from a specific date/time, or that has an expiration date/time/ + * @custom:events {VerifiedCall} event when the permissions related to `payload` have been verified successfully. * * @custom:hint If you are looking to learn how to sign and execute relay transactions via the Key Manager, * see our Javascript step by step guide [_"Execute Relay Transactions"_](../../guides/key-manager/execute-relay-transactions.md). @@ -238,6 +235,11 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc ILSP25 * + * @dev Same as {executeRelayCall} but execute a batch of signed calldata payloads (abi-encoded function calls) in a single transaction. + * + * The `signatures` can be from multiple controllers, not necessarely the same controller, as long as each of these controllers + * that signed have the right permissions related to the calldata `payload` they signed. + * * @custom:requirements * - the length of `signatures`, `nonces`, `validityTimestamps`, `values` and `payloads` MUST be the same. * - the value sent to this function (`msg.value`) MUST be equal to the sum of all `values` in the batch. @@ -426,9 +428,9 @@ abstract contract LSP6KeyManagerCore is } /** - * @notice execute the `payload` passed to `execute(...)` or `executeRelayCall(...)` - * @param payload the abi-encoded function call to execute on the target. - * @return bytes the result from calling the target with `payload` + * @notice Execute the `payload` passed to `execute(...)` or `executeRelayCall(...)` + * @param payload The abi-encoded function call to execute on the {target} contract. + * @return bytes The data returned by the call made to the linked {target} contract. */ function _executePayload( uint256 msgValue, @@ -448,9 +450,9 @@ abstract contract LSP6KeyManagerCore is } /** - * @dev verify if the `from` address is allowed to execute the `payload` on the `target`. - * @param from either the caller of `execute(...)` or the signer of `executeRelayCall(...)`. - * @param payload the payload to execute on the `target`. + * @dev Verify if the `from` address is allowed to execute the `payload` on the {target} contract linked to this Key Manager. + * @param from Either the caller of {execute} or the signer of {executeRelayCall}. + * @param payload The abi-encoded function call to execute on the {target} contract. */ function _verifyPermissions( address from, @@ -552,7 +554,8 @@ abstract contract LSP6KeyManagerCore is } /** - * @dev revert if `controller`'s `addressPermissions` doesn't contain `permissionsRequired` + * @dev Check if the `controller` has the `permissionRequired` among its permission listed in `controllerPermissions` + * If not, this function will revert with the error `NotAuthorised` and the name of the permission missing by the controller. * @param controller the caller address * @param addressPermissions the caller's permissions BitArray * @param permissionRequired the required permission diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol b/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol index 53b27e42f..dc55b8fdd 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerInit.sol @@ -11,15 +11,16 @@ import {LSP6KeyManagerInitAbstract} from "./LSP6KeyManagerInitAbstract.sol"; */ contract LSP6KeyManagerInit is LSP6KeyManagerInitAbstract { /** - * @dev Initialize (= lock) base implementation contract on deployment + * @notice Deploying a LSP6KeyManagerInit to be used as base contract behind proxy. + * @dev Initialize (= lock) base implementation contract on deployment. */ constructor() { _disableInitializers(); } /** - * @notice Deploying a LSP6KeyManager linked to contract at address `target_`. - * @dev Deploy a Key Manager and set the `target_` address in the contract storage, + * @notice Initializing a LSP6KeyManagerInit linked to contract at address `target_`. + * @dev Initialise a LSP6KeyManager and set the `target_` address in the contract storage, * making this Key Manager linked to this `target_` contract. * * @param target_ The address of the contract to control and forward calldata payloads to. diff --git a/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md b/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md new file mode 100644 index 000000000..91b7f9306 --- /dev/null +++ b/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md @@ -0,0 +1,94 @@ + + + +# LSP25MultiChannelNonce + +:::info Standard Specifications + +[`LSP-25-ExecuteRelayCall`](https://github.com/lukso-network/lips/tree/main/LSPs/LSP-25-ExecuteRelayCall.md) + +::: +:::info Solidity implementation + +[`LSP25MultiChannelNonce.sol`](https://github.com/lukso-network/lsp-smart-contracts/blob/develop/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol) + +::: + +> Implementation of the multi channel nonce and the signature verification defined in the LSP25 standard. + +This contract can be used as a backbone for other smart contracts to implement meta-transactions via the LSP25 Execute Relay Call interface. It contains a storage of nonces for signer addresses across various channel IDs, enabling these signers to submit signed transactions that order-independant. (transactions that do not need to be submitted one after the other in a specific order). Finally, it contains internal functions to verify signatures for specific calldata according the signature format specified in the LSP25 standard. + +## 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. + +### \_validateExecuteRelayCall + +:::caution Warning + +Be aware that this function can also throw an error if the `callData` was signed incorrectly (not conforming to the signature format defined in the LSP25 standard). +The contract cannot distinguish if the data is signed correctly or not. Instead, it will recover an incorrect signer address from the signature +and throw an {InvalidRelayNonce} error with the incorrect signer address as the first parameter. + +::: + +```solidity +function _validateExecuteRelayCall( + bytes signature, + uint256 nonce, + uint256 validityTimestamps, + uint256 msgValue, + bytes callData +) internal nonpayable returns (address recoveredSignerAddress); +``` + +Validate that the `nonce` given for the `signature` signed and the `payload` to execute is valid +and conform to the signature format according to the LSP25 standard. + +#### Parameters + +| Name | Type | Description | +| -------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | +| `signature` | `bytes` | A valid signature for a signer, generated according to the signature format specified in the LSP25 standard. | +| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | +| `validityTimestamps` | `uint256` | Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, | +| `msgValue` | `uint256` | - | +| `callData` | `bytes` | The abi-encoded function call to execute. | + +#### Returns + +| Name | Type | Description | +| ------------------------ | :-------: | --------------------------------------------------------------------------- | +| `recoveredSignerAddress` | `address` | The address of the signer recovered, for which the signature was validated. | + +
+ +### \_isValidNonce + +```solidity +function _isValidNonce(address from, uint256 idx) internal view returns (bool); +``` + +Verify that the nonce `_idx` for `_from` (obtained via [`getNonce`](#getnonce)) is valid in its channel ID. +The "idx" is a 256bits (unsigned) integer, where: + +- the 128 leftmost bits = channelId + +- and the 128 rightmost bits = nonce within the channel + +#### Parameters + +| Name | Type | Description | +| ------ | :-------: | ---------------------------------------------------------------------------- | +| `from` | `address` | The signer's address. | +| `idx` | `uint256` | The concatenation of the `channelId` + `nonce` within a specific channel ID. | + +#### Returns + +| Name | Type | Description | +| ---- | :----: | ------------------------------------------------------------------------ | +| `0` | `bool` | true if the nonce is the latest nonce for the `signer`, false otherwise. | + +
diff --git a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md index ed729081a..72cdb3bef 100644 --- a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md +++ b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md @@ -151,7 +151,7 @@ Same as [`execute`](#execute) but execute a batch of payloads (abi-encoded funct :::tip Hint -You can use `validityTimestamps == 0` to define an `executeRelayCall` transaction that is indefinitely valid, meaning that does not require to start from a specific date/time, or that has an expiration date/time/If you are looking to learn how to sign and execute relay transactions via the Key Manager, see our Javascript step by step guide [_"Execute Relay Transactions"_](../../guides/key-manager/execute-relay-transactions.md). See the LSP6 Standard page for more details on how to [generate a valid signature for Execute Relay Call](../universal-profile/lsp6-key-manager.md#how-to-sign-relay-transactions). +If you are looking to learn how to sign and execute relay transactions via the Key Manager, see our Javascript step by step guide [_"Execute Relay Transactions"_](../../guides/key-manager/execute-relay-transactions.md). See the LSP6 Standard page for more details on how to [generate a valid signature for Execute Relay Call](../universal-profile/lsp6-key-manager.md#how-to-sign-relay-transactions). ::: @@ -164,9 +164,9 @@ function executeRelayCall( ) external payable returns (bytes); ``` -_Executing a relay call (= meta-transaction)._ +_Executing the following payload given the nonce `nonce` and signature `signature`. Payload: `payload`_ -Allows any address (executor) to execute a payload (= abi-encoded function call) in the linked [`target`](#target) given they have a signed message from a controller with some permissions. +Allows any address (executor) to execute a payload (= abi-encoded function call), given they have a valid signature from a signer address and a valid `nonce` for this signer. The signature MUST be generated according to the signature format defined by the LSP25 standard. The signer that generated the `signature` MUST be a controller with some permissions on the linked [`target`](#target). The `payload` will be executed on the [`target`](#target) contract once the LSP25 signature and the permissions of the signer have been verified.
@@ -180,16 +180,16 @@ Allows any address (executor) to execute a payload (= abi-encoded function call) | Name | Type | Description | | -------------------- | :-------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `signature` | `bytes` | A 65 bytes long signature for a meta transaction according to LSP6. | +| `signature` | `bytes` | A 65 bytes long signature for a meta transaction according to LSP25. | | `nonce` | `uint256` | The nonce of the address that signed the calldata (in a specific `_channel`), obtained via {getNonce}. Used to prevent replay attack. | | `validityTimestamps` | `uint256` | Two `uint128` timestamps concatenated together that describes when the relay transaction is valid "from" (left `uint128`) and "until" as a deadline (right `uint128`). | -| `payload` | `bytes` | The abi-encoded function call to execute on the linked {target}. | +| `payload` | `bytes` | The abi-encoded function call to execute. | #### Returns -| Name | Type | Description | -| ---- | :-----: | ---------------------------------------------------------------------- | -| `0` | `bytes` | The data being returned by the function called on the linked {target}. | +| Name | Type | Description | +| ---- | :-----: | ------------------------------------------------- | +| `0` | `bytes` | The data being returned by the function executed. |
@@ -216,7 +216,7 @@ function executeRelayCallBatch( _Executing a batch of relay calls (= meta-transactions)._ -Same as [`executeRelayCall`](#executerelaycall) but execute a batch of signed calldata payloads (abi-encoded function calls) in a single transaction. The signed transactions can be from multiple controllers, not necessarely the same controller signer, as long as each of these controllers that signed have the right permissions related to the calldata `payload` they signed. +Same as [`executeRelayCall`](#executerelaycall) but execute a batch of signed calldata payloads (abi-encoded function calls) in a single transaction. The `signatures` can be from multiple controllers, not necessarely the same controller, as long as each of these controllers that signed have the right permissions related to the calldata `payload` they signed.
@@ -231,17 +231,17 @@ Same as [`executeRelayCall`](#executerelaycall) but execute a batch of signed ca | Name | Type | Description | | -------------------- | :---------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `signatures` | `bytes[]` | An array of 65 bytes long signatures for meta transactions according to LSP6. | +| `signatures` | `bytes[]` | An array of 65 bytes long signatures for meta transactions according to LSP25. | | `nonces` | `uint256[]` | An array of nonces of the addresses that signed the calldata payloads (in specific channels). Obtained via {getNonce}. Used to prevent replay attack. | | `validityTimestamps` | `uint256[]` | An array of two `uint128` concatenated timestamps that describe when the relay transaction is valid "from" (left `uint128`) and "until" (right `uint128`). | | `values` | `uint256[]` | An array of amount of native tokens to be transferred for each calldata `payload`. | -| `payloads` | `bytes[]` | An array of abi-encoded function calls to be executed successively on the linked {target}. | +| `payloads` | `bytes[]` | An array of abi-encoded function calls to be executed successively. | #### Returns -| Name | Type | Description | -| ---- | :-------: | ------------------------------------------------------------------------------------- | -| `0` | `bytes[]` | An array of abi-decoded data returned by the functions called on the linked {target}. | +| Name | Type | Description | +| ---- | :-------: | ---------------------------------------------------------------- | +| `0` | `bytes[]` | An array of abi-decoded data returned by the functions executed. |
@@ -269,22 +269,22 @@ function getNonce( ) external view returns (uint256); ``` -_Get latest nonce for `from` in channel ID: `channelId`._ +_Reading the latest nonce of address `from` in the channel ID `channelId`._ -Get the nonce for a specific controller `from` address that can be used for signing relay transaction. +Get the nonce for a specific controller `from` address that can be used for signing relay transactions via [`executeRelayCall`](#executerelaycall). #### Parameters | Name | Type | Description | | ----------- | :-------: | -------------------------------------------------------------------------- | -| `from` | `address` | the address of the signer of the transaction. | -| `channelId` | `uint128` | the channel id that the signer wants to use for executing the transaction. | +| `from` | `address` | The address of the signer of the transaction. | +| `channelId` | `uint128` | The channel id that the signer wants to use for executing the transaction. | #### Returns -| Name | Type | Description | -| ---- | :-------: | ------------------------------------------- | -| `0` | `uint256` | the current nonce on a specific `channelId` | +| Name | Type | Description | +| ---- | :-------: | -------------------------------------------- | +| `0` | `uint256` | The current nonce on a specific `channelId`. |
@@ -757,7 +757,8 @@ function _requirePermissions( ) internal pure; ``` -revert if `controller`'s `addressPermissions` doesn't contain `permissionsRequired` +Check if the `controller` has the `permissionRequired` among its permission listed in `controllerPermissions` +If not, this function will revert with the error `NotAuthorised` and the name of the permission missing by the controller. #### Parameters @@ -938,6 +939,14 @@ function _verifyOwnershipPermissions( ### \_validateExecuteRelayCall +:::caution Warning + +Be aware that this function can also throw an error if the `callData` was signed incorrectly (not conforming to the signature format defined in the LSP25 standard). +The contract cannot distinguish if the data is signed correctly or not. Instead, it will recover an incorrect signer address from the signature +and throw an {InvalidRelayNonce} error with the incorrect signer address as the first parameter. + +::: + ```solidity function _validateExecuteRelayCall( bytes signature, @@ -945,9 +954,28 @@ function _validateExecuteRelayCall( uint256 validityTimestamps, uint256 msgValue, bytes callData -) internal nonpayable returns (address signerAddress); +) internal nonpayable returns (address recoveredSignerAddress); ``` +Validate that the `nonce` given for the `signature` signed and the `payload` to execute is valid +and conform to the signature format according to the LSP25 standard. + +#### Parameters + +| Name | Type | Description | +| -------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | +| `signature` | `bytes` | A valid signature for a signer, generated according to the signature format specified in the LSP25 standard. | +| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | +| `validityTimestamps` | `uint256` | Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, | +| `msgValue` | `uint256` | - | +| `callData` | `bytes` | The abi-encoded function call to execute. | + +#### Returns + +| Name | Type | Description | +| ------------------------ | :-------: | --------------------------------------------------------------------------- | +| `recoveredSignerAddress` | `address` | The address of the signer recovered, for which the signature was validated. | +
### \_isValidNonce @@ -956,21 +984,25 @@ function _validateExecuteRelayCall( function _isValidNonce(address from, uint256 idx) internal view returns (bool); ``` -_verify the nonce `_idx` for `_from` (obtained via `getNonce(...)`)_ - -"idx" is a 256bits (unsigned) integer, where: +Verify that the nonce `_idx` for `_from` (obtained via [`getNonce`](#getnonce)) is valid in its channel ID. +The "idx" is a 256bits (unsigned) integer, where: - the 128 leftmost bits = channelId - and -- the 128 rightmost bits = nonce within the channel +- and the 128 rightmost bits = nonce within the channel #### Parameters -| Name | Type | Description | -| ------ | :-------: | --------------------------------------- | -| `from` | `address` | caller address | -| `idx` | `uint256` | (channel id + nonce within the channel) | +| Name | Type | Description | +| ------ | :-------: | ---------------------------------------------------------------------------- | +| `from` | `address` | The signer's address. | +| `idx` | `uint256` | The concatenation of the `channelId` + `nonce` within a specific channel ID. | + +#### Returns + +| Name | Type | Description | +| ---- | :----: | ------------------------------------------------------------------------ | +| `0` | `bool` | true if the nonce is the latest nonce for the `signer`, false otherwise. |
@@ -1008,20 +1040,20 @@ function _executePayload( ) internal nonpayable returns (bytes); ``` -_execute the `payload` passed to `execute(...)` or `executeRelayCall(...)`_ +_Execute the `payload` passed to `execute(...)` or `executeRelayCall(...)`_ #### Parameters -| Name | Type | Description | -| ---------- | :-------: | ------------------------------------------------------- | -| `msgValue` | `uint256` | - | -| `payload` | `bytes` | the abi-encoded function call to execute on the target. | +| Name | Type | Description | +| ---------- | :-------: | ------------------------------------------------------------------ | +| `msgValue` | `uint256` | - | +| `payload` | `bytes` | The abi-encoded function call to execute on the {target} contract. | #### Returns -| Name | Type | Description | -| ---- | :-----: | ------------------------------------------------------- | -| `0` | `bytes` | bytes the result from calling the target with `payload` | +| Name | Type | Description | +| ---- | :-----: | ------------------------------------------------------------------------- | +| `0` | `bytes` | bytes The data returned by the call made to the linked {target} contract. |
@@ -1035,15 +1067,15 @@ function _verifyPermissions( ) internal view; ``` -verify if the `from` address is allowed to execute the `payload` on the `target`. +Verify if the `from` address is allowed to execute the `payload` on the [`target`](#target) contract linked to this Key Manager. #### Parameters -| Name | Type | Description | -| ---------- | :-------: | ----------------------------------------------------------------------------- | -| `from` | `address` | either the caller of `execute(...)` or the signer of `executeRelayCall(...)`. | -| `msgValue` | `uint256` | - | -| `payload` | `bytes` | the payload to execute on the `target`. | +| Name | Type | Description | +| ---------- | :-------: | ------------------------------------------------------------------- | +| `from` | `address` | Either the caller of {execute} or the signer of {executeRelayCall}. | +| `msgValue` | `uint256` | - | +| `payload` | `bytes` | The abi-encoded function call to execute on the {target} contract. |
diff --git a/dodoc/config.ts b/dodoc/config.ts index 254dc7214..7890b808f 100644 --- a/dodoc/config.ts +++ b/dodoc/config.ts @@ -34,6 +34,7 @@ export const dodocConfig = { 'contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol', 'contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol', 'contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol', + 'contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol', // libraries -------------------- 'contracts/LSP0ERC725Account/LSP0Utils.sol', From 9e9dcfff3a756b8f84a8b1dd8fca20ffef4414d5 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Fri, 18 Aug 2023 07:24:59 +0100 Subject: [PATCH 6/7] test: add tests for LSP25 --- .gitignore | 2 +- .../ILSP25ExecuteRelayCall.sol | 2 +- .../LSP25ExecuteRelayCall/LSP25Errors.sol | 12 +- .../LSP25MultiChannelNonce.sol | 111 ++++++++----- contracts/LSP6KeyManager/LSP6Errors.sol | 20 +-- .../LSP6KeyManager/LSP6KeyManagerCore.sol | 54 ++++-- .../Mocks/LSP25MultiChannelNonceTester.sol | 46 ++++++ .../LSP25MultiChannelNonce.md | 95 ++++++++--- .../LSP6KeyManager/LSP6KeyManager.md | 141 ++++++++++++---- package.json | 1 + .../LSP25MultiChannelNonce.test.ts | 154 ++++++++++++++++++ .../Relay/ExecuteRelayCall.test.ts | 4 +- 12 files changed, 512 insertions(+), 130 deletions(-) create mode 100644 contracts/Mocks/LSP25MultiChannelNonceTester.sol create mode 100644 tests/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.test.ts diff --git a/.gitignore b/.gitignore index 2ff0a6950..9b491c2bd 100644 --- a/.gitignore +++ b/.gitignore @@ -142,4 +142,4 @@ benchmark.md /userdocs # test temporary folder -/.test \ No newline at end of file +/.test diff --git a/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol b/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol index b75e053c7..5c7b2b72b 100644 --- a/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol +++ b/contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol @@ -5,7 +5,7 @@ interface ILSP25ExecuteRelayCall { /** * @notice Reading the latest nonce of address `from` in the channel ID `channelId`. * - * @dev Get the nonce for a specific controller `from` address that can be used for signing relay transactions via {executeRelayCall}. + * @dev Get the nonce for a specific `from` address that can be used for signing relay transactions via {executeRelayCall}. * * @param from The address of the signer of the transaction. * @param channelId The channel id that the signer wants to use for executing the transaction. diff --git a/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol b/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol index 01f249cbc..bac4b0946 100644 --- a/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol +++ b/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol @@ -2,16 +2,10 @@ pragma solidity ^0.8.4; /** - * @dev Reverts when the `signer` address retrieved from the `signature` has an invalid nonce: `invalidNonce`. + * @notice Relay call not valid yet. * - * @param signer The address of the signer - * @param invalidNonce The nonce retrieved for the `signer` address - * @param signature The signature used to retrieve the `signer` address - */ -error InvalidRelayNonce(address signer, uint256 invalidNonce, bytes signature); - -/** - * @dev Reverts when the start timestamp provided to {executeRelayCall} function is bigger than the current timestamp. + * @dev Reverts when the relay call is cannot yet bet executed. + * This mean that the starting timestamp provided to {executeRelayCall} function is bigger than the current timestamp. */ error RelayCallBeforeStartTime(); diff --git a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol index be23f67f2..7b1c98b0f 100644 --- a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol +++ b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol @@ -8,11 +8,7 @@ import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import {LSP25_VERSION} from "./LSP25Constants.sol"; // errors -import { - InvalidRelayNonce, - RelayCallBeforeStartTime, - RelayCallExpired -} from "./LSP25Errors.sol"; +import {RelayCallBeforeStartTime, RelayCallExpired} from "./LSP25Errors.sol"; /** * @title Implementation of the multi channel nonce and the signature verification defined in the LSP25 standard. @@ -31,29 +27,59 @@ abstract contract LSP25MultiChannelNonce { mapping(address => mapping(uint256 => uint256)) internal _nonceStore; /** - * @dev Validate that the `nonce` given for the `signature` signed and the `payload` to execute is valid - * and conform to the signature format according to the LSP25 standard. + * @dev Read the nonce for a `from` address on a specific `channelId`. + * This will return an `idx`, which is the concatenation of two `uint128` as follow: + * 1. the `channelId` where the nonce was queried for. + * 2. the actual nonce of the given `channelId`. * - * @param signature A valid signature for a signer, generated according to the signature format specified in the LSP25 standard. - * @param nonce The nonce that the signer used to generate the `signature`. - * @param validityTimestamps Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, - * and the right-most `uint128` represents the timestamp after which the transaction expire. - * @param callData The abi-encoded function call to execute. + * For example, if on `channelId` number `5`, the latest nonce is `1`, the `idx` returned by this function will be: + * + * ``` + * // in decimals = 1701411834604692317316873037158841057281 + * idx = 0x0000000000000000000000000000000500000000000000000000000000000001 + * ``` * - * @return recoveredSignerAddress The address of the signer recovered, for which the signature was validated. + * This idx can be described as follow: * - * @custom:warning Be aware that this function can also throw an error if the `callData` was signed incorrectly (not conforming to the signature format defined in the LSP25 standard). - * The contract cannot distinguish if the data is signed correctly or not. Instead, it will recover an incorrect signer address from the signature - * and throw an {InvalidRelayNonce} error with the incorrect signer address as the first parameter. + * ``` + * channelId => 5 nonce in this channel => 1 + * v------------------------------v-------------------------------v + * 0x0000000000000000000000000000000500000000000000000000000000000001 + * ``` + * + * @param from The address to read the nonce for. + * @param channelId The channel in which to extract the nonce. + * + * @return idx The idx composed of two `uint128`: the channelId + nonce in channel concatenated together in a single `uint256` value. */ - function _validateExecuteRelayCall( + function _getNonce( + address from, + uint128 channelId + ) internal view virtual returns (uint256 idx) { + uint256 nonceInChannel = _nonceStore[from][channelId]; + return (uint256(channelId) << 128) | nonceInChannel; + } + + /** + * @dev Recover the address of the signer that generated a `signature` using the parameters provided `nonce`, `validityTimestamps`, `msgValue` and `callData`. + * The address of the signer will be recovered using the LSP25 signature format. + * + * @param signature A 65 bytes long signature generated according to the signature format specified in the LSP25 standard. + * @param nonce The nonce that the signer used to generate the `signature`. + * @param validityTimestamps The validity timestamp that the signer used to generate the signature (See {_verifyValidityTimestamps} to learn more). + * @param msgValue The amount of native tokens intended to be sent for the relay transaction. + * @param callData The calldata to execute as a relay transaction that the signer signed for. + * + * @return The address that signed, recovered from the `signature`. + */ + function _recoverSignerFromLSP25Signature( bytes memory signature, uint256 nonce, uint256 validityTimestamps, uint256 msgValue, bytes calldata callData - ) internal returns (address recoveredSignerAddress) { - bytes memory encodedMessage = abi.encodePacked( + ) internal view returns (address) { + bytes memory lsp25EncodedMessage = abi.encodePacked( LSP25_VERSION, block.chainid, nonce, @@ -62,31 +88,36 @@ abstract contract LSP25MultiChannelNonce { callData ); - recoveredSignerAddress = address(this) - .toDataWithIntendedValidatorHash(encodedMessage) - .recover(signature); + bytes32 eip191Hash = address(this).toDataWithIntendedValidatorHash( + lsp25EncodedMessage + ); - if (!_isValidNonce(recoveredSignerAddress, nonce)) { - revert InvalidRelayNonce(recoveredSignerAddress, nonce, signature); - } + return eip191Hash.recover(signature); + } - // increase nonce after successful verification - _nonceStore[recoveredSignerAddress][nonce >> 128]++; + /** + * @notice Verifying if the current timestamp is within the date and time range provided by `validityTimestamps`. + * + * @dev Verify that the validity timestamp provided is within a valid range compared to the current timestamp. + * + * @param validityTimestamps Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, + * and the right-most `uint128` represents the timestamp after which the transaction expire. + */ + function _verifyValidityTimestamps( + uint256 validityTimestamps + ) internal view { + if (validityTimestamps == 0) return; - if (validityTimestamps != 0) { - uint128 startingTimestamp = uint128(validityTimestamps >> 128); - uint128 endingTimestamp = uint128(validityTimestamps); + uint128 startingTimestamp = uint128(validityTimestamps >> 128); + uint128 endingTimestamp = uint128(validityTimestamps); - // solhint-disable not-rely-on-time - if (block.timestamp < startingTimestamp) { - revert RelayCallBeforeStartTime(); - } - if (block.timestamp > endingTimestamp) { - revert RelayCallExpired(); - } + // solhint-disable not-rely-on-time + if (block.timestamp < startingTimestamp) { + revert RelayCallBeforeStartTime(); + } + if (block.timestamp > endingTimestamp) { + revert RelayCallExpired(); } - - return recoveredSignerAddress; } /** @@ -109,6 +140,6 @@ abstract contract LSP25MultiChannelNonce { // Alternatively: // uint256 mask = (1<<128)-1; // uint256 mask = 0xffffffffffffffffffffffffffffffff; - return (idx & mask) == (_nonceStore[from][idx >> 128]); + return (idx & mask) == _nonceStore[from][idx >> 128]; } } diff --git a/contracts/LSP6KeyManager/LSP6Errors.sol b/contracts/LSP6KeyManager/LSP6Errors.sol index 4f1e057b8..9b1c4b8b7 100644 --- a/contracts/LSP6KeyManager/LSP6Errors.sol +++ b/contracts/LSP6KeyManager/LSP6Errors.sol @@ -59,9 +59,9 @@ error InvalidLSP6Target(); * * @dev Reverts when the `signer` address retrieved from the `signature` has an invalid nonce: `invalidNonce`. * - * @param signer The address of the signer - * @param invalidNonce The nonce retrieved for the `signer` address - * @param signature The signature used to retrieve the `signer` address + * @param signer The address of the signer. + * @param invalidNonce The nonce retrieved for the `signer` address. + * @param signature The signature used to retrieve the `signer` address. */ error InvalidRelayNonce(address signer, uint256 invalidNonce, bytes signature); @@ -227,20 +227,6 @@ error CannotSendValueToSetData(); */ error CallingKeyManagerNotAllowed(); -/** - * @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(); - /** * @dev reverts when the address of the Key Manager is being set as extensions for lsp20 functions */ diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index 6f94ae9b6..12ddccf07 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -41,6 +41,7 @@ import { LSP6BatchExcessiveValueSent, LSP6BatchInsufficientValueSent, InvalidPayload, + InvalidRelayNonce, NoPermissionsSet, InvalidERC725Function, CannotSendValueToSetData @@ -113,15 +114,22 @@ abstract contract LSP6KeyManagerCore is /** * @inheritdoc ILSP25 * - * @custom:info A signer can choose its channel number arbitrarily. Channel ID = 0 can be used for sequential nonces (transactions - * that are order dependant), any other channel ID for out-of-order execution (= execution in parallel). + * @custom:hint A signer can choose its channel number arbitrarily. The recommended practice is to: + * - use `channelId == 0` for transactions for which the ordering of execution matters.abi + * + * _Example: you have two transactions A and B, and transaction A must be executed first and complete successfully before + * transaction B should be executed)._ + * + * - use any other `channelId` number for transactions that you want to be order independant (out-of-order execution, execution _"in parallel"_). + * + * _Example: you have two transactions A and B. You want transaction B to be executed a) without having to wait for transaction A to complete, + * or b) regardless if transaction A completed successfully or not. */ function getNonce( address from, uint128 channelId ) public view virtual returns (uint256) { - uint256 nonceInChannel = _nonceStore[from][channelId]; - return (uint256(channelId) << 128) | nonceInChannel; + return LSP25MultiChannelNonce._getNonce(from, channelId); } /** @@ -386,6 +394,20 @@ abstract contract LSP6KeyManagerCore is return result; } + /** + * @dev Validate that the `nonce` given for the `signature` signed and the `payload` to execute is valid + * and conform to the signature format according to the LSP25 standard. + * + * @param signature A valid signature for a signer, generated according to the signature format specified in the LSP25 standard. + * @param nonce The nonce that the signer used to generate the `signature`. + * @param validityTimestamps Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, + * and the right-most `uint128` represents the timestamp after which the transaction expire. + * @param payload The abi-encoded function call to execute. + * + * @custom:warning Be aware that this function can also throw an error if the `callData` was signed incorrectly (not conforming to the signature format defined in the LSP25 standard). + * This is because the contract cannot distinguish if the data is signed correctly or not. Instead, it will recover an incorrect signer address from the signature + * and throw an {InvalidRelayNonce} error with the incorrect signer address as the first parameter. + */ function _executeRelayCall( bytes memory signature, uint256 nonce, @@ -397,13 +419,23 @@ abstract contract LSP6KeyManagerCore is revert InvalidPayload(payload); } - address signer = LSP25MultiChannelNonce._validateExecuteRelayCall( - signature, - nonce, - validityTimestamps, - msgValue, - payload - ); + address signer = LSP25MultiChannelNonce + ._recoverSignerFromLSP25Signature( + signature, + nonce, + validityTimestamps, + msgValue, + payload + ); + + if (!_isValidNonce(signer, nonce)) { + revert InvalidRelayNonce(signer, nonce, signature); + } + + // increase nonce after successful verification + _nonceStore[signer][nonce >> 128]++; + + LSP25MultiChannelNonce._verifyValidityTimestamps(validityTimestamps); bool isSetData = false; if ( diff --git a/contracts/Mocks/LSP25MultiChannelNonceTester.sol b/contracts/Mocks/LSP25MultiChannelNonceTester.sol new file mode 100644 index 000000000..c825ea946 --- /dev/null +++ b/contracts/Mocks/LSP25MultiChannelNonceTester.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.4; + +import { + LSP25MultiChannelNonce +} from "../LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol"; + +/** + * @dev This contract is used only for testing the internal functions. + */ +contract LSP25MultiChannelNonceTester is LSP25MultiChannelNonce { + function getNonce( + address from, + uint128 channelId + ) public view returns (uint256 idx) { + return _getNonce(from, channelId); + } + + function recoverSignerFromLSP25Signature( + bytes memory signature, + uint256 nonce, + uint256 validityTimestamps, + uint256 msgValue, + bytes calldata callData + ) public view returns (address) { + return + _recoverSignerFromLSP25Signature( + signature, + nonce, + validityTimestamps, + msgValue, + callData + ); + } + + function verifyValidityTimestamps(uint256 validityTimestamps) public view { + return _verifyValidityTimestamps(validityTimestamps); + } + + function isValidNonce( + address from, + uint256 nonce + ) public view returns (bool) { + return _isValidNonce(from, nonce); + } +} diff --git a/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md b/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md index 91b7f9306..51f323d8c 100644 --- a/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md +++ b/docs/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.md @@ -24,44 +24,99 @@ 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. -### \_validateExecuteRelayCall +### \_getNonce -:::caution Warning +```solidity +function _getNonce( + address from, + uint128 channelId +) internal view returns (uint256 idx); +``` -Be aware that this function can also throw an error if the `callData` was signed incorrectly (not conforming to the signature format defined in the LSP25 standard). -The contract cannot distinguish if the data is signed correctly or not. Instead, it will recover an incorrect signer address from the signature -and throw an {InvalidRelayNonce} error with the incorrect signer address as the first parameter. +Read the nonce for a `from` address on a specific `channelId`. +This will return an `idx`, which is the concatenation of two `uint128` as follow: -::: +1. the `channelId` where the nonce was queried for. + +2. the actual nonce of the given `channelId`. + For example, if on `channelId` number `5`, the latest nonce is `1`, the `idx` returned by this function will be: + +``` +// in decimals = 1701411834604692317316873037158841057281 +idx = 0x0000000000000000000000000000000500000000000000000000000000000001 +``` + +This idx can be described as follow: + +``` + channelId => 5 nonce in this channel => 1 + v------------------------------v-------------------------------v +0x0000000000000000000000000000000500000000000000000000000000000001 +``` + +#### Parameters + +| Name | Type | Description | +| ----------- | :-------: | ------------------------------------------ | +| `from` | `address` | The address to read the nonce for. | +| `channelId` | `uint128` | The channel in which to extract the nonce. | + +#### Returns + +| Name | Type | Description | +| ----- | :-------: | ---------------------------------------------------------------------------------------------------------------------- | +| `idx` | `uint256` | The idx composed of two `uint128`: the channelId + nonce in channel concatenated together in a single `uint256` value. | + +
+ +### \_recoverSignerFromLSP25Signature ```solidity -function _validateExecuteRelayCall( +function _recoverSignerFromLSP25Signature( bytes signature, uint256 nonce, uint256 validityTimestamps, uint256 msgValue, bytes callData -) internal nonpayable returns (address recoveredSignerAddress); +) internal view returns (address); ``` -Validate that the `nonce` given for the `signature` signed and the `payload` to execute is valid -and conform to the signature format according to the LSP25 standard. +Recover the address of the signer that generated a `signature` using the parameters provided `nonce`, `validityTimestamps`, `msgValue` and `callData`. +The address of the signer will be recovered using the LSP25 signature format. #### Parameters -| Name | Type | Description | -| -------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | -| `signature` | `bytes` | A valid signature for a signer, generated according to the signature format specified in the LSP25 standard. | -| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | -| `validityTimestamps` | `uint256` | Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, | -| `msgValue` | `uint256` | - | -| `callData` | `bytes` | The abi-encoded function call to execute. | +| Name | Type | Description | +| -------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------- | +| `signature` | `bytes` | A 65 bytes long signature generated according to the signature format specified in the LSP25 standard. | +| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | +| `validityTimestamps` | `uint256` | The validity timestamp that the signer used to generate the signature (See {\_verifyValidityTimestamps} to learn more). | +| `msgValue` | `uint256` | The amount of native tokens intended to be sent for the relay transaction. | +| `callData` | `bytes` | The calldata to execute as a relay transaction that the signer signed for. | #### Returns -| Name | Type | Description | -| ------------------------ | :-------: | --------------------------------------------------------------------------- | -| `recoveredSignerAddress` | `address` | The address of the signer recovered, for which the signature was validated. | +| Name | Type | Description | +| ---- | :-------: | -------------------------------------------------------- | +| `0` | `address` | The address that signed, recovered from the `signature`. | + +
+ +### \_verifyValidityTimestamps + +```solidity +function _verifyValidityTimestamps(uint256 validityTimestamps) internal view; +``` + +_Verifying if the current timestamp is within the date and time range provided by `validityTimestamps`._ + +Verify that the validity timestamp provided is within a valid range compared to the current timestamp. + +#### Parameters + +| Name | Type | Description | +| -------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | +| `validityTimestamps` | `uint256` | Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, |
diff --git a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md index 72cdb3bef..a4f0384b8 100644 --- a/docs/contracts/LSP6KeyManager/LSP6KeyManager.md +++ b/docs/contracts/LSP6KeyManager/LSP6KeyManager.md @@ -256,9 +256,12 @@ Same as [`executeRelayCall`](#executerelaycall) but execute a batch of signed ca ::: -:::info +:::tip Hint + +A signer can choose its channel number arbitrarily. The recommended practice is to: -A signer can choose its channel number arbitrarily. Channel ID = 0 can be used for sequential nonces (transactions that are order dependant), any other channel ID for out-of-order execution (= execution in parallel). +- use `channelId == 0` for transactions for which the ordering of execution matters.abi _Example: you have two transactions A and B, and transaction A must be executed first and complete successfully before transaction B should be executed)._ +- use any other `channelId` number for transactions that you want to be order independant (out-of-order execution, execution _"in parallel"_). \_Example: you have two transactions A and B. You want transaction B to be executed a) without having to wait for transaction A to complete, or b) regardless if transaction A completed successfully or not. ::: @@ -271,7 +274,7 @@ function getNonce( _Reading the latest nonce of address `from` in the channel ID `channelId`._ -Get the nonce for a specific controller `from` address that can be used for signing relay transactions via [`executeRelayCall`](#executerelaycall). +Get the nonce for a specific `from` address that can be used for signing relay transactions via [`executeRelayCall`](#executerelaycall). #### Parameters @@ -937,44 +940,99 @@ function _verifyOwnershipPermissions(
-### \_validateExecuteRelayCall +### \_getNonce -:::caution Warning +```solidity +function _getNonce( + address from, + uint128 channelId +) internal view returns (uint256 idx); +``` -Be aware that this function can also throw an error if the `callData` was signed incorrectly (not conforming to the signature format defined in the LSP25 standard). -The contract cannot distinguish if the data is signed correctly or not. Instead, it will recover an incorrect signer address from the signature -and throw an {InvalidRelayNonce} error with the incorrect signer address as the first parameter. +Read the nonce for a `from` address on a specific `channelId`. +This will return an `idx`, which is the concatenation of two `uint128` as follow: -::: +1. the `channelId` where the nonce was queried for. + +2. the actual nonce of the given `channelId`. + For example, if on `channelId` number `5`, the latest nonce is `1`, the `idx` returned by this function will be: + +``` +// in decimals = 1701411834604692317316873037158841057281 +idx = 0x0000000000000000000000000000000500000000000000000000000000000001 +``` + +This idx can be described as follow: + +``` + channelId => 5 nonce in this channel => 1 + v------------------------------v-------------------------------v +0x0000000000000000000000000000000500000000000000000000000000000001 +``` + +#### Parameters + +| Name | Type | Description | +| ----------- | :-------: | ------------------------------------------ | +| `from` | `address` | The address to read the nonce for. | +| `channelId` | `uint128` | The channel in which to extract the nonce. | + +#### Returns + +| Name | Type | Description | +| ----- | :-------: | ---------------------------------------------------------------------------------------------------------------------- | +| `idx` | `uint256` | The idx composed of two `uint128`: the channelId + nonce in channel concatenated together in a single `uint256` value. | + +
+ +### \_recoverSignerFromLSP25Signature ```solidity -function _validateExecuteRelayCall( +function _recoverSignerFromLSP25Signature( bytes signature, uint256 nonce, uint256 validityTimestamps, uint256 msgValue, bytes callData -) internal nonpayable returns (address recoveredSignerAddress); +) internal view returns (address); ``` -Validate that the `nonce` given for the `signature` signed and the `payload` to execute is valid -and conform to the signature format according to the LSP25 standard. +Recover the address of the signer that generated a `signature` using the parameters provided `nonce`, `validityTimestamps`, `msgValue` and `callData`. +The address of the signer will be recovered using the LSP25 signature format. #### Parameters -| Name | Type | Description | -| -------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | -| `signature` | `bytes` | A valid signature for a signer, generated according to the signature format specified in the LSP25 standard. | -| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | -| `validityTimestamps` | `uint256` | Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, | -| `msgValue` | `uint256` | - | -| `callData` | `bytes` | The abi-encoded function call to execute. | +| Name | Type | Description | +| -------------------- | :-------: | ----------------------------------------------------------------------------------------------------------------------- | +| `signature` | `bytes` | A 65 bytes long signature generated according to the signature format specified in the LSP25 standard. | +| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | +| `validityTimestamps` | `uint256` | The validity timestamp that the signer used to generate the signature (See {\_verifyValidityTimestamps} to learn more). | +| `msgValue` | `uint256` | The amount of native tokens intended to be sent for the relay transaction. | +| `callData` | `bytes` | The calldata to execute as a relay transaction that the signer signed for. | #### Returns -| Name | Type | Description | -| ------------------------ | :-------: | --------------------------------------------------------------------------- | -| `recoveredSignerAddress` | `address` | The address of the signer recovered, for which the signature was validated. | +| Name | Type | Description | +| ---- | :-------: | -------------------------------------------------------- | +| `0` | `address` | The address that signed, recovered from the `signature`. | + +
+ +### \_verifyValidityTimestamps + +```solidity +function _verifyValidityTimestamps(uint256 validityTimestamps) internal view; +``` + +_Verifying if the current timestamp is within the date and time range provided by `validityTimestamps`._ + +Verify that the validity timestamp provided is within a valid range compared to the current timestamp. + +#### Parameters + +| Name | Type | Description | +| -------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | +| `validityTimestamps` | `uint256` | Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, |
@@ -1019,6 +1077,14 @@ function _execute( ### \_executeRelayCall +:::caution Warning + +Be aware that this function can also throw an error if the `callData` was signed incorrectly (not conforming to the signature format defined in the LSP25 standard). +This is because the contract cannot distinguish if the data is signed correctly or not. Instead, it will recover an incorrect signer address from the signature +and throw an {InvalidRelayNonce} error with the incorrect signer address as the first parameter. + +::: + ```solidity function _executeRelayCall( bytes signature, @@ -1029,6 +1095,19 @@ function _executeRelayCall( ) internal nonpayable returns (bytes); ``` +Validate that the `nonce` given for the `signature` signed and the `payload` to execute is valid +and conform to the signature format according to the LSP25 standard. + +#### Parameters + +| Name | Type | Description | +| -------------------- | :-------: | -------------------------------------------------------------------------------------------------------------------------------------- | +| `signature` | `bytes` | A valid signature for a signer, generated according to the signature format specified in the LSP25 standard. | +| `nonce` | `uint256` | The nonce that the signer used to generate the `signature`. | +| `validityTimestamps` | `uint256` | Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, | +| `msgValue` | `uint256` | - | +| `payload` | `bytes` | The abi-encoded function call to execute. | +
### \_executePayload @@ -1458,15 +1537,17 @@ Reverts when the payload is invalid. error InvalidRelayNonce(address signer, uint256 invalidNonce, bytes signature); ``` +_The relay call failed because an invalid nonce was provided for the address `signer` that signed the execute relay call. Invalid nonce: `invalidNonce`, signature of signer: `signature`._ + Reverts when the `signer` address retrieved from the `signature` has an invalid nonce: `invalidNonce`. #### Parameters -| Name | Type | Description | -| -------------- | :-------: | --------------------------------------------------- | -| `signer` | `address` | The address of the signer | -| `invalidNonce` | `uint256` | The nonce retrieved for the `signer` address | -| `signature` | `bytes` | The signature used to retrieve the `signer` address | +| Name | Type | Description | +| -------------- | :-------: | ---------------------------------------------------- | +| `signer` | `address` | The address of the signer. | +| `invalidNonce` | `uint256` | The nonce retrieved for the `signer` address. | +| `signature` | `bytes` | The signature used to retrieve the `signer` address. |
@@ -1784,7 +1865,9 @@ Reverts when `dataKey` is a `bytes32` value that does not adhere to any of the p error RelayCallBeforeStartTime(); ``` -Reverts when the start timestamp provided to [`executeRelayCall`](#executerelaycall) function is bigger than the current timestamp. +_Relay call not valid yet._ + +Reverts when the relay call is cannot yet bet executed. This mean that the starting timestamp provided to [`executeRelayCall`](#executerelaycall) function is bigger than the current timestamp.
diff --git a/package.json b/package.json index cf0245c3d..bcee054f5 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "test:lsp20": "hardhat test --no-compile tests/LSP20CallVerification/LSP6/LSP20WithLSP6.test.ts", "test:lsp20init": "hardhat test --no-compile tests/LSP20CallVerification/LSP6/LSP20WithLSP6Init.test.ts", "test:lsp23": "hardhat test --no-compile tests/LSP23LinkedContractsDeployment/LSP23LinkedContractsDeployment.test.ts", + "test:lsp25": "hardhat test --no-compile tests/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.test.ts", "test:universalfactory": "hardhat test --no-compile tests/LSP16UniversalFactory/LSP16UniversalFactory.test.ts", "test:reentrancy": "hardhat test --no-compile tests/Reentrancy/Reentrancy.test.ts", "test:reentrancyinit": "hardhat test --no-compile tests/Reentrancy/ReentrancyInit.test.ts", diff --git a/tests/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.test.ts b/tests/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.test.ts new file mode 100644 index 000000000..1d3b9c6cd --- /dev/null +++ b/tests/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.test.ts @@ -0,0 +1,154 @@ +import { ethers } from 'hardhat'; +import { expect } from 'chai'; +import { LSP25_VERSION } from '../../constants'; +import { LOCAL_PRIVATE_KEYS } from '../utils/helpers'; +import { EIP191Signer } from '@lukso/eip191-signer.js'; + +import { LSP25MultiChannelNonceTester, LSP25MultiChannelNonceTester__factory } from '../../types'; + +describe('LSP25MultiChannelNonce', () => { + let contract: LSP25MultiChannelNonceTester; + let account; + + const HARDHAT_CHAINID = 31337; + + const eip191Signer = new EIP191Signer(); + const signerPrivateKey = LOCAL_PRIVATE_KEYS.ACCOUNT0; + + before(async () => { + account = (await ethers.getSigners())[0]; + + contract = await new LSP25MultiChannelNonceTester__factory(account).deploy(); + }); + + describe('testing `_isValidNonce`', () => { + it('should return `true` when providing a valid nonce', async () => { + const nonce = await contract.getNonce(account.address, 0); + const result = await contract.isValidNonce(account.address, nonce); + expect(result).to.be.true; + }); + + it('should return `false` if the wrong nonce provided', async () => { + const nonce = await contract.getNonce(account.address, 0); + const invalidNonce = 35; + expect(nonce).to.not.equal(invalidNonce); // sanity check + + const result = await contract.isValidNonce(account.address, invalidNonce); + expect(result).to.be.false; + }); + }); + + describe('testing `_recoverSignerFromLSP25Signature`', () => { + it('should pass and recover the right address if the data was signed with LSP25 signature format', async () => { + const channelId = 0; + + const parameters = { + nonce: await contract.getNonce(account.address, channelId), + validityTimestamps: 0, + valueToSend: 0, + payload: '0xcafecafe', + }; + + const encodedMessage = ethers.utils.solidityPack( + ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], + [ + LSP25_VERSION, + HARDHAT_CHAINID, + parameters.nonce, + parameters.validityTimestamps, + parameters.valueToSend, + parameters.payload, + ], + ); + + const { signature } = await eip191Signer.signDataWithIntendedValidator( + contract.address, + encodedMessage, + signerPrivateKey, + ); + + const recoveredAddress = await contract.recoverSignerFromLSP25Signature( + signature, + parameters.nonce, + parameters.validityTimestamps, + parameters.valueToSend, + parameters.payload, + ); + expect(recoveredAddress).to.equal(account.address); + }); + + it('should return the wrong address if the data was signed with version different than 25', async () => { + const channelId = 0; + + const parameters = { + nonce: await contract.getNonce(account.address, channelId), + validityTimestamps: 0, + valueToSend: 0, + payload: '0xcafecafe', + }; + + const encodedMessage = ethers.utils.solidityPack( + ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], + [ + 12345, // incorrect version number + HARDHAT_CHAINID, + parameters.nonce, + parameters.validityTimestamps, + parameters.valueToSend, + parameters.payload, + ], + ); + + const { signature } = await eip191Signer.signDataWithIntendedValidator( + contract.address, + encodedMessage, + signerPrivateKey, + ); + + const recoveredAddress = await contract.recoverSignerFromLSP25Signature( + signature, + parameters.nonce, + parameters.validityTimestamps, + parameters.valueToSend, + parameters.payload, + ); + expect(recoveredAddress).to.not.equal(account.address); + }); + + it('should return the wrong address if the data was signed with an invalid nonce', async () => { + const parameters = { + nonce: 12345, + validityTimestamps: 0, + valueToSend: 0, + payload: '0xcafecafe', + }; + + const encodedMessage = ethers.utils.solidityPack( + ['uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], + [ + 12345, // incorrect version number + HARDHAT_CHAINID, + parameters.nonce, + parameters.validityTimestamps, + parameters.valueToSend, + parameters.payload, + ], + ); + + const { signature } = await eip191Signer.signDataWithIntendedValidator( + contract.address, + encodedMessage, + signerPrivateKey, + ); + + const recoveredAddress = await contract.recoverSignerFromLSP25Signature( + signature, + parameters.nonce, + parameters.validityTimestamps, + parameters.valueToSend, + parameters.payload, + ); + expect(recoveredAddress).to.not.equal(account.address); + }); + }); +}); diff --git a/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts b/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts index 896c8468b..d1c8a0207 100644 --- a/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts +++ b/tests/LSP6KeyManager/Relay/ExecuteRelayCall.test.ts @@ -554,8 +554,8 @@ export const shouldBehaveLikeExecuteRelayCall = ( }); describe('When testing `validityTimestamps`', () => { - describe('(invalid timestamps) `startingTimestamp` is greter than `endingTimestamp`', () => { - describe('`now` is equal to `startingTimestamp` and `now` is greter than `endingTimestamp`', () => { + describe('(invalid timestamps) `startingTimestamp` is greater than `endingTimestamp`', () => { + describe('`now` is equal to `startingTimestamp` and `now` is greater than `endingTimestamp`', () => { it('reverts', async () => { const now = await time.latest(); const startingTimestamp = now; From f12617e2d192ee7e8d5e615cc373270948f2ea58 Mon Sep 17 00:00:00 2001 From: CJ42 Date: Tue, 22 Aug 2023 15:28:02 +0100 Subject: [PATCH 7/7] chore: add `@notice` tags + use import alias for LSP6 --- contracts/LSP25ExecuteRelayCall/LSP25Errors.sol | 2 ++ contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol | 4 +--- contracts/LSP6KeyManager/LSP6KeyManagerCore.sol | 4 ++-- dodoc/config.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol b/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol index bac4b0946..b15e72fa8 100644 --- a/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol +++ b/contracts/LSP25ExecuteRelayCall/LSP25Errors.sol @@ -10,6 +10,8 @@ pragma solidity ^0.8.4; error RelayCallBeforeStartTime(); /** + * @notice Relay call expired (deadline passed). + * * @dev Reverts when the period to execute the relay call has expired. */ error RelayCallExpired(); diff --git a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol index 7b1c98b0f..ff90206f1 100644 --- a/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol +++ b/contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol @@ -96,9 +96,7 @@ abstract contract LSP25MultiChannelNonce { } /** - * @notice Verifying if the current timestamp is within the date and time range provided by `validityTimestamps`. - * - * @dev Verify that the validity timestamp provided is within a valid range compared to the current timestamp. + * @dev Verify that the current timestamp is within the date and time range provided by `validityTimestamps`. * * @param validityTimestamps Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed, * and the right-most `uint128` represents the timestamp after which the transaction expire. diff --git a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol index 12ddccf07..390a7079c 100644 --- a/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol +++ b/contracts/LSP6KeyManager/LSP6KeyManagerCore.sol @@ -9,7 +9,7 @@ import { import { IERC725Y } from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol"; -import {ILSP6KeyManager} from "./ILSP6KeyManager.sol"; +import {ILSP6KeyManager as ILSP6} from "./ILSP6KeyManager.sol"; import { ILSP20CallVerifier as ILSP20 } from "../LSP20CallVerification/ILSP20CallVerifier.sol"; @@ -72,7 +72,7 @@ import {_INTERFACEID_LSP25} from "../LSP25ExecuteRelayCall/LSP25Constants.sol"; */ abstract contract LSP6KeyManagerCore is ERC165, - ILSP6KeyManager, + ILSP6, ILSP20, ILSP25, LSP6SetDataModule, diff --git a/dodoc/config.ts b/dodoc/config.ts index 7890b808f..2823789aa 100644 --- a/dodoc/config.ts +++ b/dodoc/config.ts @@ -17,6 +17,7 @@ export const dodocConfig = { 'contracts/LSP17ContractExtension/LSP17Extension.sol', 'contracts/LSP20CallVerification/LSP20CallVerification.sol', 'contracts/LSP23LinkedContractsDeployment/LSP23LinkedContractsFactory.sol', + 'contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol', // tokens 'contracts/LSP4DigitalAssetMetadata/LSP4Compatibility.sol', @@ -34,7 +35,6 @@ export const dodocConfig = { 'contracts/LSP8IdentifiableDigitalAsset/extensions/LSP8Enumerable.sol', 'contracts/LSP8IdentifiableDigitalAsset/presets/LSP8CompatibleERC721Mintable.sol', 'contracts/LSP8IdentifiableDigitalAsset/presets/LSP8Mintable.sol', - 'contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol', // libraries -------------------- 'contracts/LSP0ERC725Account/LSP0Utils.sol',