Skip to content

Commit

Permalink
compiles
Browse files Browse the repository at this point in the history
  • Loading branch information
Filipp Makarov authored and Filipp Makarov committed Dec 4, 2024
1 parent b7f26f3 commit 713d31d
Show file tree
Hide file tree
Showing 10 changed files with 102 additions and 99 deletions.
10 changes: 8 additions & 2 deletions contracts/common/BiconomyTokenPaymasterErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,14 @@ contract BiconomyTokenPaymasterErrors {
*/
error WithdrawalFailed();


/**
* @notice Throws when PM was not able to charge user
*/
error FailedToChargeTokens(address account, address token, uint256 amount, bytes32 userOpHash);

/**
* @notice Emitted when ETH is withdrawn from the paymaster
* Throws when account has insufficient token balance to pay for gas
*/
event EthWithdrawn(address indexed recipient, uint256 indexed amount);
error InsufficientTokenBalance(address account, address token, uint256 amount, bytes32 userOpHash);
}
12 changes: 10 additions & 2 deletions contracts/interfaces/IBiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ interface IBiconomyTokenPaymaster {
enum PaymasterMode {
EXTERNAL, // Price provided by external service. Authenticated using signature from verifyingSigner
INDEPENDENT // Price queried from oracle. No signature needed from external service.

}

// Struct for storing information about the token
Expand All @@ -26,7 +25,7 @@ interface IBiconomyTokenPaymaster {
event TokensRefunded(
address indexed userOpSender, address indexed token, uint256 refundAmount, bytes32 indexed userOpHash
);
event PaidGasInTokens(
event PaidGasInTokensIndependent(
address indexed userOpSender,
address indexed token,
uint256 nativeCharge,
Expand All @@ -35,6 +34,15 @@ interface IBiconomyTokenPaymaster {
uint256 tokenPrice,
bytes32 indexed userOpHash
);
event PaidGasInTokensExternal(
address indexed userOpSender,
address indexed token,
uint256 tokenAmount,
bytes32 indexed userOpHash
);

event EthWithdrawn(address indexed recipient, uint256 indexed amount);

event Received(address indexed sender, uint256 value);
event TokensWithdrawn(address indexed token, address indexed to, uint256 indexed amount, address actor);
event AddedToTokenDirectory(address indexed tokenAddress, IOracle indexed oracle, uint8 decimals);
Expand Down
8 changes: 3 additions & 5 deletions contracts/libraries/TokenPaymasterParserLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,15 @@ library TokenPaymasterParserLib {
uint48 validUntil,
uint48 validAfter,
address tokenAddress,
uint256 tokenPrice,
uint32 externalPriceMarkup,
uint256 estimatedTokenAmount,
bytes calldata signature
)
{
validUntil = uint48(bytes6(modeSpecificData[:6]));
validAfter = uint48(bytes6(modeSpecificData[6:12]));
tokenAddress = address(bytes20(modeSpecificData[12:32]));
tokenPrice = uint256(bytes32(modeSpecificData[32:64]));
externalPriceMarkup = uint32(bytes4(modeSpecificData[64:68]));
signature = modeSpecificData[68:];
estimatedTokenAmount = uint256(bytes32(modeSpecificData[32:64]));
signature = modeSpecificData[64:];
}

function parseIndependentModeSpecificData(
Expand Down
123 changes: 64 additions & 59 deletions contracts/token/BiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,7 @@ contract BiconomyTokenPaymaster is
uint48 validUntil,
uint48 validAfter,
address tokenAddress,
uint256 tokenPrice,
uint32 externalPriceMarkup
uint256 estimatedTokenAmount
)
public
view
Expand All @@ -415,8 +414,7 @@ contract BiconomyTokenPaymaster is
validUntil,
validAfter,
tokenAddress,
tokenPrice,
externalPriceMarkup
estimatedTokenAmount
)
);
}
Expand Down Expand Up @@ -479,14 +477,6 @@ contract BiconomyTokenPaymaster is
revert InvalidPaymasterMode();
}

// callGasLimit + paymasterPostOpGas
uint256 maxPenalty = (
(
uint128(uint256(userOp.accountGasLimits))
+ uint128(bytes16(userOp.paymasterAndData[_PAYMASTER_POSTOP_GAS_OFFSET:_PAYMASTER_DATA_OFFSET]))
) * 10 * userOp.unpackMaxFeePerGas()
) / 100;

if (mode == PaymasterMode.EXTERNAL) {
// Use the price and other params specified in modeSpecificData by the verifyingSigner
// Useful for supporting tokens which don't have oracle support
Expand All @@ -495,8 +485,7 @@ contract BiconomyTokenPaymaster is
uint48 validUntil,
uint48 validAfter,
address tokenAddress,
uint256 tokenPrice, // Note: what backend should pass is nativeTokenPriceInUsd/tokenPriceInUsd * 10^token decimals
uint32 externalPriceMarkup,
uint256 estimatedTokenAmount,
bytes memory signature
) = modeSpecificData.parseExternalModeSpecificData();

Expand All @@ -506,7 +495,7 @@ contract BiconomyTokenPaymaster is

bool validSig = verifyingSigner.isValidSignatureNow(
ECDSA_solady.toEthSignedMessageHash(
getHash(userOp, validUntil, validAfter, tokenAddress, tokenPrice, externalPriceMarkup)
getHash(userOp, validUntil, validAfter, tokenAddress, estimatedTokenAmount)
),
signature
);
Expand All @@ -516,38 +505,33 @@ contract BiconomyTokenPaymaster is
return ("", _packValidationData(true, validUntil, validAfter));
}

if (externalPriceMarkup > _MAX_PRICE_MARKUP || externalPriceMarkup < _PRICE_DENOMINATOR) {
revert InvalidPriceMarkup();
}


uint256 tokenAmount;
{
uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas(userOp);
tokenAmount = ((maxCost + maxPenalty + (unaccountedGas * maxFeePerGas)) * externalPriceMarkup * tokenPrice)
/ (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR);
if(IERC20(tokenAddress).balanceOf(userOp.sender) < estimatedTokenAmount) {
revert InsufficientTokenBalance(userOp.sender, tokenAddress, estimatedTokenAmount, userOpHash);
}

// Transfer full amount to this address. Unused amount will be refunded in postOP
SafeTransferLib.safeTransferFrom(tokenAddress, userOp.sender, address(this), tokenAmount);

// deduct max penalty from the token amount we pass to the postOp
// so we don't refund it at postOp
context = abi.encode(
mode,
userOp.sender,
tokenAddress,
tokenAmount-((maxPenalty*tokenPrice*externalPriceMarkup)/(_NATIVE_TOKEN_DECIMALS*_PRICE_DENOMINATOR)),
tokenPrice,
externalPriceMarkup,
estimatedTokenAmount,
userOpHash
);
validationData = _packValidationData(false, validUntil, validAfter);

/// INDEPENDENT MODE
} else if (mode == PaymasterMode.INDEPENDENT) {
// Use only oracles for the token specified in modeSpecificData
if (modeSpecificData.length != 20) {
revert InvalidTokenAddress();
}

uint256 maxPenalty = (
(
uint128(uint256(userOp.accountGasLimits))
+ uint128(bytes16(userOp.paymasterAndData[_PAYMASTER_POSTOP_GAS_OFFSET:_PAYMASTER_DATA_OFFSET]))
) * 10 * userOp.unpackMaxFeePerGas()
) / 100;

// Get address for token used to pay
address tokenAddress = modeSpecificData.parseIndependentModeSpecificData();
uint256 tokenPrice = _getPrice(tokenAddress);
Expand All @@ -566,13 +550,17 @@ contract BiconomyTokenPaymaster is
}

// Transfer full amount to this address. Unused amount will be refunded in postOP
SafeTransferLib.safeTransferFrom(tokenAddress, userOp.sender, address(this), tokenAmount);
// SafeTransferLib.safeTransferFrom(tokenAddress, userOp.sender, address(this), tokenAmount);
if(IERC20(tokenAddress).balanceOf(userOp.sender) < tokenAmount) {
revert InsufficientTokenBalance(userOp.sender, tokenAddress, tokenAmount, userOpHash);
}

context =
abi.encode(
mode,
userOp.sender,
tokenAddress,
tokenAmount-((maxPenalty*tokenPrice*priceMarkup)/(_NATIVE_TOKEN_DECIMALS*_PRICE_DENOMINATOR)),
maxPenalty,
tokenPrice,
priceMarkup,
userOpHash
Expand All @@ -596,31 +584,48 @@ contract BiconomyTokenPaymaster is
)
internal
override
{
// Decode context data
(
address userOpSender,
address tokenAddress,
uint256 prechargedAmount,
uint256 tokenPrice,
uint32 appliedPriceMarkup,
bytes32 userOpHash
) = abi.decode(context, (address, address, uint256, uint256, uint32, bytes32));

// Calculate the actual cost in tokens based on the actual gas cost and the token price
uint256 actualTokenAmount = (
(actualGasCost + (unaccountedGas * actualUserOpFeePerGas)) * appliedPriceMarkup * tokenPrice
) / (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR);
if (prechargedAmount > actualTokenAmount) {
// If the user was overcharged, refund the excess tokens
uint256 refundAmount = prechargedAmount - actualTokenAmount;
SafeTransferLib.safeTransfer(tokenAddress, userOpSender, refundAmount);
emit TokensRefunded(userOpSender, tokenAddress, refundAmount, userOpHash);
}
{

PaymasterMode mode = PaymasterMode(uint8(context[0]));

emit PaidGasInTokens(
userOpSender, tokenAddress, actualGasCost, actualTokenAmount, appliedPriceMarkup, tokenPrice, userOpHash
);
if (mode == PaymasterMode.EXTERNAL) {
// Decode context data
(
address userOpSender,
address tokenAddress,
uint256 estimatedTokenAmount,
bytes32 userOpHash
) = abi.decode(context[1:], (address, address, uint256, bytes32));

if (SafeTransferLib.trySafeTransferFrom(tokenAddress, userOpSender, address(this), estimatedTokenAmount)) {
emit PaidGasInTokensExternal(userOpSender, tokenAddress, estimatedTokenAmount, userOpHash);
} else {
revert FailedToChargeTokens(userOpSender, tokenAddress, estimatedTokenAmount, userOpHash);
}

} else if (mode == PaymasterMode.INDEPENDENT) {
(
address userOpSender,
address tokenAddress,
uint256 maxPenalty,
uint256 tokenPrice,
uint32 appliedPriceMarkup,
bytes32 userOpHash
) = abi.decode(context[1:], (address, address, uint256, uint256, uint32, bytes32));
// Calculate the amount to charge. unaccountedGas and maxPenalty are used, as we do not know the exact gas spent for postop and actual penalty at this point
// this is obviously overcharge, however, the excess amount can be refunded by backend, when we know the exact gas spent (emitted by EP after executing UserOp)
uint256 tokenAmount = (
(actualGasCost + ((unaccountedGas + maxPenalty)) * actualUserOpFeePerGas)) * appliedPriceMarkup * tokenPrice
/ (_NATIVE_TOKEN_DECIMALS * _PRICE_DENOMINATOR);

if (SafeTransferLib.trySafeTransferFrom(tokenAddress, userOpSender, address(this), tokenAmount)) {
emit PaidGasInTokensIndependent(
userOpSender, tokenAddress, actualGasCost, tokenAmount, appliedPriceMarkup, tokenPrice, userOpHash
);
} else {
revert FailedToChargeTokens(userOpSender, tokenAddress, tokenAmount, userOpHash);
}
}
}

function _validateTokenInfo(TokenInfo memory tokenInfo) internal view {
Expand Down
11 changes: 4 additions & 7 deletions test/base/TestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
uint48 validUntil;
uint48 validAfter;
address tokenAddress;
uint256 tokenPrice;
uint32 externalPriceMarkup;
uint256 estimatedTokenAmount;
}

// Used to buffer user op gas limits
Expand Down Expand Up @@ -342,8 +341,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
pmData.validUntil,
pmData.validAfter,
pmData.tokenAddress,
pmData.tokenPrice,
pmData.externalPriceMarkup,
pmData.estimatedTokenAmount,
new bytes(65) // Zero signature
);

Expand All @@ -352,7 +350,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {

// Generate hash to be signed
bytes32 paymasterHash =
paymaster.getHash(userOp, pmData.validUntil, pmData.validAfter, pmData.tokenAddress, pmData.tokenPrice, pmData.externalPriceMarkup);
paymaster.getHash(userOp, pmData.validUntil, pmData.validAfter, pmData.tokenAddress, pmData.estimatedTokenAmount);

// Sign the hash
signature = signMessage(signer, paymasterHash);
Expand All @@ -367,8 +365,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
pmData.validUntil,
pmData.validAfter,
pmData.tokenAddress,
pmData.tokenPrice,
pmData.externalPriceMarkup,
pmData.estimatedTokenAmount,
signature
);
}
Expand Down
3 changes: 1 addition & 2 deletions test/mocks/PaymasterParserLibExposed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ library PaymasterParserLibExposed {
uint48 validUntil,
uint48 validAfter,
address tokenAddress,
uint256 tokenPrice,
uint32 externalPriceMarkup,
uint256 estimatedTokenAmount,
bytes calldata signature
) {
return modeSpecificData.parseExternalModeSpecificData();
Expand Down
5 changes: 2 additions & 3 deletions test/mocks/PaymasterParserLibWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ contract PaymasterParserLibWrapper {
uint48 validUntil,
uint48 validAfter,
address tokenAddress,
uint256 tokenPrice,
uint32 externalPriceMarkup,
uint256 estimatedTokenAmount,
bytes memory signature
) {
(validUntil, validAfter, tokenAddress, tokenPrice, externalPriceMarkup, signature) = modeSpecificData.parseExternalModeSpecificData();
(validUntil, validAfter, tokenAddress, estimatedTokenAmount, signature) = modeSpecificData.parseExternalModeSpecificData();
}

function parseIndependentModeSpecificData(bytes calldata modeSpecificData) external pure returns (address tokenAddress) {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/concrete/TestTokenPaymaster.Base.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ contract TestTokenPaymasterBase is TestBase {
emit IBiconomyTokenPaymaster.TokensRefunded(address(ALICE_ACCOUNT), address(usdc), 0, bytes32(0));

vm.expectEmit(true, true, false, false, address(tokenPaymaster));
emit IBiconomyTokenPaymaster.PaidGasInTokens(address(ALICE_ACCOUNT), address(usdc), 0, 0, 1e6, 0, bytes32(0));
emit IBiconomyTokenPaymaster.PaidGasInTokensIndependent(address(ALICE_ACCOUNT), address(usdc), 0, 0, 1e6, 0, bytes32(0));

uint256 customGasPrice = 3e6;
startPrank(BUNDLER.addr);
Expand Down
Loading

0 comments on commit 713d31d

Please sign in to comment.