Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/token paymaster test refactor #34

Merged
merged 13 commits into from
Nov 5, 2024
2 changes: 1 addition & 1 deletion contracts/libraries/TokenPaymasterParserLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ library TokenPaymasterParserLib {
uint48 validUntil,
uint48 validAfter,
address tokenAddress,
uint128 tokenPrice,
uint128 tokenPrice, // Review: why uint128 and not uint256. in independent mode it is uint256
uint32 externalPriceMarkup,
bytes memory signature
)
Expand Down
55 changes: 44 additions & 11 deletions contracts/token/BiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import { ECDSA as ECDSA_solady } from "solady/utils/ECDSA.sol";
import "account-abstraction/core/Helpers.sol";
import "./swaps/Uniswapper.sol";
// Todo: marked for removal
import "forge-std/console2.sol";

Check failure on line 20 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected import of console file
Comment on lines +19 to +20
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you want to remove it after audit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I will


/**
* @title BiconomyTokenPaymaster
Expand Down Expand Up @@ -56,7 +58,8 @@
// supported in // independent mode

// PAYMASTER_ID_OFFSET
uint256 private constant _UNACCOUNTED_GAS_LIMIT = 50_000; // Limit for unaccounted gas cost
// Note: Temp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it for testing purposes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, haven't gotten to right accounting yet.

uint256 private constant _UNACCOUNTED_GAS_LIMIT = 200_000; // Limit for unaccounted gas cost
uint256 private constant _PRICE_DENOMINATOR = 1e6; // Denominator used when calculating cost with price markup
uint256 private constant _MAX_PRICE_MARKUP = 2e6; // 100% premium on price (2e6/PRICE_DENOMINATOR)

Expand Down Expand Up @@ -123,6 +126,11 @@
independentTokenDirectory[independentTokensArg[i]] =
TokenInfo(oraclesArg[i], 10 ** IERC20Metadata(independentTokensArg[i]).decimals());
}
// Approve swappable tokens for max amount
uint256 length = swappableTokens.length;
for (uint256 i; i < length; i++) {
IERC20(swappableTokens[i]).approve(address(uniswapRouterArg), type(uint256).max);
}
}

/**
Expand Down Expand Up @@ -338,10 +346,12 @@
{
// Swap tokens for WETH
uint256 amountOut = _swapTokenToWeth(tokenAddress, tokenAmount, minEthAmountRecevied);
// Unwrap WETH to ETH
_unwrapWeth(amountOut);
// Deposit ETH into EP
entryPoint.depositTo{ value: amountOut }(address(this));
if(amountOut > 0) {
// Unwrap WETH to ETH
_unwrapWeth(amountOut);
// Deposit ETH into EP
entryPoint.depositTo{ value: amountOut }(address(this));
}
}

/**
Expand Down Expand Up @@ -412,7 +422,7 @@
* @param userOpHash The hash of the user operation.
* @param maxCost The maximum cost of the user operation.
*/
function _validatePaymasterUserOp(

Check failure on line 425 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Function body contains 96 lines but allowed no more than 90 lines
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 maxCost
Expand All @@ -427,6 +437,14 @@
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 @@ -435,7 +453,8 @@
uint48 validUntil,
uint48 validAfter,
address tokenAddress,
uint128 tokenPrice,
// Review if uint128 is enough
uint128 tokenPrice, // NotE: what backend should pass is token/native * 10^token decimals
uint32 externalPriceMarkup,
bytes memory signature
) = modeSpecificData.parseExternalModeSpecificData();
Expand All @@ -460,10 +479,12 @@
revert InvalidPriceMarkup();
}


uint256 tokenAmount;
// Review
{
uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas(userOp);
tokenAmount = ((maxCost + (unaccountedGas) * maxFeePerGas) * externalPriceMarkup * tokenPrice)
tokenAmount = ((maxCost + maxPenalty + (unaccountedGas * maxFeePerGas)) * externalPriceMarkup * tokenPrice)
/ (1e18 * _PRICE_DENOMINATOR);
}

Expand All @@ -480,13 +501,14 @@

// Get address for token used to pay
address tokenAddress = modeSpecificData.parseIndependentModeSpecificData();
uint192 tokenPrice = _getPrice(tokenAddress);
uint256 tokenPrice = _getPrice(tokenAddress);
uint256 tokenAmount;

// TODO: Account for penalties here
{
// Calculate token amount to precharge
uint256 maxFeePerGas = UserOperationLib.unpackMaxFeePerGas(userOp);
tokenAmount = ((maxCost + (unaccountedGas) * maxFeePerGas) * independentPriceMarkup * tokenPrice)
tokenAmount = ((maxCost + maxPenalty + (unaccountedGas * maxFeePerGas)) * independentPriceMarkup * tokenPrice)
/ (1e18 * _PRICE_DENOMINATOR);
}

Expand Down Expand Up @@ -529,6 +551,12 @@
uint256 actualTokenAmount = (
(actualGasCost + (unaccountedGas * actualUserOpFeePerGas)) * appliedPriceMarkup * tokenPrice
) / (1e18 * _PRICE_DENOMINATOR);
console2.log("tokenPrice", tokenPrice);

Check failure on line 554 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected console statement
console2.log("appliedPriceMarkup", appliedPriceMarkup);

Check failure on line 555 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected console statement
console2.log("actualGasCost", actualGasCost);

Check failure on line 556 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected console statement
console2.log("actualUserOpFeePerGas", actualUserOpFeePerGas);

Check failure on line 557 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected console statement
console2.log("actualTokenAmount", actualTokenAmount);

Check failure on line 558 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected console statement
console2.log("prechargedAmount", prechargedAmount);

Check failure on line 559 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected console statement

if (prechargedAmount > actualTokenAmount) {
// If the user was overcharged, refund the excess tokens
Expand All @@ -537,14 +565,15 @@
emit TokensRefunded(userOpSender, tokenAddress, refundAmount, userOpHash);
}

// Todo: Review events and what we need to emit.
emit PaidGasInTokens(
userOpSender, tokenAddress, actualGasCost, actualTokenAmount, appliedPriceMarkup, userOpHash
);
}

/// @notice Fetches the latest token price.
/// @return price The latest token price fetched from the oracles.
function _getPrice(address tokenAddress) internal view returns (uint192 price) {
function _getPrice(address tokenAddress) internal view returns (uint256 price) {
// Fetch token information from directory
TokenInfo memory tokenInfo = independentTokenDirectory[tokenAddress];

Expand All @@ -556,15 +585,19 @@
// Calculate price by using token and native oracle
uint192 tokenPrice = _fetchPrice(tokenInfo.oracle);
uint192 nativeAssetPrice = _fetchPrice(nativeAssetToUsdOracle);
console2.log("tokenPrice oracle", tokenPrice);

Check failure on line 588 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected console statement
console2.log("nativeAssetPrice oracle", nativeAssetPrice);

Check failure on line 589 in contracts/token/BiconomyTokenPaymaster.sol

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected console statement

// Adjust to token decimals
price = (nativeAssetPrice * uint192(tokenInfo.decimals)) / tokenPrice;
price = (nativeAssetPrice * tokenInfo.decimals) / tokenPrice;
console2.log("derived & used price", price);
}

/// @notice Fetches the latest price from the given oracle.
/// @dev This function is used to get the latest price from the tokenOracle or nativeAssetToUsdOracle.
/// @param oracle The oracle contract to fetch the price from.
/// @return price The latest price fetched from the oracle.
/// Note: We could do this using oracle aggregator, so we can also use Pyth. or Twap based oracle and just not chainlink.
function _fetchPrice(IOracle oracle) internal view returns (uint192 price) {
(, int256 answer,, uint256 updatedAt,) = oracle.latestRoundData();
if (answer <= 0) {
Expand Down
12 changes: 10 additions & 2 deletions contracts/token/swaps/Uniswapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ abstract contract Uniswapper {
tokenToPools[token] = poolFeeTier; // set mapping of token to uniswap pool to use for swap
}

function _swapTokenToWeth(address tokenIn, uint256 amountIn, uint256 minAmountOut) internal returns (uint256) {
function _swapTokenToWeth(address tokenIn, uint256 amountIn, uint256 minAmountOut) internal returns (uint256 amountOut) {
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: wrappedNative,
Expand All @@ -63,10 +63,18 @@ abstract contract Uniswapper {
amountOutMinimum: minAmountOut,
sqrtPriceLimitX96: 0
});
return uniswapRouter.exactInputSingle(params);

try uniswapRouter.exactInputSingle(params) returns (uint256 _amountOut) {
amountOut = _amountOut;
} catch {
// Review could emit an event here
// Uniswap Reverted
amountOut = 0;
}
}

function _unwrapWeth(uint256 amount) internal {
if(amount == 0) return;
IPeripheryPayments(address(uniswapRouter)).unwrapWETH9(amount, address(this));
}
}
Loading
Loading