mirror of
https://github.com/luxfi/eoa-contracts.git
synced 2026-10-30 16:58:32 +00:00
128 lines
4.9 KiB
Solidity
128 lines
4.9 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.17;
|
|
import "./ISessionValidationModule.sol";
|
|
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
|
|
|
/**
|
|
* @title ERC20 Session Validation Module for Biconomy Smart Accounts.
|
|
* @dev Validates userOps for ERC20 transfers and approvals using a session key signature.
|
|
* - Recommended to use with standard ERC20 tokens only
|
|
* - Can be used with any method of any contract which implement
|
|
* method(address, uint256) interface
|
|
*
|
|
* @author Fil Makarov - <[email protected]>
|
|
*/
|
|
|
|
contract ERC20SessionValidationModule is ISessionValidationModule {
|
|
/**
|
|
* @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
|
|
* param _callSpecificData additional data, for example some proofs if the SVM utilizes merkle trees itself
|
|
* for example to store a list of allowed tokens or receivers
|
|
*/
|
|
function validateSessionParams(
|
|
address destinationContract,
|
|
uint256 callValue,
|
|
bytes calldata _funcCallData,
|
|
bytes calldata _sessionKeyData,
|
|
bytes calldata /*_callSpecificData*/
|
|
) external virtual override returns (address) {
|
|
(
|
|
address sessionKey,
|
|
address token,
|
|
address recipient,
|
|
uint256 maxAmount
|
|
) = abi.decode(_sessionKeyData, (address, address, address, uint256));
|
|
|
|
require(destinationContract == token, "ERC20SV Invalid Token");
|
|
require(callValue == 0, "ERC20SV Non Zero Value");
|
|
|
|
(address recipientCalled, uint256 amount) = abi.decode(
|
|
_funcCallData[4:],
|
|
(address, uint256)
|
|
);
|
|
|
|
require(recipient == recipientCalled, "ERC20SV Wrong Recipient");
|
|
require(amount <= maxAmount, "ERC20SV Max Amount Exceeded");
|
|
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) {
|
|
require(
|
|
bytes4(_op.callData[0:4]) == EXECUTE_OPTIMIZED_SELECTOR ||
|
|
bytes4(_op.callData[0:4]) == EXECUTE_SELECTOR,
|
|
"ERC20SV Invalid Selector"
|
|
);
|
|
(
|
|
address sessionKey,
|
|
address token,
|
|
address recipient,
|
|
uint256 maxAmount
|
|
) = abi.decode(_sessionKeyData, (address, address, address, uint256));
|
|
|
|
{
|
|
// we expect _op.callData to be `SmartAccount.execute(to, value, calldata)` calldata
|
|
(address tokenAddr, uint256 callValue, ) = abi.decode(
|
|
_op.callData[4:], // skip selector
|
|
(address, uint256, bytes)
|
|
);
|
|
if (tokenAddr != token) {
|
|
revert("ERC20SV Wrong Token");
|
|
}
|
|
if (callValue != 0) {
|
|
revert("ERC20SV Non Zero Value");
|
|
}
|
|
}
|
|
// working with userOp.callData
|
|
// check if the call is to the allowed recepient and amount is not more than allowed
|
|
bytes calldata data;
|
|
|
|
{
|
|
//offset represents where does the inner bytes array start
|
|
uint256 offset = uint256(bytes32(_op.callData[4 + 64:4 + 96]));
|
|
uint256 length = uint256(
|
|
bytes32(_op.callData[4 + offset:4 + offset + 32])
|
|
);
|
|
//we expect data to be the `IERC20.transfer(address, uint256)` calldata
|
|
data = _op.callData[4 + offset + 32:4 + offset + 32 + length];
|
|
}
|
|
|
|
(address recipientCalled, uint256 amount) = abi.decode(
|
|
data[4:],
|
|
(address, uint256)
|
|
);
|
|
|
|
if (recipientCalled != recipient) {
|
|
revert("ERC20SV Wrong Recipient");
|
|
}
|
|
if (amount > maxAmount) {
|
|
revert("ERC20SV Max Amount Exceeded");
|
|
}
|
|
|
|
return
|
|
ECDSA.recover(
|
|
ECDSA.toEthSignedMessageHash(_userOpHash),
|
|
_sessionKeySignature
|
|
) == sessionKey;
|
|
}
|
|
}
|