-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Independent paymaster mode validation
- Loading branch information
1 parent
e747ec7
commit a4d56bb
Showing
5 changed files
with
117 additions
and
29 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
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
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,34 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity ^0.8.26; | ||
|
||
import { IBiconomyTokenPaymaster } from "../interfaces/IBiconomyTokenPaymaster.sol"; | ||
import "@account-abstraction/contracts/core/UserOperationLib.sol"; | ||
|
||
// A helper library to parse paymaster and data | ||
library PaymasterParser { | ||
uint256 private constant PAYMASTER_MODE_OFFSET = UserOperationLib.PAYMASTER_DATA_OFFSET; // Start offset of mode in | ||
// PND | ||
|
||
function parsePaymasterAndData(bytes calldata paymasterAndData) | ||
external | ||
pure | ||
returns (IBiconomyTokenPaymaster.PaymasterMode mode, bytes memory modeSpecificData) | ||
{ | ||
unchecked { | ||
mode = IBiconomyTokenPaymaster.PaymasterMode( | ||
uint8(bytes1(paymasterAndData[PAYMASTER_MODE_OFFSET:PAYMASTER_MODE_OFFSET + 8])) | ||
); | ||
modeSpecificData = paymasterAndData[PAYMASTER_MODE_OFFSET + 8:]; | ||
} | ||
} | ||
|
||
function parseExternalModeSpecificData(bytes calldata modeSpecificData) external pure { } | ||
|
||
function parseIndependentModeSpecificData(bytes calldata modeSpecificData) | ||
external | ||
pure | ||
returns (address tokenAddress) | ||
{ | ||
tokenAddress = address(bytes20(modeSpecificData[:20])); | ||
} | ||
} |
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 |
---|---|---|
|
@@ -4,12 +4,14 @@ pragma solidity ^0.8.26; | |
import { ReentrancyGuardTransient } from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; | ||
import { IEntryPoint } from "@account-abstraction/contracts/interfaces/IEntryPoint.sol"; | ||
import { PackedUserOperation, UserOperationLib } from "@account-abstraction/contracts/core/UserOperationLib.sol"; | ||
import { IERC20, ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
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 { IOracle } from "../interfaces/oracles/IOracle.sol"; | ||
import { PaymasterParser } from "../libraries/PaymasterParser.sol"; | ||
import "@account-abstraction/contracts/core/Helpers.sol"; | ||
|
||
/** | ||
|
@@ -18,8 +20,9 @@ import "@account-abstraction/contracts/core/Helpers.sol"; | |
* @author livingrockrises<[email protected]> | ||
* @notice Token Paymaster for Entry Point v0.7 | ||
* @dev A paymaster that allows user to pay gas fees in ERC20 tokens. The paymaster uses the precharge and refund model | ||
* to handle gas remittances. For fair and "always available" operation, it relies on price oracles which | ||
* implement the IOracle interface to calculate the gas cost of the transaction in a supported token. The owner has full | ||
* to handle gas remittances. For fair and "always available" operation, it supports a mode that relies purely on price | ||
* oracles (Offchain and TWAP) which | ||
* implement the IOracle interface to calculate the token cost of a transaction. The owner has full | ||
* discretion over the supported tokens, premium and discounts applied (if any), and how to manage the assets | ||
* received by the paymaster. The properties described above, make the paymaster self-relaint: independent of any | ||
* offchain service for use. | ||
|
@@ -31,6 +34,7 @@ contract BiconomyTokenPaymaster is | |
IBiconomyTokenPaymaster | ||
{ | ||
using UserOperationLib for PackedUserOperation; | ||
using PaymasterParser for bytes; | ||
|
||
// State variables | ||
address public feeCollector; | ||
|
@@ -40,9 +44,9 @@ contract BiconomyTokenPaymaster is | |
IOracle public nativeOracle; // ETH -> USD price | ||
mapping(address => TokenInfo) tokenDirectory; | ||
|
||
// Limit for unaccounted gas cost | ||
uint256 private constant UNACCOUNTED_GAS_LIMIT = 50_000; | ||
uint256 private constant PRICE_DENOMINATOR = 1e6; | ||
// PAYMASTER_ID_OFFSET | ||
uint256 private constant UNACCOUNTED_GAS_LIMIT = 50_000; // Limit for unaccounted gas cost | ||
uint256 private constant PRICE_DENOMINATOR = 1e6; // Denominator used when calculating cost with dynamic adjustment | ||
uint256 private constant MAX_DYNAMIC_ADJUSTMENT = 2e6; // 100% premium on price (2e6/PRICE_DENOMINATOR) | ||
|
||
constructor( | ||
|
@@ -53,7 +57,6 @@ contract BiconomyTokenPaymaster is | |
IOracle _nativeOracle, | ||
uint256 _priceExpiryDuration, | ||
address[] memory _tokens, // Array of token addresses | ||
uint8[] memory _decimals, // Array of corresponding token decimals | ||
IOracle[] memory _oracles // Array of corresponding oracle addresses | ||
) | ||
BasePaymaster(_owner, _entryPoint) | ||
|
@@ -62,19 +65,20 @@ contract BiconomyTokenPaymaster is | |
revert UnaccountedGasTooHigh(); | ||
} else if (_dynamicAdjustment > MAX_DYNAMIC_ADJUSTMENT || _dynamicAdjustment == 0) { | ||
revert InvalidDynamicAdjustment(); | ||
} else if (_tokens.length != _oracles.length || _tokens.length != _decimals.length) { | ||
} else if (_tokens.length != _oracles.length) { | ||
revert TokensAndInfoLengthMismatch(); | ||
} | ||
assembly ("memory-safe") { | ||
sstore(feeCollector.slot, address()) // initialize fee collector to this contract | ||
sstore(unaccountedGas.slot, _unaccountedGas) | ||
sstore(dynamicAdjustment.slot, _dynamicAdjustment) | ||
sstore(priceExpiryDuration.slot, _priceExpiryDuration) | ||
sstore(nativeOracle.slot, _nativeOracle) | ||
} | ||
|
||
// Populate the tokenToOracle mapping | ||
for (uint256 i = 0; i < _tokens.length; i++) { | ||
tokenDirectory[_tokens[i]] = TokenInfo(_oracles[i], _decimals[i]); | ||
tokenDirectory[_tokens[i]] = TokenInfo(_oracles[i], IERC20Metadata(_tokens[i]).decimals()); | ||
} | ||
} | ||
|
||
|
@@ -232,21 +236,12 @@ contract BiconomyTokenPaymaster is | |
* @dev Set or update a TokenInfo entry in the tokenDirectory mapping. | ||
* @param _tokenAddress The new value to be set as the unaccounted gas value | ||
* @param _oracle The new value to be set as the unaccounted gas value | ||
* @param _decimals The new value to be set as the unaccounted gas value | ||
* @notice only to be called by the owner of the contract. | ||
*/ | ||
function setTokenInfo( | ||
address _tokenAddress, | ||
IOracle _oracle, | ||
uint8 _decimals | ||
) | ||
external | ||
payable | ||
override | ||
onlyOwner | ||
{ | ||
tokenDirectory[_tokenAddress] = TokenInfo(_oracle, _decimals); | ||
emit UpdatedTokenDirectory(_tokenAddress, _oracle, _decimals); | ||
function setTokenInfo(address _tokenAddress, IOracle _oracle) external payable override onlyOwner { | ||
uint8 decimals = IERC20Metadata(_tokenAddress).decimals(); | ||
tokenDirectory[_tokenAddress] = TokenInfo(_oracle, decimals); | ||
emit UpdatedTokenDirectory(_tokenAddress, _oracle, decimals); | ||
} | ||
|
||
/** | ||
|
@@ -264,7 +259,41 @@ contract BiconomyTokenPaymaster is | |
internal | ||
override | ||
returns (bytes memory context, uint256 validationData) | ||
{ } | ||
{ | ||
(PaymasterMode mode, bytes memory modeSpecificData) = userOp.paymasterAndData.parsePaymasterAndData(); | ||
|
||
if (uint8(mode) > 1) { | ||
revert InvalidPaymasterMode(); | ||
} | ||
|
||
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 | ||
} else if (mode == PaymasterMode.INDEPENDENT) { | ||
// Use only oracles for the token specified in modeSpecificData | ||
if (modeSpecificData.length != 20) { | ||
revert InvalidTokenAddress(); | ||
} | ||
|
||
// Get address for token used to pay | ||
address tokenAddress = modeSpecificData.parseIndependentModeSpecificData(); | ||
uint192 tokenPrice = getPrice(tokenAddress); | ||
uint256 tokenAmount; | ||
|
||
{ | ||
// Calculate token amount to precharge | ||
uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas(userOp); | ||
tokenAmount = (maxCost + (unaccountedGas) * maxFeePerGas) * dynamicAdjustment * tokenPrice | ||
/ (1e18 * PRICE_DENOMINATOR); | ||
} | ||
|
||
// Transfer full amount to this address. Unused amount will be refunded in postOP | ||
SafeTransferLib.safeTransferFrom(tokenAddress, userOp.sender, address(this), tokenAmount); | ||
|
||
context = abi.encodePacked(tokenAmount, tokenPrice, userOp.sender, userOpHash); | ||
validationData = 0; | ||
} | ||
} | ||
|
||
/** | ||
* @dev Post-operation handler. | ||
|
@@ -292,14 +321,19 @@ contract BiconomyTokenPaymaster is | |
} | ||
|
||
/// @notice Fetches the latest token price. | ||
|
||
/// @return price The latest token price fetched from the oracles. | ||
function getPrice(address tokenAddress) internal view returns (uint192) { | ||
function getPrice(address tokenAddress) internal view returns (uint192 price) { | ||
TokenInfo memory tokenInfo = tokenDirectory[tokenAddress]; | ||
|
||
if (address(tokenInfo.oracle) == address(0)) { | ||
// If oracle not set, token isn't supported | ||
revert TokenNotSupported(); | ||
} | ||
|
||
uint192 tokenPrice = _fetchPrice(tokenInfo.oracle); | ||
uint192 nativeAssetPrice = _fetchPrice(nativeOracle); | ||
uint192 price = nativeAssetPrice * uint192(tokenInfo.decimals) / tokenPrice; | ||
return price; | ||
|
||
price = nativeAssetPrice * uint192(tokenInfo.decimals) / tokenPrice; | ||
} | ||
|
||
/// @notice Fetches the latest price from the given oracle. | ||
|
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