Skip to content

Commit

Permalink
feat!: add first implementation of LSP25 Execute Relay Call + update …
Browse files Browse the repository at this point in the history
…LSP6 interface ID (#678)

* docs: update Natspec comments for LSP6 + add auto-generated doc

* feat: create LSP25 interface + implementation for multi channel nonces

* refactor!: update LSP6 interface ID + add `LSP25MultiChannelNonce` in LSP6 Core implementation

* test: update tests for `executeRelayCall` (single + batch)

* docs: update LSP6 docs + add LSP25 docs

* test: add tests for LSP25

* chore: add `@notice` tags + use import alias for LSP6
  • Loading branch information
CJ42 authored Aug 23, 2023
1 parent 28f19b3 commit 9f8e234
Show file tree
Hide file tree
Showing 32 changed files with 971 additions and 296 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,4 @@ benchmark.md
/userdocs

# test temporary folder
/.test
/.test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import {
ERC725YDataKeys,
PERMISSIONS,
ALL_PERMISSIONS,
LSP25_VERSION,
ErrorSelectors,
EventSigHashes,
FunctionSelectors,
Expand Down
18 changes: 11 additions & 7 deletions constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const INTERFACE_IDS = {
ERC725Y: '0x629aa694',
LSP0ERC725Account: '0x24871b3d',
LSP1UniversalReceiver: '0x6bb56a14',
LSP6KeyManager: '0x38bb3cdb',
LSP6KeyManager: '0x627ca5d3',
LSP7DigitalAsset: '0xda1f85e4',
LSP8IdentifiableDigitalAsset: '0x622e7a01',
LSP9Vault: '0x28af17e6',
Expand All @@ -36,6 +36,7 @@ export const INTERFACE_IDS = {
LSP17Extension: '0xcee78b40',
LSP20CallVerification: '0x1a0eb6a5',
LSP20CallVerifier: '0x480c0ec2',
LSP25ExecuteRelayCall: '0x5ac79908',
};

// ERC1271
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
68 changes: 68 additions & 0 deletions contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.0;

interface ILSP25ExecuteRelayCall {
/**
* @notice Reading the latest nonce of address `from` in the channel ID `channelId`.
*
* @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.
*
* @return The current nonce on a specific `channelId`.
*/
function getNonce(
address from,
uint128 channelId
) external view returns (uint256);

/**
* @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), 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 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.
*
* @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,
uint256 nonce,
uint256 validityTimestamps,
bytes calldata payload
) external payable returns (bytes memory);

/**
* @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.
*
* @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.
*
* @return An array of abi-decoded data returned by the functions executed.
*/
function executeRelayCallBatch(
bytes[] calldata signatures,
uint256[] calldata nonces,
uint256[] calldata validityTimestamps,
uint256[] calldata values,
bytes[] calldata payloads
) external payable returns (bytes[] memory);
}
8 changes: 8 additions & 0 deletions contracts/LSP25ExecuteRelayCall/LSP25Constants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +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;
17 changes: 17 additions & 0 deletions contracts/LSP25ExecuteRelayCall/LSP25Errors.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

/**
* @notice Relay call not valid yet.
*
* @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();

/**
* @notice Relay call expired (deadline passed).
*
* @dev Reverts when the period to execute the relay call has expired.
*/
error RelayCallExpired();
143 changes: 143 additions & 0 deletions contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.4;

// libraries
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

// constants
import {LSP25_VERSION} from "./LSP25Constants.sol";

// errors
import {RelayCallBeforeStartTime, 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;

/**
* @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`.
*
* 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
* ```
*
* @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 _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 view returns (address) {
bytes memory lsp25EncodedMessage = abi.encodePacked(
LSP25_VERSION,
block.chainid,
nonce,
validityTimestamps,
msgValue,
callData
);

bytes32 eip191Hash = address(this).toDataWithIntendedValidatorHash(
lsp25EncodedMessage
);

return eip191Hash.recover(signature);
}

/**
* @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.
*/
function _verifyValidityTimestamps(
uint256 validityTimestamps
) internal view {
if (validityTimestamps == 0) return;

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();
}
}

/**
* @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,
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];
}
}
59 changes: 0 additions & 59 deletions contracts/LSP6KeyManager/ILSP6KeyManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,6 @@ interface ILSP6KeyManager is
*/
function target() external view returns (address);

/**
* @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.
*
* @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 Executing the following payload on the linked contract: `payload`
*
Expand Down Expand Up @@ -75,48 +60,4 @@ interface ILSP6KeyManager is
uint256[] calldata values,
bytes[] calldata payloads
) external payable returns (bytes[] memory);

/**
* @notice Executing a relay call (= meta-transaction).
*
* @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);

/**
* @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 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}.
*
* @return An array of abi-decoded 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);
}
4 changes: 1 addition & 3 deletions contracts/LSP6KeyManager/LSP6Constants.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.4;

uint256 constant LSP6_VERSION = 6;

// --- ERC165 interface ids
bytes4 constant _INTERFACEID_LSP6 = 0x38bb3cdb;
bytes4 constant _INTERFACEID_LSP6 = 0x627ca5d3;

// --- ERC725Y Data Keys

Expand Down
Loading

0 comments on commit 9f8e234

Please sign in to comment.