generated from Kwenta/foundry-scaffold
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
π·π»ββοΈ Implement SessionValidationModule for SMv3
- Loading branch information
1 parent
0857f42
commit 92ad72a
Showing
2 changed files
with
199 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,163 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity 0.8.18; | ||
|
||
import { | ||
ISessionValidationModule, | ||
UserOperation | ||
} from "src/biconomy/interfaces/ISessionValidationModule.sol"; | ||
import {ECDSA} from "src/openzeppelin/ECDSA.sol"; | ||
|
||
/** | ||
* @title Kwenta Smart Margin v3 Session Validation Module for Biconomy Smart Accounts. | ||
* @dev Validates userOps for | ||
* `IEngine.modifyCollateral()` | ||
* `IEngine.commitOrder()` | ||
* `IEngine.invalidateUnorderedNonces()` | ||
* `IERC7412.fulfillOracleQuery()` | ||
* using a session key signature. | ||
* @author Fil Makarov - <[email protected]> | ||
* @author JaredBorders ([email protected]) | ||
*/ | ||
contract SMv3SessionValidationModule is ISessionValidationModule { | ||
error InvalidSelector(); | ||
error InvalidSMv3Selector(); | ||
error InvalidDestinationContract(); | ||
error InvalidCallValue(); | ||
|
||
/** | ||
* @dev validates that the call (destinationContract, callValue, funcCallData) | ||
* complies with the Session Key permissions represented by sessionKeyData | ||
* @param destinationContract address of the contract to be called | ||
* @param callValue value to be sent with the call | ||
* @param _funcCallData the data for the call. is parsed inside the SVM | ||
* @param _sessionKeyData SessionKey data, that describes sessionKey permissions | ||
*/ | ||
function validateSessionParams( | ||
address destinationContract, | ||
uint256 callValue, | ||
bytes calldata _funcCallData, | ||
bytes calldata _sessionKeyData, | ||
bytes calldata /*_callSpecificData*/ | ||
) external virtual override returns (address) { | ||
( | ||
address sessionKey, | ||
address smv3Engine, | ||
bytes4 smv3ModifyCollateralSelector, | ||
bytes4 smv3CommitOrderSelector, | ||
bytes4 smv3InvalidateUnorderedNoncesSelector, | ||
bytes4 smv3FulfillOracleQuerySelector | ||
) = abi.decode( | ||
_sessionKeyData, (address, address, bytes4, bytes4, bytes4, bytes4) | ||
); | ||
|
||
/// @dev ensure destinationContract is the smv3Engine | ||
if (destinationContract != smv3Engine) { | ||
revert InvalidDestinationContract(); | ||
} | ||
|
||
/// @dev ensure the function selector is the a valid selector | ||
bytes4 funcSelector = bytes4(_funcCallData[:4]); | ||
if ( | ||
funcSelector != smv3ModifyCollateralSelector | ||
|| funcSelector != smv3CommitOrderSelector | ||
|| funcSelector != smv3InvalidateUnorderedNoncesSelector | ||
|| funcSelector != smv3FulfillOracleQuerySelector | ||
) { | ||
revert InvalidSMv3Selector(); | ||
} | ||
|
||
/// @dev ensure call value is zero | ||
if (callValue != 0) { | ||
revert InvalidCallValue(); | ||
} | ||
|
||
return sessionKey; | ||
} | ||
|
||
/** | ||
* @dev validates if the _op (UserOperation) matches the SessionKey permissions | ||
* and that _op has been signed by this SessionKey | ||
* Please mind the decimals of your exact token when setting maxAmount | ||
* @param _op User Operation to be validated. | ||
* @param _userOpHash Hash of the User Operation to be validated. | ||
* @param _sessionKeyData SessionKey data, that describes sessionKey permissions | ||
* @param _sessionKeySignature Signature over the the _userOpHash. | ||
* @return true if the _op is valid, false otherwise. | ||
*/ | ||
function validateSessionUserOp( | ||
UserOperation calldata _op, | ||
bytes32 _userOpHash, | ||
bytes calldata _sessionKeyData, | ||
bytes calldata _sessionKeySignature | ||
) external pure override returns (bool) { | ||
/// @dev ensure function selector is `IAccount.execute` | ||
if ( | ||
bytes4(_op.callData[0:4]) != EXECUTE_SELECTOR | ||
|| bytes4(_op.callData[0:4]) != EXECUTE_OPTIMIZED_SELECTOR | ||
) { | ||
revert InvalidSelector(); | ||
} | ||
|
||
(address sessionKey, address smv3Engine,,,,) = abi.decode( | ||
_sessionKeyData, (address, address, bytes4, bytes4, bytes4, bytes4) | ||
); | ||
|
||
{ | ||
(address smv3EngineAddress, uint256 callValue,) = abi.decode( | ||
_op.callData[4:], // skip selector | ||
(address, uint256, bytes) | ||
); | ||
|
||
/// @dev ensure destinationContract is the smv3Engine | ||
if (smv3EngineAddress != smv3Engine) { | ||
revert InvalidDestinationContract(); | ||
} | ||
|
||
/// @dev ensure call value is zero | ||
if (callValue != 0) { | ||
revert InvalidCallValue(); | ||
} | ||
} | ||
|
||
// working with userOp.callData | ||
// check if the call is conforms to valid smv3Engine selectors | ||
bytes calldata data; | ||
{ | ||
uint256 offset = uint256(bytes32(_op.callData[4 + 64:4 + 96])); | ||
uint256 length = | ||
uint256(bytes32(_op.callData[4 + offset:4 + offset + 32])); | ||
data = _op.callData[4 + offset + 32:4 + offset + 32 + length]; | ||
} | ||
|
||
{ | ||
( | ||
, | ||
, | ||
bytes4 smv3ModifyCollateralSelector, | ||
bytes4 smv3CommitOrderSelector, | ||
bytes4 smv3InvalidateUnorderedNoncesSelector, | ||
bytes4 smv3FulfillOracleQuerySelector | ||
) = abi.decode( | ||
_sessionKeyData, | ||
(address, address, bytes4, bytes4, bytes4, bytes4) | ||
); | ||
|
||
/// @dev ensure the function selector is the a valid selector | ||
bytes4 funcSelector = bytes4(data[:4]); | ||
if ( | ||
funcSelector != smv3ModifyCollateralSelector | ||
|| funcSelector != smv3CommitOrderSelector | ||
|| funcSelector != smv3InvalidateUnorderedNoncesSelector | ||
|| funcSelector != smv3FulfillOracleQuerySelector | ||
) { | ||
revert InvalidSMv3Selector(); | ||
} | ||
} | ||
|
||
/// @dev this method of signature validation is out-of-date | ||
/// see https://github.com/OpenZeppelin/openzeppelin-sdk/blob/7d96de7248ae2e7e81a743513ccc617a2e6bba21/packages/lib/contracts/cryptography/ECDSA.sol#L6 | ||
return ECDSA.recover( | ||
ECDSA.toEthSignedMessageHash(_userOpHash), _sessionKeySignature | ||
) == sessionKey; | ||
} | ||
} |
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,36 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity 0.8.18; | ||
|
||
/// @title ERC-7412 Off-Chain Data Retrieval Contract | ||
/// @author Synthetix | ||
interface IERC7412 { | ||
/// @dev Emitted when an oracle is requested to provide data. | ||
/// Upon receipt of this error, a wallet client | ||
/// should automatically resolve the requested oracle data | ||
/// and call fulfillOracleQuery. | ||
/// @param oracleContract The address of the oracle contract | ||
/// (which is also the fulfillment contract). | ||
/// @param oracleQuery The query to be sent to the off-chain interface. | ||
error OracleDataRequired(address oracleContract, bytes oracleQuery); | ||
|
||
/// @dev Emitted when the recently posted oracle data requires | ||
/// a fee to be paid. Upon receipt of this error, | ||
/// a wallet client should attach the requested feeAmount | ||
/// to the most recently posted oracle data transaction | ||
error FeeRequired(uint256 feeAmount); | ||
|
||
/// @dev Returns a human-readable identifier of the oracle | ||
/// contract. This should map to a URL and API | ||
/// key on the client side. | ||
/// @return The oracle identifier. | ||
function oracleId() external view returns (bytes32); | ||
|
||
/// @dev Upon resolving the oracle query, the client should | ||
/// call this function to post the data to the | ||
/// blockchain. | ||
/// @param signedOffchainData The data that was returned | ||
/// from the off-chain interface, signed by the oracle. | ||
function fulfillOracleQuery(bytes calldata signedOffchainData) | ||
external | ||
payable; | ||
} |