Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add first implementation of LSP25 Execute Relay Call + update LSP6 interface ID #678

Merged
merged 9 commits into from
Aug 23, 2023
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();
CJ42 marked this conversation as resolved.
Show resolved Hide resolved
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();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spoke to @YamenMerhi earlier about that but I believe it would make more sense that we consider uint128(0) to be the undefined value.
Meaning if I do not want to mention a startingTimestamp or a endingTimestamp we should just leave this value empty.
because right now if we want both to be empty we just leave them both empty (which makes sense) and it will return on line 107
however if I only want to have a startingDate I will need to give a value to the endingDate. A "random" value or bytes(f) but why?
For UX reasons I think I would delibarately leave the values are initialized. More logic. Specially with our logic on line 107.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meaning if I do not want to mention a startingTimestamp or a endingTimestamp we should just leave this value empty.

Agree here, such value for a validityTimestamp should work:

image
// 64E69D80 -> valid from 24th August 2023
0x00000000000000000000000064E69D800000000000000000000000000000000


/**
* @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
Loading