Skip to content

Instantly share code, notes, and snippets.

@CJ42
Created July 29, 2023 10:40
Show Gist options
  • Save CJ42/3c0a109fbbc30e422d4808499afa252e to your computer and use it in GitHub Desktop.
Save CJ42/3c0a109fbbc30e422d4808499afa252e to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.5;
// interfaces
import {
IERC725Y
} from "@erc725/smart-contracts/contracts/interfaces/IERC725Y.sol";
import {ILSP6KeyManager} from "./ILSP6KeyManager.sol";
// modules
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {ERC725Y} from "@erc725/smart-contracts/contracts/ERC725Y.sol";
import {
OwnableUnset
} from "@erc725/smart-contracts/contracts/custom/OwnableUnset.sol";
import {LSP6SetDataModule} from "./LSP6Modules/LSP6SetDataModule.sol";
import {LSP6OwnershipModule} from "./LSP6Modules/LSP6OwnershipModule.sol";
// librairies
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {LSP6Utils} from "./LSP6Utils.sol";
// constants
import {
_INTERFACEID_LSP6,
LSP6_VERSION,
_PERMISSION_SIGN
} from "./LSP6Constants.sol";
import {
_INTERFACEID_ERC1271,
_ERC1271_MAGICVALUE,
_ERC1271_FAILVALUE
} from "../LSP0ERC725Account/LSP0Constants.sol";
// errors
import {
BatchExecuteParamsLengthMismatch,
BatchExecuteRelayCallParamsLengthMismatch,
LSP6BatchExcessiveValueSent,
LSP6BatchInsufficientValueSent,
InvalidPayload,
InvalidRelayNonce,
NoPermissionsSet,
InvalidERC725Function,
CannotSendValueToSetData,
RelayCallBeforeStartTime,
RelayCallExpired,
InvalidLSP6Target
} from "./LSP6Errors.sol";
/**
* @title LSP6 Implementation to manage a LSP7/8 Token through multiple controllers
* @author Jean Cavallera <CJ42>
*/
contract LSP6TokenManager is
ILSP6KeyManager,
ERC165,
LSP6SetDataModule,
LSP6OwnershipModule
{
using Address for *;
using ECDSA for *;
using LSP6Utils for *;
/**
* @dev address of the LSP7/8 Token contract this Key Manager controls
*/
address private immutable _linkedToken;
mapping(address => mapping(uint256 => uint256)) internal _nonceStore;
constructor(address linkedToken_) {
if (linkedToken_ == address(0)) revert InvalidLSP6Target();
_linkedToken = linkedToken_;
}
/**
* TODO: is it really necessary that the LSP6 interface inherits the ERC1271 interface?
* As in the context of a Key Manager linked to a token like here, does the Key Manager
* really need to implement `isValidSignature(...)` function? What would be the use case?
*/
// function isValidSignature(
// bytes32 dataHash,
// bytes memory signature
// ) public view returns (bytes4 magicValue) {
// // if isValidSignature fail, the error is catched in returnedError
// (address recoveredAddress, ECDSA.RecoverError returnedError) = ECDSA
// .tryRecover(dataHash, signature);
// // if recovering throws an error, return the fail value
// if (returnedError != ECDSA.RecoverError.NoError)
// return _ERC1271_FAILVALUE;
// // if the address recovered has SIGN permission return the ERC1271 magic value, otherwise the fail value
// return (
// ERC725Y(_linkedToken)
// .getPermissionsFor(recoveredAddress)
// .hasPermission(_PERMISSION_SIGN)
// ? _ERC1271_MAGICVALUE
// : _ERC1271_FAILVALUE
// );
// }
function target() public view returns (address) {
return _linkedToken;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
interfaceId == _INTERFACEID_LSP6 ||
super.supportsInterface(interfaceId);
}
/**
* @inheritdoc ILSP6KeyManager
*/
function getNonce(
address from,
uint128 channelId
) public view returns (uint256) {
uint256 nonceInChannel = _nonceStore[from][channelId];
return (uint256(channelId) << 128) | nonceInChannel;
}
/**
* @inheritdoc ILSP6KeyManager
*/
function execute(
bytes calldata payload
) external payable returns (bytes memory) {
return _execute(msg.value, payload);
}
/**
* @inheritdoc ILSP6KeyManager
*/
function executeBatch(
uint256[] calldata values,
bytes[] calldata payloads
) external payable returns (bytes[] memory) {
if (values.length != payloads.length) {
revert BatchExecuteParamsLengthMismatch();
}
bytes[] memory results = new bytes[](payloads.length);
uint256 totalValues;
for (uint256 ii = 0; ii < payloads.length; ) {
if ((totalValues += values[ii]) > msg.value) {
revert LSP6BatchInsufficientValueSent(totalValues, msg.value);
}
results[ii] = _execute(values[ii], payloads[ii]);
unchecked {
++ii;
}
}
if (totalValues < msg.value) {
revert LSP6BatchExcessiveValueSent(totalValues, msg.value);
}
return results;
}
/**
* @inheritdoc ILSP6KeyManager
*/
function executeRelayCall(
bytes memory signature,
uint256 nonce,
uint256 validityTimestamps,
bytes calldata payload
) public payable virtual returns (bytes memory) {
return
_executeRelayCall(
signature,
nonce,
validityTimestamps,
msg.value,
payload
);
}
/**
* @inheritdoc ILSP6KeyManager
*/
function executeRelayCallBatch(
bytes[] memory signatures,
uint256[] calldata nonces,
uint256[] calldata validityTimestamps,
uint256[] calldata values,
bytes[] calldata payloads
) public payable virtual returns (bytes[] memory) {
if (
signatures.length != nonces.length ||
nonces.length != validityTimestamps.length ||
validityTimestamps.length != values.length ||
values.length != payloads.length
) {
revert BatchExecuteRelayCallParamsLengthMismatch();
}
bytes[] memory results = new bytes[](payloads.length);
uint256 totalValues;
for (uint256 ii = 0; ii < payloads.length; ) {
if ((totalValues += values[ii]) > msg.value) {
revert LSP6BatchInsufficientValueSent(totalValues, msg.value);
}
results[ii] = _executeRelayCall(
signatures[ii],
nonces[ii],
validityTimestamps[ii],
values[ii],
payloads[ii]
);
unchecked {
++ii;
}
}
if (totalValues < msg.value) {
revert LSP6BatchExcessiveValueSent(totalValues, msg.value);
}
return results;
}
function _execute(
uint256 msgValue,
bytes calldata payload
) internal virtual returns (bytes memory) {
if (payload.length < 4) {
revert InvalidPayload(payload);
}
_verifyPermissions(msg.sender, msgValue, payload);
emit VerifiedCall(msg.sender, msgValue, bytes4(payload));
return _executePayload(msgValue, payload);
}
function _executeRelayCall(
bytes memory signature,
uint256 nonce,
uint256 validityTimestamps,
uint256 msgValue,
bytes calldata payload
) internal virtual returns (bytes memory) {
if (payload.length < 4) {
revert InvalidPayload(payload);
}
bytes memory encodedMessage = abi.encodePacked(
LSP6_VERSION,
block.chainid,
nonce,
validityTimestamps,
msgValue,
payload
);
address signer = address(this)
.toDataWithIntendedValidatorHash(encodedMessage)
.recover(signature);
if (!_isValidNonce(signer, nonce)) {
revert InvalidRelayNonce(signer, nonce, signature);
}
// increase nonce after successful verification
_nonceStore[signer][nonce >> 128]++;
if (validityTimestamps != 0) {
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();
}
}
_verifyPermissions(signer, msgValue, payload);
emit VerifiedCall(signer, msgValue, bytes4(payload));
return _executePayload(msgValue, payload);
}
/**
* @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`
*/
function _executePayload(
uint256 msgValue,
bytes calldata payload
) internal virtual returns (bytes memory) {
(bool success, bytes memory returnData) = _linkedToken.call{
value: msgValue,
gas: gasleft()
}(payload);
bytes memory result = success.verifyCallResult(
returnData,
"LSP6: failed executing payload"
);
return result.length != 0 ? abi.decode(result, (bytes)) : result;
}
/**
* @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)
*/
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]);
}
/**
* @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`.
*/
function _verifyPermissions(
address from,
uint256 msgValue,
bytes calldata payload
) internal view virtual {
bytes32 permissions = ERC725Y(_linkedToken).getPermissionsFor(from);
if (permissions == bytes32(0)) revert NoPermissionsSet(from);
bytes4 erc725Function = bytes4(payload);
// ERC725Y.setData(bytes32,bytes)
if (erc725Function == IERC725Y.setData.selector) {
if (msgValue != 0) revert CannotSendValueToSetData();
(bytes32 inputKey, bytes memory inputValue) = abi.decode(
payload[4:],
(bytes32, bytes)
);
LSP6SetDataModule._verifyCanSetData(
_linkedToken,
from,
permissions,
inputKey,
inputValue
);
} else if (erc725Function == IERC725Y.setDataBatch.selector) {
if (msgValue != 0) revert CannotSendValueToSetData();
(bytes32[] memory inputKeys, bytes[] memory inputValues) = abi
.decode(payload[4:], (bytes32[], bytes[]));
LSP6SetDataModule._verifyCanSetData(
_linkedToken,
from,
permissions,
inputKeys,
inputValues
);
} else if (erc725Function == OwnableUnset.transferOwnership.selector) {
LSP6OwnershipModule._verifyOwnershipPermissions(from, permissions);
} else {
revert InvalidERC725Function(erc725Function);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment