-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: add first implementation of LSP25 Execute Relay Call + update …
…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
Showing
32 changed files
with
971 additions
and
296 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -142,4 +142,4 @@ benchmark.md | |
/userdocs | ||
|
||
# test temporary folder | ||
/.test | ||
/.test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
143
contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.