diff --git a/contracts/interfaces/paymasters/IBiconomyTokenPaymaster.sol b/contracts/interfaces/paymasters/IBiconomyTokenPaymaster.sol index 7dcb253..a2d0482 100644 --- a/contracts/interfaces/paymasters/IBiconomyTokenPaymaster.sol +++ b/contracts/interfaces/paymasters/IBiconomyTokenPaymaster.sol @@ -32,6 +32,8 @@ interface IBiconomyTokenPaymaster { */ event FeeReceiverChanged(address indexed _oldfeeReceiver, address indexed _newfeeReceiver, address indexed _actor); + event TokensWithdrawn(address indexed _token, address indexed _to, uint256 indexed _amount, address actor); + /** * Designed to enable tracking how much fees were charged from the sender and in which ERC20 token * More information can be emitted like exchangeRate used, what was the source of exchangeRate etc diff --git a/contracts/libs/MathLib.sol b/contracts/libs/MathLib.sol index cfc5db7..04c0796 100644 --- a/contracts/libs/MathLib.sol +++ b/contracts/libs/MathLib.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.23; library MathLib { function minuint256(uint256 a, uint256 b) internal pure returns (uint256 result) { assembly { - result := xor(b, mul(xor(b, a), gt(a, b))) - } + result := xor(b, mul(xor(b, a), gt(b, a))) } +} function maxuint256(uint256 a, uint256 b) internal pure returns (uint256 result) { assembly { @@ -14,11 +14,11 @@ library MathLib { } } - function minuint32(uint32 a, uint32 b) internal pure returns (uint32 result) { + function minuint32(uint32 a, uint32 b) internal pure returns (uint32 result) { assembly { - result := xor(b, mul(xor(b, a), gt(a, b))) - } + result := xor(b, mul(xor(b, a), gt(b, a))) } +} function maxuint32(uint32 a, uint32 b) internal pure returns (uint32 result) { assembly { diff --git a/contracts/token/BiconomyTokenPaymaster.sol b/contracts/token/BiconomyTokenPaymaster.sol index b09866d..9f6bed0 100644 --- a/contracts/token/BiconomyTokenPaymaster.sol +++ b/contracts/token/BiconomyTokenPaymaster.sol @@ -14,6 +14,7 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@account-abstraction/contracts/core/Helpers.sol" as Helpers; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import "../utils/SafeTransferLib.sol"; +import {MathLib} from "../libs/MathLib.sol"; import {TokenPaymasterErrors} from "./TokenPaymasterErrors.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import {OracleAggregator} from "./oracles/OracleAggregator.sol"; @@ -79,6 +80,10 @@ contract BiconomyTokenPaymaster is } } + receive() external payable { + emit Received(msg.sender, msg.value); + } + /** * @dev Set a new verifying signer address. * Can only be called by the owner of the contract. @@ -180,11 +185,12 @@ contract BiconomyTokenPaymaster is onlyOwner nonReentrant { - if (token.length != amount.length) { + uint256 tokLen = token.length; + if (tokLen != amount.length) { revert TokensAndAmountsLengthMismatch(); } unchecked { - for (uint256 i; i < token.length;) { + for (uint256 i; i < tokLen;) { _withdrawERC20(token[i], target, amount[i]); ++i; } @@ -198,7 +204,8 @@ contract BiconomyTokenPaymaster is */ function withdrawMultipleERC20Full(IERC20[] calldata token, address target) public payable onlyOwner nonReentrant { unchecked { - for (uint256 i; i < token.length;) { + uint256 tokLen = token.length; + for (uint256 i; i < tokLen;) { uint256 amount = token[i].balanceOf(address(this)); _withdrawERC20(token[i], target, amount); ++i; @@ -285,71 +292,6 @@ contract BiconomyTokenPaymaster is signature = paymasterAndData[VALID_PND_OFFSET + 53:]; } - function _getRequiredPrefund(UserOperation calldata userOp) internal view returns (uint256 requiredPrefund) { - unchecked { - uint256 requiredGas = - userOp.callGasLimit + userOp.verificationGasLimit + userOp.preVerificationGas + unaccountedEPGasOverhead; - - requiredPrefund = requiredGas * userOp.maxFeePerGas; - } - } - - /** - * @dev Verify that an external signer signed the paymaster data of a user operation. - * The paymaster data is expected to be the paymaster address, request data and a signature over the entire request parameters. - * paymasterAndData: hexConcat([paymasterAddress, priceSource, abi.encode(validUntil, validAfter, feeToken, exchangeRate, priceMarkup), signature]) - * @param userOp The UserOperation struct that represents the current user operation. - * userOpHash The hash of the UserOperation struct. - * @param requiredPreFund The required amount of pre-funding for the paymaster. - * @return context A context string returned by the entry point after successful validation. - * @return validationData An integer returned by the entry point after successful validation. - */ - function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 requiredPreFund) - internal - view - override - returns (bytes memory context, uint256 validationData) - { - (requiredPreFund); - - ( - ExchangeRateSource priceSource, - uint48 validUntil, - uint48 validAfter, - address feeToken, - uint128 exchangeRate, - uint32 priceMarkup, - bytes calldata signature - ) = parsePaymasterAndData(userOp.paymasterAndData); - - bytes32 _hash = getHash(userOp, priceSource, validUntil, validAfter, feeToken, exchangeRate, priceMarkup) - .toEthSignedMessageHash(); - - //don't revert on signature failure: return SIG_VALIDATION_FAILED - if (verifyingSigner != _hash.recover(signature)) { - // empty context and sigFailed true - return (context, Helpers._packValidationData(true, validUntil, validAfter)); - } - - address account = userOp.getSender(); - - // This model assumes irrespective of priceSource exchangeRate is always sent from outside - // for below checks you would either need maxCost or some exchangeRate - - uint256 btpmRequiredPrefund = _getRequiredPrefund(userOp); - - uint256 tokenRequiredPreFund = (btpmRequiredPrefund * exchangeRate) / 10 ** 18; - require(priceMarkup <= 2e6, "BTPM: price markup percentage too high"); - require( - IERC20(feeToken).balanceOf(account) >= ((tokenRequiredPreFund * priceMarkup) / PRICE_DENOMINATOR), - "BTPM: account does not have enough token balance" - ); - - context = abi.encode(account, feeToken, priceSource, exchangeRate, priceMarkup, userOpHash); - - return (context, Helpers._packValidationData(false, validUntil, validAfter)); - } - /** * @dev Executes the paymaster's payment conditions * @param mode tells whether the op succeeded, reverted, or if the op succeeded but cause the postOp to revert @@ -362,6 +304,8 @@ contract BiconomyTokenPaymaster is ExchangeRateSource priceSource; uint128 exchangeRate; uint32 priceMarkup; + uint256 maxFeePerGas; + uint256 maxPriorityFeePerGas; bytes32 userOpHash; assembly ("memory-safe") { let offset := context.offset @@ -381,6 +325,12 @@ contract BiconomyTokenPaymaster is priceMarkup := calldataload(offset) offset := add(offset, 0x20) + maxFeePerGas := calldataload(offset) + offset := add(offset, 0x20) + + maxPriorityFeePerGas := calldataload(offset) + offset := add(offset, 0x20) + userOpHash := calldataload(offset) } @@ -391,11 +341,16 @@ contract BiconomyTokenPaymaster is if (result != 0) effectiveExchangeRate = result; } + uint256 effectiveGasPrice = getGasPrice( + maxFeePerGas, + maxPriorityFeePerGas + ); + // We could either touch the state for BASEFEE and calculate based on maxPriorityFee passed (to be added in context along with maxFeePerGas) or just use tx.gasprice uint256 charge; // Final amount to be charged from user account { uint256 actualTokenCost = - ((actualGasCost + (unaccountedEPGasOverhead * tx.gasprice)) * effectiveExchangeRate) / 1e18; + ((actualGasCost + (unaccountedEPGasOverhead * effectiveGasPrice)) * effectiveExchangeRate) / 1e18; charge = ((actualTokenCost * priceMarkup) / PRICE_DENOMINATOR); } @@ -425,12 +380,90 @@ contract BiconomyTokenPaymaster is } } + function _getRequiredPrefund(UserOperation calldata userOp) internal view returns (uint256 requiredPrefund) { + unchecked { + uint256 requiredGas = + userOp.callGasLimit + userOp.verificationGasLimit + userOp.preVerificationGas + unaccountedEPGasOverhead; + + requiredPrefund = requiredGas * userOp.maxFeePerGas; + } + } + + // Note: do not use this in validation phase + function getGasPrice( + uint256 maxFeePerGas, + uint256 maxPriorityFeePerGas + ) internal view returns (uint256) { + if (maxFeePerGas == maxPriorityFeePerGas) { + //legacy mode (for networks that don't support basefee opcode) + return maxFeePerGas; + } + return + MathLib.minuint256( + maxFeePerGas, + maxPriorityFeePerGas + block.basefee + ); + } + + /** + * @dev Verify that an external signer signed the paymaster data of a user operation. + * The paymaster data is expected to be the paymaster address, request data and a signature over the entire request parameters. + * paymasterAndData: hexConcat([paymasterAddress, priceSource, abi.encode(validUntil, validAfter, feeToken, exchangeRate, priceMarkup), signature]) + * @param userOp The UserOperation struct that represents the current user operation. + * userOpHash The hash of the UserOperation struct. + * @param requiredPreFund The required amount of pre-funding for the paymaster. + * @return context A context string returned by the entry point after successful validation. + * @return validationData An integer returned by the entry point after successful validation. + */ + function _validatePaymasterUserOp(UserOperation calldata userOp, bytes32 userOpHash, uint256 requiredPreFund) + internal + view + override + returns (bytes memory context, uint256 validationData) + { + (requiredPreFund); + + ( + ExchangeRateSource priceSource, + uint48 validUntil, + uint48 validAfter, + address feeToken, + uint128 exchangeRate, + uint32 priceMarkup, + bytes calldata signature + ) = parsePaymasterAndData(userOp.paymasterAndData); + + bytes32 _hash = getHash(userOp, priceSource, validUntil, validAfter, feeToken, exchangeRate, priceMarkup) + .toEthSignedMessageHash(); + + //don't revert on signature failure: return SIG_VALIDATION_FAILED + if (verifyingSigner != _hash.recover(signature)) { + // empty context and sigFailed true + return (context, Helpers._packValidationData(true, validUntil, validAfter)); + } + + address account = userOp.getSender(); + + // This model assumes irrespective of priceSource exchangeRate is always sent from outside + // for below checks you would either need maxCost or some exchangeRate + + uint256 btpmRequiredPrefund = _getRequiredPrefund(userOp); + + uint256 tokenRequiredPreFund = (btpmRequiredPrefund * exchangeRate) / 10 ** 18; + require(priceMarkup <= 2e6, "BTPM: price markup percentage too high"); + require( + IERC20(feeToken).balanceOf(account) >= ((tokenRequiredPreFund * priceMarkup) / PRICE_DENOMINATOR), + "BTPM: Insufficient token balance" + ); + + context = abi.encode(account, feeToken, priceSource, exchangeRate, priceMarkup, userOp.maxFeePerGas, userOp.maxPriorityFeePerGas, userOpHash); + + return (context, Helpers._packValidationData(false, validUntil, validAfter)); + } + function _withdrawERC20(IERC20 token, address target, uint256 amount) private { if (target == address(0)) revert CanNotWithdrawToZeroAddress(); SafeTransferLib.safeTransfer(address(token), target, amount); - } - - receive() external payable { - emit Received(msg.sender, msg.value); + emit TokensWithdrawn(address(token), target, amount, msg.sender); } } diff --git a/contracts/token/oracles/IOracleAggregator.sol b/contracts/token/oracles/IOracleAggregator.sol index 71cce8e..2b495ee 100644 --- a/contracts/token/oracles/IOracleAggregator.sol +++ b/contracts/token/oracles/IOracleAggregator.sol @@ -2,18 +2,19 @@ pragma solidity ^0.8.23; interface IOracleAggregator { - error MismatchInBaseAndQuoteDecimals(); - error InvalidPriceFromRound(); - error LatestRoundIncomplete(); - error PriceFeedStale(); - error OracleAddressCannotBeZero(); - struct TokenInfo { uint8 tokenDecimals; + uint24 priceUpdateThreshold; address tokenOracle; address nativeOracle; bool isDerivedFeed; } + error MismatchInBaseAndQuoteDecimals(); + error InvalidPriceFromRound(); + error LatestRoundIncomplete(); + error PriceFeedStale(); + error OracleAddressCannotBeZero(); + function getTokenValueOfOneNativeToken(address _token) external view returns (uint128 exchangeRate); } diff --git a/contracts/token/oracles/OracleAggregator.sol b/contracts/token/oracles/OracleAggregator.sol index 9f3a720..5eabbc4 100644 --- a/contracts/token/oracles/OracleAggregator.sol +++ b/contracts/token/oracles/OracleAggregator.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.23; import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "./IOracleAggregator.sol"; import "./FeedInterface.sol"; @@ -14,10 +15,10 @@ abstract contract OracleAggregator is Ownable, IOracleAggregator { function setTokenOracle( address token, - uint8 tokenDecimals, address tokenOracle, address nativeOracle, - bool isDerivedFeed + bool isDerivedFeed, + uint24 priceUpdateThreshold ) external onlyOwner { if (tokenOracle == address(0)) revert OracleAddressCannotBeZero(); if (nativeOracle == address(0)) revert OracleAddressCannotBeZero(); @@ -27,8 +28,9 @@ abstract contract OracleAggregator is Ownable, IOracleAggregator { if (decimals1 != decimals2) revert MismatchInBaseAndQuoteDecimals(); tokensInfo[token].tokenOracle = tokenOracle; tokensInfo[token].nativeOracle = nativeOracle; - tokensInfo[token].tokenDecimals = tokenDecimals; + tokensInfo[token].tokenDecimals = ERC20(token).decimals(); tokensInfo[token].isDerivedFeed = isDerivedFeed; + tokensInfo[token].priceUpdateThreshold = priceUpdateThreshold; } /** @@ -50,18 +52,19 @@ abstract contract OracleAggregator is Ownable, IOracleAggregator { view returns (uint256 tokenPrice, uint8 tokenOracleDecimals, uint8 tokenDecimals, bool isError) { - TokenInfo storage tokenInfo = tokensInfo[token]; + TokenInfo memory tokenInfo = tokensInfo[token]; tokenDecimals = tokenInfo.tokenDecimals; + uint24 priceUpdateThreshold = tokenInfo.priceUpdateThreshold; if (tokenInfo.isDerivedFeed) { - (uint256 price1, bool isError1) = fetchPrice(FeedInterface(tokenInfo.nativeOracle)); - (uint256 price2, bool isError2) = fetchPrice(FeedInterface(tokenInfo.tokenOracle)); + (uint256 price1, bool isError1) = fetchPrice(FeedInterface(tokenInfo.nativeOracle), priceUpdateThreshold); + (uint256 price2, bool isError2) = fetchPrice(FeedInterface(tokenInfo.tokenOracle), priceUpdateThreshold); isError = isError1 || isError2; if (isError) return (0, 0, 0, isError); tokenPrice = (price2 * (10 ** 18)) / price1; tokenOracleDecimals = 18; } else { - (tokenPrice, isError) = fetchPrice(FeedInterface(tokenInfo.tokenOracle)); + (tokenPrice, isError) = fetchPrice(FeedInterface(tokenInfo.tokenOracle), priceUpdateThreshold); tokenOracleDecimals = FeedInterface(tokenInfo.tokenOracle).decimals(); } } @@ -70,17 +73,18 @@ abstract contract OracleAggregator is Ownable, IOracleAggregator { * @dev This function is used to get the latest price from the tokenOracle or nativeOracle. * @notice Fetches the latest price from the given Oracle. * @param _oracle The Oracle contract to fetch the price from. + * @param _priceUpdateThreshold The time after which the price is considered stale. * @return price The latest price fetched from the Oracle. */ - function fetchPrice(FeedInterface _oracle) internal view returns (uint256 price, bool isError) { + function fetchPrice(FeedInterface _oracle, uint24 _priceUpdateThreshold) internal view returns (uint256 price, bool isError) { try _oracle.latestRoundData() returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound ) { // validateRound if (answer <= 0) return (0, true); - // 2 days old price is considered stale since the price is updated every 24 hours - if (updatedAt < block.timestamp - 60 * 60 * 24 * 2) return (0, true); - if (answeredInRound < roundId) return (0, true); + // price older than set _priceUpdateThreshold is considered stale + // _priceUpdateThreshold for oracle feed is usually heartbeat interval + block time + buffer + if (updatedAt < block.timestamp - _priceUpdateThreshold) return (0, true); price = uint256(answer); return (price, false); } catch Error(string memory reason) { diff --git a/scripts/configs/prod/token-config-mumbai.json b/scripts/configs/prod/token-config-mumbai.json index 14e1859..3ca5b80 100644 --- a/scripts/configs/prod/token-config-mumbai.json +++ b/scripts/configs/prod/token-config-mumbai.json @@ -26,7 +26,8 @@ "address": "0xE03489D4E90b22c59c5e23d45DFd59Fc0dB8a025", "nativeOracleAddress": "0xd0D5e3DB44DE05E9F294BB0a3bEEaF030DE24Ada", "tokenOracleAddress": "0x9dd18534b8f456557d11B9DDB14dA89b2e52e308", - "derivedFeed": true + "derivedFeed": true, + "priceUpdateThreshold": 43200 } ] } diff --git a/scripts/set-token-oracle-chainlink-aggregator.ts b/scripts/set-token-oracle-chainlink-aggregator.ts deleted file mode 100644 index d6f5593..0000000 --- a/scripts/set-token-oracle-chainlink-aggregator.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ethers } from "hardhat"; - -function delay(ms: number) { - return new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, ms); - }); -} - -// WIP on existing oracle aggregator given price feeds are already deployed go ahead and set oracle aggregator - -async function main() { - let tx, receipt; - const provider = ethers.provider; - - // Polygon Mainnet example - - const chainlinkAggregatorAddress = - "0xDc1c19fB74aC9dD6C7b1fafb7bF604F8277e4927"; - - // example - const sandAddress = "0xBbba073C31bF03b8ACf7c28EF0738DeCF3695683"; - const sandPriceFeedAddress = "0x985eBa99eB4612B97E94060bBc582B209c7Be28d"; - - const gasPrices = { maxFeePerGas: 250e9, maxPriorityFeePerGas: 80e9 }; - const oracleAggregator = await ethers.getContractAt( - "contracts/token/oracles/ChainlinkOracleAggregator.sol:ChainlinkOracleAggregator", - chainlinkAggregatorAddress - ); - - const priceFeedSand = await ethers.getContractAt( - "FeedInterface", - sandPriceFeedAddress - ); - - const priceFeedTxSand: any = - await priceFeedSand.populateTransaction.getThePrice(); - - console.log("priceFeedTxSand ", priceFeedTxSand); - - tx = await oracleAggregator.setTokenOracle( - sandAddress, - sandPriceFeedAddress, - 18, - priceFeedTxSand.data, - true - ); - receipt = await tx.wait(); - console.log("Oracle set for SAND"); - await delay(10000); -} - -// We recommend this pattern to be able to use async/await everywhere -// and properly handle errors. -main().catch((error) => { - console.error(error); - process.exitCode = 1; -}); diff --git a/scripts/token-paymaster-v2/deploy-token-paymaster-mumbai.ts b/scripts/token-paymaster-v2/deploy-token-paymaster-mumbai.ts index 2ff6085..5df02f5 100644 --- a/scripts/token-paymaster-v2/deploy-token-paymaster-mumbai.ts +++ b/scripts/token-paymaster-v2/deploy-token-paymaster-mumbai.ts @@ -11,7 +11,6 @@ import { BiconomyTokenPaymaster__factory, Deployer, Deployer__factory, - ERC20__factory, } from "../../typechain-types"; import { mumbaiConfigInfoProd } from "../configs"; import { TokenConfig } from "../utils/Types"; @@ -127,18 +126,18 @@ async function getPredeployedDeployerContractInstance(): Promise { async function setTokenOracle( tokenPaymasterInstance: BiconomyTokenPaymaster, tokenAddress: string, - tokenDecimals: number, tokenOracle: string, nativeOracle: string, - isDerivedFeed: boolean + isDerivedFeed: boolean, + priceUpdateThreshold: number = 172800 // 2 days ) { // Connect as the owner of the token paymaster const tx = await tokenPaymasterInstance.setTokenOracle( tokenAddress, - tokenDecimals, tokenOracle, nativeOracle, - isDerivedFeed + isDerivedFeed, + priceUpdateThreshold ); const receipt = await tx.wait(); console.log( @@ -171,11 +170,6 @@ async function getTokenPaymasterContractInstance( } } -async function getERC20TokenInstance(tokenAddress: string) { - const [signer] = await ethers.getSigners(); - return ERC20__factory.connect(tokenAddress, signer); -} - async function main() { const accounts = await ethers.getSigners(); const earlyOwner = await accounts[0].getAddress(); @@ -216,22 +210,23 @@ async function main() { derivedFeed, } = token; - let tokenDecimals = 18; + let priceUpdateThreshold = token.priceUpdateThreshold; - if (address) { - const tokenInstance = await getERC20TokenInstance(address); - tokenDecimals = await tokenInstance.decimals(); - } else { + if (priceUpdateThreshold === null || priceUpdateThreshold === undefined) { + priceUpdateThreshold = 172800; // 2 days default + } + + if (!address) { throw new Error("token address can not be undefined"); } if (tokenPaymasterInstance) { await setTokenOracle( tokenPaymasterInstance, address, - tokenDecimals, nativeOracleAddress, tokenOracleAddress, - derivedFeed + derivedFeed, + priceUpdateThreshold ); } } diff --git a/scripts/utils/Types.ts b/scripts/utils/Types.ts index dd07982..8a05dc8 100644 --- a/scripts/utils/Types.ts +++ b/scripts/utils/Types.ts @@ -4,6 +4,7 @@ export interface Token { tokenOracleAddress: string; symbol: string; derivedFeed: boolean; + priceUpdateThreshold?: number; // Add any other required properties for each token } diff --git a/scripts/utils/index.ts b/scripts/utils/index.ts index 64153cb..aec806c 100644 --- a/scripts/utils/index.ts +++ b/scripts/utils/index.ts @@ -31,7 +31,7 @@ export enum DEPLOYMENT_SALTS { // PROD ORACLE_AGGREGATOR = "PROD_CHAINLINK_ORACLE_AGGREGATOR_V0_27062023_UT8R11e", // 0x00000f7748595e46527413574a9327942e744e91 TOKEN_PAYMASTER = "PROD_TOKEN_PAYMASTER_V0_08072023_cONP4xM", // 0x00000f7365ca6c59a2c93719ad53d567ed49c14c - TOKEN_PAYMASTER_V2 = "PROD_TOKEN_PAYMASTER_V2_04022024_cONP4xM", // 0x00000f7365ca6c59a2c93719ad53d567ed49c14c + TOKEN_PAYMASTER_V2 = "PROD_TOKEN_PAYMASTER_V2_07022024_cONP4xM", // when using deployer DEV // ORACLE_AGGREGATOR = "DEVX_CHAINLINK_ORACLE_AGGREGATOR_V0_27062023_bBee55b", // 0x0000065b8abb967271817555f23945eedf08015c diff --git a/test/bundler-integration/token-paymaster/biconomy-token-paymaster-specs.ts b/test/bundler-integration/token-paymaster/biconomy-token-paymaster-specs.ts index e9b15ce..811cf21 100644 --- a/test/bundler-integration/token-paymaster/biconomy-token-paymaster-specs.ts +++ b/test/bundler-integration/token-paymaster/biconomy-token-paymaster-specs.ts @@ -151,10 +151,10 @@ describe("Biconomy Token Paymaster (with Bundler)", function () { await sampleTokenPaymaster.setTokenOracle( token.address, - await token.decimals(), tokenOracle.address, nativeOracle.address, - true + true, + 172800 // 2 days ); const priceResult = diff --git a/test/bundler-integration/token-paymaster/btpm-undeployed-wallet.ts b/test/bundler-integration/token-paymaster/btpm-undeployed-wallet.ts index f866441..5fbd2cb 100644 --- a/test/bundler-integration/token-paymaster/btpm-undeployed-wallet.ts +++ b/test/bundler-integration/token-paymaster/btpm-undeployed-wallet.ts @@ -153,10 +153,10 @@ describe("Biconomy Token Paymaster (with Bundler)", function () { await sampleTokenPaymaster.setTokenOracle( token.address, - await token.decimals(), tokenOracle.address, nativeOracle.address, - true + true, + 172800 // 2 days ); const priceResult = diff --git a/test/bundler-integration/token-paymaster/oracle-aggregator-specs.ts b/test/bundler-integration/token-paymaster/oracle-aggregator-specs.ts index 5bbd4ac..55683df 100644 --- a/test/bundler-integration/token-paymaster/oracle-aggregator-specs.ts +++ b/test/bundler-integration/token-paymaster/oracle-aggregator-specs.ts @@ -139,10 +139,10 @@ describe("Biconomy Token Paymaster (With Bundler)", function () { await sampleTokenPaymaster.setTokenOracle( token.address, - await token.decimals(), tokenOracle.address, nativeOracle.address, - true + true, + 172800 // 2 days ); const priceResult = await sampleTokenPaymaster.getTokenValueOfOneNativeToken(token.address); diff --git a/test/foundry/token-paymaster/TokenPaymaster.t.sol b/test/foundry/token-paymaster/TokenPaymaster.t.sol index cf7670b..34d6e0a 100644 --- a/test/foundry/token-paymaster/TokenPaymaster.t.sol +++ b/test/foundry/token-paymaster/TokenPaymaster.t.sol @@ -24,6 +24,7 @@ import {MockToken} from "../../../contracts/test/helpers/MockToken.sol"; import {MockOracle} from "../../../contracts/test/helpers/MockOracle.sol"; import {FeedInterface} from "../../../contracts/token/oracles/FeedInterface.sol"; import {SATestBase} from "../base/SATestBase.sol"; +import { MathLib } from "../../../contracts/libs/MathLib.sol"; error SetupIncomplete(); @@ -44,6 +45,20 @@ contract TokenPaymasterTest is SATestBase { function setUp() public virtual override { super.setUp(); + /*uint256 testResult = MathLib.minuint256( + 50, + 60 + ); + + console2.log("testResult ", testResult); + + uint256 testResult2 = MathLib.minuint32( + 50, + 60 + ); + + console2.log("testResult2 ", testResult2);*/ + vm.warp(1680509051); console2.log("current block timestamp ", block.timestamp); @@ -68,7 +83,8 @@ contract TokenPaymasterTest is SATestBase { vm.startPrank(alice.addr); // could also make a .call using selector and handle success - _btpm.setTokenOracle(address(usdc), usdc.decimals(), address(tokenOracle), address(nativeOracle), true); + // append 2 days price threshold + _btpm.setTokenOracle(address(usdc), address(tokenOracle), address(nativeOracle), true, 172800); vm.stopPrank(); uint256 priceToLog = _btpm.getTokenValueOfOneNativeToken((address(usdc))); diff --git a/test/foundry/token-paymaster/TokenPaymasterMumbai.t.sol b/test/foundry/token-paymaster/TokenPaymasterMumbai.t.sol index e3fb4d4..154eabd 100644 --- a/test/foundry/token-paymaster/TokenPaymasterMumbai.t.sol +++ b/test/foundry/token-paymaster/TokenPaymasterMumbai.t.sol @@ -68,14 +68,12 @@ contract TokenPaymasterMumbaiTest is SATestBase { vm.startPrank(alice.addr); // could also make a .call using selector and handle success _btpm.setTokenOracle( - address(usdc), ERC20(address(usdc)).decimals(), address(tokenOracle), address(nativeOracle), true + address(usdc), address(tokenOracle), address(nativeOracle), true, 172800 ); vm.stopPrank(); uint256 priceToLog = _btpm.getTokenValueOfOneNativeToken((address(usdc))); - // console2.log(priceToLog); - - address accountAddress = address(sa); + console2.log(priceToLog); vm.startPrank(charlie.addr); entryPoint.depositTo{value: 2 ether}(address(_btpm)); diff --git a/test/sponsorship-paymaster/biconomy-sponsorship-paymaster-specs.ts b/test/sponsorship-paymaster/biconomy-sponsorship-paymaster-specs.ts index f7e5320..bc8cb4f 100644 --- a/test/sponsorship-paymaster/biconomy-sponsorship-paymaster-specs.ts +++ b/test/sponsorship-paymaster/biconomy-sponsorship-paymaster-specs.ts @@ -175,9 +175,12 @@ describe("EntryPoint with VerifyingPaymaster Singleton", function () { it("succeed with valid signature", async () => { const fundingId = await offchainSigner.getAddress(); + // TODO: find accurate value of unaccountedEPGasOverhead based on userOpGasPrice calculation flip (maxFeePerGas vs block.gasprice + priorityFee) + + // already bit high here because of first txn await sponsorshipPaymaster .connect(deployer) - .setUnaccountedEPGasOverhead(35500); + .setUnaccountedEPGasOverhead(55500); await sponsorshipPaymaster.depositFor(fundingId, { value: ethers.utils.parseEther("1"), @@ -307,9 +310,10 @@ describe("EntryPoint with VerifyingPaymaster Singleton", function () { it("succeed with valid signature - second transaction ", async () => { const fundingId = await offchainSigner.getAddress(); + // TODO: find accurate value of unaccountedEPGasOverhead based on userOpGasPrice calculation flip (maxFeePerGas vs block.gasprice + priorityFee) await sponsorshipPaymaster .connect(deployer) - .setUnaccountedEPGasOverhead(18500); + .setUnaccountedEPGasOverhead(38500); await sponsorshipPaymaster.depositFor(fundingId, { value: ethers.utils.parseEther("1"), @@ -440,9 +444,10 @@ describe("EntryPoint with VerifyingPaymaster Singleton", function () { it("succeed with valid signature - same account - different funding id ", async () => { const fundingId = await secondFundingId.getAddress(); + // TODO: find accurate value of unaccountedEPGasOverhead based on userOpGasPrice calculation flip (maxFeePerGas vs block.gasprice + priorityFee) await sponsorshipPaymaster .connect(deployer) - .setUnaccountedEPGasOverhead(18500); + .setUnaccountedEPGasOverhead(38500); await sponsorshipPaymaster.depositFor(fundingId, { value: ethers.utils.parseEther("1"), @@ -573,9 +578,10 @@ describe("EntryPoint with VerifyingPaymaster Singleton", function () { it("fails for fundingId which does not have enough deposit", async () => { const fundingId = await thirdFundingId.getAddress(); + // TODO: find accurate value of unaccountedEPGasOverhead based on userOpGasPrice calculation flip (maxFeePerGas vs block.gasprice + priorityFee) await sponsorshipPaymaster .connect(deployer) - .setUnaccountedEPGasOverhead(18500); + .setUnaccountedEPGasOverhead(38500); // do not deposit diff --git a/test/token-paymaster/biconomy-token-paymaster-specs.ts b/test/token-paymaster/biconomy-token-paymaster-specs.ts index d194f4c..21580df 100644 --- a/test/token-paymaster/biconomy-token-paymaster-specs.ts +++ b/test/token-paymaster/biconomy-token-paymaster-specs.ts @@ -147,10 +147,10 @@ describe("Biconomy Token Paymaster", function () { await sampleTokenPaymaster.setTokenOracle( token.address, - await token.decimals(), tokenOracle.address, nativeOracle.address, - true + true, + 172800 // 2 days ); const priceResult = diff --git a/test/token-paymaster/btpm-coverage-specs.ts b/test/token-paymaster/btpm-coverage-specs.ts index 7b5ac1b..bf52a9f 100644 --- a/test/token-paymaster/btpm-coverage-specs.ts +++ b/test/token-paymaster/btpm-coverage-specs.ts @@ -148,10 +148,10 @@ describe("Biconomy Token Paymaster", function () { await sampleTokenPaymaster.setTokenOracle( token.address, - await token.decimals(), tokenOracle.address, nativeOracle.address, - true + true, + 172800 // 2 days ); const priceResult = diff --git a/test/token-paymaster/btpm-undeployed-wallet.ts b/test/token-paymaster/btpm-undeployed-wallet.ts index 7379157..076fa6e 100644 --- a/test/token-paymaster/btpm-undeployed-wallet.ts +++ b/test/token-paymaster/btpm-undeployed-wallet.ts @@ -157,10 +157,10 @@ describe("Biconomy Token Paymaster", function () { await sampleTokenPaymaster.setTokenOracle( token.address, - await token.decimals(), tokenOracle.address, nativeOracle.address, - true + true, + 172800 // 2 days ); const priceResult = diff --git a/test/token-paymaster/oracle-aggregator-specs.ts b/test/token-paymaster/oracle-aggregator-specs.ts index b2beee3..5af9f20 100644 --- a/test/token-paymaster/oracle-aggregator-specs.ts +++ b/test/token-paymaster/oracle-aggregator-specs.ts @@ -147,10 +147,10 @@ describe("Biconomy Token Paymaster", function () { await sampleTokenPaymaster.setTokenOracle( token.address, - await token.decimals(), tokenOracle.address, nativeOracle.address, - true + true, + 172800 // 2 days ); const priceResult = @@ -337,10 +337,10 @@ describe("Biconomy Token Paymaster", function () { await sampleTokenPaymaster.setTokenOracle( token.address, - await token.decimals(), staleFeed.address, nativeOracle.address, - true + true, + 172800 // 2 days ); // this is not expected to revert