@lukso/lsp25-contracts 0.15.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/contracts/ILSP25ExecuteRelayCall.sol +68 -0
- package/contracts/LSP25Constants.sol +8 -0
- package/contracts/LSP25Errors.sol +17 -0
- package/contracts/LSP25MultiChannelNonce.sol +144 -0
- package/dist/index.cjs +7 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.mjs +4 -0
- package/package.json +49 -0
package/README.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
# LSP25 Execute Relay Call · [](https://www.npmjs.com/package/@lukso/lsp25-contracts)
|
2
|
+
|
3
|
+
Package for the LSP25 Execute Relay Call standard.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```bash
|
8
|
+
npm i @lukso/lsp25-contracts
|
9
|
+
```
|
@@ -0,0 +1,68 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache 2.0
|
2
|
+
pragma solidity ^0.8.0;
|
3
|
+
|
4
|
+
interface ILSP25ExecuteRelayCall {
|
5
|
+
/**
|
6
|
+
* @notice Reading the latest nonce of address `from` in the channel ID `channelId`.
|
7
|
+
*
|
8
|
+
* @dev Get the nonce for a specific `from` address that can be used for signing relay transactions via {executeRelayCall}.
|
9
|
+
*
|
10
|
+
* @param from The address of the signer of the transaction.
|
11
|
+
* @param channelId The channel id that the signer wants to use for executing the transaction.
|
12
|
+
*
|
13
|
+
* @return The current nonce on a specific `channelId`.
|
14
|
+
*/
|
15
|
+
function getNonce(
|
16
|
+
address from,
|
17
|
+
uint128 channelId
|
18
|
+
) external view returns (uint256);
|
19
|
+
|
20
|
+
/**
|
21
|
+
* @notice Executing the following payload given the nonce `nonce` and signature `signature`. Payload: `payload`
|
22
|
+
*
|
23
|
+
* @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.
|
24
|
+
* The signature MUST be generated according to the signature format defined by the LSP25 standard.
|
25
|
+
*
|
26
|
+
* @param signature A 65 bytes long signature for a meta transaction according to LSP25.
|
27
|
+
* @param nonce The nonce of the address that signed the calldata (in a specific `_channel`), obtained via {getNonce}. Used to prevent replay attack.
|
28
|
+
* @param validityTimestamps Two `uint128` timestamps concatenated together that describes
|
29
|
+
* when the relay transaction is valid "from" (left `uint128`) and "until" as a deadline (right `uint128`).
|
30
|
+
* @param payload The abi-encoded function call to execute.
|
31
|
+
*
|
32
|
+
* @return The data being returned by the function executed.
|
33
|
+
*
|
34
|
+
* @custom:requirements
|
35
|
+
* - `nonce` MUST be a valid nonce nonce provided (see {getNonce} function).
|
36
|
+
* - The transaction MUST be submitted within a valid time period defined by the `validityTimestamp`.
|
37
|
+
*
|
38
|
+
* @custom:hint You can use `validityTimestamps == 0` to define an `executeRelayCall` transaction that is indefinitely valid,
|
39
|
+
* meaning that does not require to start from a specific date/time, or that has an expiration date/time.
|
40
|
+
*/
|
41
|
+
function executeRelayCall(
|
42
|
+
bytes calldata signature,
|
43
|
+
uint256 nonce,
|
44
|
+
uint256 validityTimestamps,
|
45
|
+
bytes calldata payload
|
46
|
+
) external payable returns (bytes memory);
|
47
|
+
|
48
|
+
/**
|
49
|
+
* @notice Executing a batch of relay calls (= meta-transactions).
|
50
|
+
*
|
51
|
+
* @dev Same as {executeRelayCall} but execute a batch of signed calldata payloads (abi-encoded function calls) in a single transaction.
|
52
|
+
*
|
53
|
+
* @param signatures An array of 65 bytes long signatures for meta transactions according to LSP25.
|
54
|
+
* @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.
|
55
|
+
* @param validityTimestamps An array of two `uint128` concatenated timestamps that describe when the relay transaction is valid "from" (left `uint128`) and "until" (right `uint128`).
|
56
|
+
* @param values An array of amount of native tokens to be transferred for each calldata `payload`.
|
57
|
+
* @param payloads An array of abi-encoded function calls to be executed successively.
|
58
|
+
*
|
59
|
+
* @return An array of abi-decoded data returned by the functions executed.
|
60
|
+
*/
|
61
|
+
function executeRelayCallBatch(
|
62
|
+
bytes[] calldata signatures,
|
63
|
+
uint256[] calldata nonces,
|
64
|
+
uint256[] calldata validityTimestamps,
|
65
|
+
uint256[] calldata values,
|
66
|
+
bytes[] calldata payloads
|
67
|
+
) external payable returns (bytes[] memory);
|
68
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.4;
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @notice Relay call not valid yet.
|
6
|
+
*
|
7
|
+
* @dev Reverts when the relay call is cannot yet bet executed.
|
8
|
+
* This mean that the starting timestamp provided to {executeRelayCall} function is bigger than the current timestamp.
|
9
|
+
*/
|
10
|
+
error RelayCallBeforeStartTime();
|
11
|
+
|
12
|
+
/**
|
13
|
+
* @notice Relay call expired (deadline passed).
|
14
|
+
*
|
15
|
+
* @dev Reverts when the period to execute the relay call has expired.
|
16
|
+
*/
|
17
|
+
error RelayCallExpired();
|
@@ -0,0 +1,144 @@
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
2
|
+
pragma solidity ^0.8.4;
|
3
|
+
|
4
|
+
// libraries
|
5
|
+
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
6
|
+
|
7
|
+
// constants
|
8
|
+
import {LSP25_VERSION} from "./LSP25Constants.sol";
|
9
|
+
|
10
|
+
// errors
|
11
|
+
import {RelayCallBeforeStartTime, RelayCallExpired} from "./LSP25Errors.sol";
|
12
|
+
|
13
|
+
/**
|
14
|
+
* @title Implementation of the multi channel nonce and the signature verification defined in the LSP25 standard.
|
15
|
+
* @author Jean Cavallera (CJ42)
|
16
|
+
* @dev This contract can be used as a backbone for other smart contracts to implement meta-transactions via the LSP25 Execute Relay Call interface.
|
17
|
+
*
|
18
|
+
* It contains a storage of nonces for signer addresses across various channel IDs, enabling these signers to submit signed transactions that order-independant.
|
19
|
+
* (transactions that do not need to be submitted one after the other in a specific order).
|
20
|
+
*
|
21
|
+
* Finally, it contains internal functions to verify signatures for specific calldata according the signature format specified in the LSP25 standard.
|
22
|
+
*/
|
23
|
+
abstract contract LSP25MultiChannelNonce {
|
24
|
+
using ECDSA for *;
|
25
|
+
|
26
|
+
// Mapping of signer -> channelId -> nonce in channel
|
27
|
+
mapping(address => mapping(uint256 => uint256)) internal _nonceStore;
|
28
|
+
|
29
|
+
/**
|
30
|
+
* @dev Read the nonce for a `from` address on a specific `channelId`.
|
31
|
+
* This will return an `idx`, which is the concatenation of two `uint128` as follow:
|
32
|
+
* 1. the `channelId` where the nonce was queried for.
|
33
|
+
* 2. the actual nonce of the given `channelId`.
|
34
|
+
*
|
35
|
+
* For example, if on `channelId` number `5`, the latest nonce is `1`, the `idx` returned by this function will be:
|
36
|
+
*
|
37
|
+
* ```
|
38
|
+
* // in decimals = 1701411834604692317316873037158841057281
|
39
|
+
* idx = 0x0000000000000000000000000000000500000000000000000000000000000001
|
40
|
+
* ```
|
41
|
+
*
|
42
|
+
* This idx can be described as follow:
|
43
|
+
*
|
44
|
+
* ```
|
45
|
+
* channelId => 5 nonce in this channel => 1
|
46
|
+
* v------------------------------v-------------------------------v
|
47
|
+
* 0x0000000000000000000000000000000500000000000000000000000000000001
|
48
|
+
* ```
|
49
|
+
*
|
50
|
+
* @param from The address to read the nonce for.
|
51
|
+
* @param channelId The channel in which to extract the nonce.
|
52
|
+
*
|
53
|
+
* @return idx The idx composed of two `uint128`: the channelId + nonce in channel concatenated together in a single `uint256` value.
|
54
|
+
*/
|
55
|
+
function _getNonce(
|
56
|
+
address from,
|
57
|
+
uint128 channelId
|
58
|
+
) internal view virtual returns (uint256 idx) {
|
59
|
+
return (uint256(channelId) << 128) | _nonceStore[from][channelId];
|
60
|
+
}
|
61
|
+
|
62
|
+
/**
|
63
|
+
* @dev Recover the address of the signer that generated a `signature` using the parameters provided `nonce`, `validityTimestamps`, `msgValue` and `callData`.
|
64
|
+
* The address of the signer will be recovered using the LSP25 signature format.
|
65
|
+
*
|
66
|
+
* @param signature A 65 bytes long signature generated according to the signature format specified in the LSP25 standard.
|
67
|
+
* @param nonce The nonce that the signer used to generate the `signature`.
|
68
|
+
* @param validityTimestamps The validity timestamp that the signer used to generate the signature (See {_verifyValidityTimestamps} to learn more).
|
69
|
+
* @param msgValue The amount of native tokens intended to be sent for the relay transaction.
|
70
|
+
* @param callData The calldata to execute as a relay transaction that the signer signed for.
|
71
|
+
*
|
72
|
+
* @return The address that signed, recovered from the `signature`.
|
73
|
+
*/
|
74
|
+
function _recoverSignerFromLSP25Signature(
|
75
|
+
bytes memory signature,
|
76
|
+
uint256 nonce,
|
77
|
+
uint256 validityTimestamps,
|
78
|
+
uint256 msgValue,
|
79
|
+
bytes calldata callData
|
80
|
+
) internal view returns (address) {
|
81
|
+
bytes memory lsp25EncodedMessage = abi.encodePacked(
|
82
|
+
LSP25_VERSION,
|
83
|
+
block.chainid,
|
84
|
+
nonce,
|
85
|
+
validityTimestamps,
|
86
|
+
msgValue,
|
87
|
+
callData
|
88
|
+
);
|
89
|
+
|
90
|
+
bytes32 eip191Hash = address(this).toDataWithIntendedValidatorHash(
|
91
|
+
lsp25EncodedMessage
|
92
|
+
);
|
93
|
+
|
94
|
+
return eip191Hash.recover(signature);
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* @dev Verify that the current timestamp is within the date and time range provided by `validityTimestamps`.
|
99
|
+
*
|
100
|
+
* @param validityTimestamps Two `uint128` concatenated together, where the left-most `uint128` represent the timestamp from which the transaction can be executed,
|
101
|
+
* and the right-most `uint128` represents the timestamp after which the transaction expire.
|
102
|
+
*/
|
103
|
+
function _verifyValidityTimestamps(
|
104
|
+
uint256 validityTimestamps
|
105
|
+
) internal view {
|
106
|
+
if (validityTimestamps == 0) return;
|
107
|
+
|
108
|
+
uint128 startingTimestamp = uint128(validityTimestamps >> 128);
|
109
|
+
uint128 endingTimestamp = uint128(validityTimestamps);
|
110
|
+
|
111
|
+
// solhint-disable-next-line not-rely-on-time
|
112
|
+
if (block.timestamp < startingTimestamp) {
|
113
|
+
revert RelayCallBeforeStartTime();
|
114
|
+
}
|
115
|
+
|
116
|
+
// Allow `endingTimestamp` to be 0
|
117
|
+
// Allow execution anytime past `startingTimestamp`
|
118
|
+
if (endingTimestamp == 0) return;
|
119
|
+
|
120
|
+
// solhint-disable-next-line not-rely-on-time
|
121
|
+
if (block.timestamp > endingTimestamp) {
|
122
|
+
revert RelayCallExpired();
|
123
|
+
}
|
124
|
+
}
|
125
|
+
|
126
|
+
/**
|
127
|
+
* @dev Verify that the nonce `_idx` for `_from` (obtained via {getNonce}) is valid in its channel ID.
|
128
|
+
*
|
129
|
+
* The "idx" is a 256bits (unsigned) integer, where:
|
130
|
+
* - the 128 leftmost bits = channelId
|
131
|
+
* - and the 128 rightmost bits = nonce within the channel
|
132
|
+
|
133
|
+
* @param from The signer's address.
|
134
|
+
* @param idx The concatenation of the `channelId` + `nonce` within a specific channel ID.
|
135
|
+
*
|
136
|
+
* @return true if the nonce is the latest nonce for the `signer`, false otherwise.
|
137
|
+
*/
|
138
|
+
function _isValidNonce(
|
139
|
+
address from,
|
140
|
+
uint256 idx
|
141
|
+
) internal view virtual returns (bool) {
|
142
|
+
return uint128(idx) == _nonceStore[from][idx >> 128];
|
143
|
+
}
|
144
|
+
}
|
package/dist/index.cjs
ADDED
package/dist/index.d.cts
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
declare const INTERFACE_ID_LSP25 = "0x5ac79908";
|
2
|
+
/**
|
3
|
+
* @dev LSP25 version number for signing `executeRelayCall(...)` transaction using EIP191
|
4
|
+
* @see for details see: https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-25-ExecuteRelayCall.md#executerelaycall
|
5
|
+
*/
|
6
|
+
declare const LSP25_VERSION = 25;
|
7
|
+
|
8
|
+
export { INTERFACE_ID_LSP25, LSP25_VERSION };
|
package/dist/index.d.mts
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
declare const INTERFACE_ID_LSP25 = "0x5ac79908";
|
2
|
+
/**
|
3
|
+
* @dev LSP25 version number for signing `executeRelayCall(...)` transaction using EIP191
|
4
|
+
* @see for details see: https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-25-ExecuteRelayCall.md#executerelaycall
|
5
|
+
*/
|
6
|
+
declare const LSP25_VERSION = 25;
|
7
|
+
|
8
|
+
export { INTERFACE_ID_LSP25, LSP25_VERSION };
|
package/dist/index.d.ts
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
declare const INTERFACE_ID_LSP25 = "0x5ac79908";
|
2
|
+
/**
|
3
|
+
* @dev LSP25 version number for signing `executeRelayCall(...)` transaction using EIP191
|
4
|
+
* @see for details see: https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-25-ExecuteRelayCall.md#executerelaycall
|
5
|
+
*/
|
6
|
+
declare const LSP25_VERSION = 25;
|
7
|
+
|
8
|
+
export { INTERFACE_ID_LSP25, LSP25_VERSION };
|
package/dist/index.mjs
ADDED
package/package.json
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
{
|
2
|
+
"name": "@lukso/lsp25-contracts",
|
3
|
+
"version": "0.15.0-rc.0",
|
4
|
+
"description": "Package for the LSP25 Execute Relay Call standard",
|
5
|
+
"license": "Apache-2.0",
|
6
|
+
"author": "",
|
7
|
+
"main": "./dist/index.cjs",
|
8
|
+
"module": "./dist/index.mjs",
|
9
|
+
"typings": "./dist/index.d.ts",
|
10
|
+
"exports": {
|
11
|
+
".": {
|
12
|
+
"require": "./dist/index.cjs",
|
13
|
+
"import": "./dist/index.mjs",
|
14
|
+
"types": "./dist/index.d.ts"
|
15
|
+
},
|
16
|
+
"./artifacts/*": "./artifacts/*",
|
17
|
+
"./package.json": "./package.json"
|
18
|
+
},
|
19
|
+
"files": [
|
20
|
+
"contracts/**/*.sol",
|
21
|
+
"!contracts/Mocks/**/*.sol",
|
22
|
+
"artifacts/*.json",
|
23
|
+
"dist",
|
24
|
+
"./README.md"
|
25
|
+
],
|
26
|
+
"keywords": [
|
27
|
+
"LUKSO",
|
28
|
+
"LSP",
|
29
|
+
"Blockchain",
|
30
|
+
"Standards",
|
31
|
+
"Smart Contracts",
|
32
|
+
"Ethereum",
|
33
|
+
"EVM",
|
34
|
+
"Solidity"
|
35
|
+
],
|
36
|
+
"scripts": {
|
37
|
+
"build": "hardhat compile --show-stack-traces",
|
38
|
+
"build:js": "unbuild",
|
39
|
+
"clean": "hardhat clean && rm -Rf dist/",
|
40
|
+
"format": "prettier --write .",
|
41
|
+
"lint": "eslint . --ext .ts,.js",
|
42
|
+
"lint:solidity": "solhint 'contracts/**/*.sol' && prettier --check 'contracts/**/*.sol'",
|
43
|
+
"test": "hardhat test --no-compile tests/*.test.ts",
|
44
|
+
"test:coverage": "hardhat coverage"
|
45
|
+
},
|
46
|
+
"dependencies": {
|
47
|
+
"@openzeppelin/contracts": "^4.9.3"
|
48
|
+
}
|
49
|
+
}
|