Skip to content

Commit

Permalink
docs: update LSP6 docs + add LSP25 docs
Browse files Browse the repository at this point in the history
  • Loading branch information
CJ42 committed Aug 15, 2023
1 parent abe3db7 commit 7a7d713
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 104 deletions.
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
37 changes: 21 additions & 16 deletions contracts/LSP25ExecuteRelayCall/ILSP25ExecuteRelayCall.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,40 @@ 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,
uint128 channelId
) 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,
Expand All @@ -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,
Expand Down
67 changes: 45 additions & 22 deletions contracts/LSP25ExecuteRelayCall/LSP25MultiChannelNonce.sol
Original file line number Diff line number Diff line change
@@ -1,39 +1,58 @@
// 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,
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;

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,
Expand All @@ -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);
Expand All @@ -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,
Expand Down
37 changes: 20 additions & 17 deletions contracts/LSP6KeyManager/LSP6KeyManagerCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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).
Expand All @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions contracts/LSP6KeyManager/LSP6KeyManagerInit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 7a7d713

Please sign in to comment.