Skip to content

Commit

Permalink
hashing and parsing PND
Browse files Browse the repository at this point in the history
  • Loading branch information
ShivaanshK committed Sep 2, 2024
1 parent b944e24 commit 7d47e18
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 5 deletions.
5 changes: 5 additions & 0 deletions contracts/common/BiconomyTokenPaymasterErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ contract BiconomyTokenPaymasterErrors {
* @notice Throws when trying to withdraw multiple tokens, but each token doesn't have a corresponding amount
*/
error TokensAndAmountsLengthMismatch();

/**
* @notice Throws when invalid signature length in paymasterAndData
*/
error InvalidSignatureLength();
}
7 changes: 3 additions & 4 deletions contracts/interfaces/IBiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
pragma solidity ^0.8.26;

interface IBiconomyTokenPaymaster {
enum ExchangeRateSource {
EXTERNAL_EXCHANGE_RATE,
ORACLE_BASED,
TWAP_BASED
enum PriceSource {
EXTERNAL,
ORACLE
}

event UnaccountedGasChanged(uint256 indexed oldValue, uint256 indexed newValue);
Expand Down
103 changes: 102 additions & 1 deletion contracts/token/BiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { SafeTransferLib } from "@solady/src/utils/SafeTransferLib.sol";
import { BasePaymaster } from "../base/BasePaymaster.sol";
import { BiconomyTokenPaymasterErrors } from "../common/BiconomyTokenPaymasterErrors.sol";
import { IBiconomyTokenPaymaster } from "../interfaces/IBiconomyTokenPaymaster.sol";
import "@account-abstraction/contracts/core/Helpers.sol";


/**
* @title BiconomyTokenPaymaster
Expand Down Expand Up @@ -191,6 +193,78 @@ contract BiconomyTokenPaymaster is
emit UnaccountedGasChanged(oldValue, value);
}

/**
* return the hash we're going to sign off-chain (and validate on-chain)
* this method is called by the off-chain service, to sign the request.
* it is called on-chain from the validatePaymasterUserOp, to validate the signature.
* note that this signature covers all fields of the UserOperation, except the "paymasterAndData",
* which will carry the signature itself.
*/
function getHash(
PackedUserOperation calldata userOp,
PriceSource priceSource,
uint48 validUntil,
uint48 validAfter,
address feeToken,
address oracleAggregator,
uint256 exchangeRate,
uint32 priceMarkup
)
public
view
returns (bytes32)
{
//can't use userOp.hash(), since it contains also the paymasterAndData itself.
address sender = userOp.getSender();
return keccak256(
abi.encode(
sender,
userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.accountGasLimits,
uint256(bytes32(userOp.paymasterAndData[PAYMASTER_VALIDATION_GAS_OFFSET:PAYMASTER_DATA_OFFSET])),
userOp.preVerificationGas,
userOp.gasFees,
block.chainid,
address(this),
priceSource, // treated as a uint8
validUntil,
validAfter,
feeToken,
oracleAggregator,
exchangeRate,
priceMarkup
)
);
}

function parsePaymasterAndData(bytes calldata paymasterAndData)
public
pure
returns (
PriceSource priceSource,
uint48 validUntil,
uint48 validAfter,
address feeToken,
address oracleAggregator,
uint256 exchangeRate,
uint32 priceMarkup,
bytes calldata signature
)
{
unchecked {
priceSource = PriceSource(uint8(bytes1(paymasterAndData[PAYMASTER_DATA_OFFSET])));
validUntil = uint48(bytes6(paymasterAndData[PAYMASTER_DATA_OFFSET + 1:PAYMASTER_DATA_OFFSET + 7]));
validAfter = uint48(bytes6(paymasterAndData[PAYMASTER_DATA_OFFSET + 7:PAYMASTER_DATA_OFFSET + 13]));
feeToken = address(bytes20(paymasterAndData[PAYMASTER_DATA_OFFSET + 13:PAYMASTER_DATA_OFFSET + 33]));
oracleAggregator = address(bytes20(paymasterAndData[PAYMASTER_DATA_OFFSET + 33:PAYMASTER_DATA_OFFSET + 53]));
exchangeRate = uint256(bytes32(paymasterAndData[PAYMASTER_DATA_OFFSET + 53:PAYMASTER_DATA_OFFSET + 85]));
priceMarkup = uint32(bytes4(paymasterAndData[PAYMASTER_DATA_OFFSET + 85:PAYMASTER_DATA_OFFSET + 89]));
signature = paymasterAndData[PAYMASTER_DATA_OFFSET + 89:];
}
}

/**
* @dev Validate a user operation.
* This method is abstract in BasePaymaster and must be implemented in derived contracts.
Expand All @@ -207,7 +281,34 @@ contract BiconomyTokenPaymaster is
override
returns (bytes memory context, uint256 validationData)
{
// Implementation of user operation validation logic
// review: in this method try to resolve stack too deep (though via-ir is good enough)
(
PriceSource priceSource,
uint48 validUntil,
uint48 validAfter,
address feeToken,
address oracleAggregator,
uint256 exchangeRate,
uint32 priceMarkup,
bytes calldata signature
) = parsePaymasterAndData(userOp.paymasterAndData);

if (signature.length != 64 && signature.length != 65) {
revert InvalidSignatureLength();
}

bool validSig = verifyingSigner.isValidSignatureNow(
ECDSA_solady.toEthSignedMessageHash(
getHash(
userOp, priceSource, validUntil, validAfter, feeToken, oracleAggregator, exchangeRate, priceMarkup
)
),
signature
);

if (!validSig) {
return ("", _packValidationData(true, validUntil, validAfter));
}
}

/**
Expand Down

0 comments on commit 7d47e18

Please sign in to comment.