Skip to content

Commit

Permalink
πŸ‘·πŸ»β€β™‚οΈ Implement SessionValidationModule for SMv3
Browse files Browse the repository at this point in the history
  • Loading branch information
JaredBorders committed Sep 28, 2023
1 parent 0857f42 commit 92ad72a
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 0 deletions.
163 changes: 163 additions & 0 deletions src/SMv3SessionValidationModule.sol
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;
}
}
36 changes: 36 additions & 0 deletions src/kwenta/smv3/IERC7412.sol
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;
}

0 comments on commit 92ad72a

Please sign in to comment.