Skip to content

Commit

Permalink
dev notes+test updates
Browse files Browse the repository at this point in the history
  • Loading branch information
livingrockrises committed Oct 26, 2024
1 parent b4250ea commit f6a2c4c
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 21 deletions.
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
14 changes: 9 additions & 5 deletions contracts/token/BiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SignatureCheckerLib } from "solady/utils/SignatureCheckerLib.sol";
import { ECDSA as ECDSA_solady } from "solady/utils/ECDSA.sol";
import "account-abstraction/core/Helpers.sol";
import "./swaps/Uniswapper.sol";
import "forge-std/console2.sol";

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

View workflow job for this annotation

GitHub Actions / Lint sources

Unexpected import of console file

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

// PAYMASTER_ID_OFFSET
uint256 private constant _UNACCOUNTED_GAS_LIMIT = 50_000; // Limit for unaccounted gas cost
// Note: Temp
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 @@ -435,7 +437,8 @@ contract BiconomyTokenPaymaster is
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 Down Expand Up @@ -480,7 +483,7 @@ contract BiconomyTokenPaymaster is

// 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
Expand Down Expand Up @@ -538,14 +541,15 @@ contract BiconomyTokenPaymaster is
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 @@ -559,7 +563,7 @@ contract BiconomyTokenPaymaster is
uint192 nativeAssetPrice = _fetchPrice(nativeAssetToUsdOracle);

// Adjust to token decimals
price = (nativeAssetPrice * uint192(tokenInfo.decimals)) / tokenPrice;
price = (nativeAssetPrice * tokenInfo.decimals) / tokenPrice;
}

/// @notice Fetches the latest price from the given oracle.
Expand Down
19 changes: 12 additions & 7 deletions test/base/TestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
IBiconomyTokenPaymaster,
BiconomyTokenPaymasterErrors,
IOracle
} from "../../../contracts/token/BiconomyTokenPaymaster.sol";
} from "../../contracts/token/BiconomyTokenPaymaster.sol";

abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
using UserOperationLib for PackedUserOperation;
Expand Down Expand Up @@ -444,6 +444,7 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {
uint256 initialPaymasterEpBalance,
uint256 initialUserTokenBalance,
uint256 initialPaymasterTokenBalance,
uint256 tokenPrice,
uint32 priceMarkup,
uint256 maxPenalty
)
Expand All @@ -459,15 +460,19 @@ abstract contract TestBase is CheatCodes, TestHelper, BaseEventsAndErrors {

uint256 gasCollectedInERC20ByPaymaster = token.balanceOf(address(tokenPaymaster)) - initialPaymasterTokenBalance;

// Accounts for refund etc
// Revirw if it should be exact equal
assertApproxEqRel(gasPaidBySAInERC20, gasCollectedInERC20ByPaymaster, 0.02e18);
// What user paid = received by paymaster
// unless ofcourse there is same token transfer in calldata
assertEq(gasPaidBySAInERC20, gasCollectedInERC20ByPaymaster);

// assertGt(gasPaidBySAInERC20 * tokenPrice, BUNDLER.addr.balance - initialBundlerBalance);
// console2.log("gasPaidBySAInERC20", gasPaidBySAInERC20);
// console2.log("gasCollectedInERC20ByPaymaster", gasCollectedInERC20ByPaymaster);

// Review we will also need to update premium numbers in below if there is premium: multiply by 1e6 / premium
// assertGt(gasPaidBySAInERC20 * 1e18 / tokenPrice, BUNDLER.addr.balance - initialBundlerBalance);

// Ensure that max 2% difference between total gas paid + the adjustment premium and gas paid by smart account (ERC20 charge * token gas price) (from
// Todo
// assertApproxEqRel(totalGasFeePaid + actualPriceMarkup + maxPenalty, gasPaidByDapp, 0.02e18);
// Todo
// assertApproxEqRel(totalGasFeePaid + actualPriceMarkup + maxPenalty, gasPaidByDapp, 0.02e18);
}

function _toSingletonArray(address addr) internal pure returns (address[] memory) {
Expand Down
40 changes: 32 additions & 8 deletions test/unit/concrete/TestTokenPaymaster.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ contract TestTokenPaymaster is TestBase {
function setUp() public {
setupPaymasterTestEnvironment();

uint256 customGasPrice = 3e6;
vm.txGasPrice(customGasPrice);

// Deploy mock oracles and tokens
swapRouter = ISwapRouter(address(SWAP_ROUTER_ADDRESS));
nativeAssetToUsdOracle = new MockOracle(100_000_000, 8); // Oracle with 8 decimals for ETH // ETH/USD
Expand All @@ -36,7 +39,7 @@ contract TestTokenPaymaster is TestBase {
PAYMASTER_OWNER.addr,
PAYMASTER_SIGNER.addr,
ENTRYPOINT,
5000, // unaccounted gas
50000, // unaccounted gas
1e6, // price markup
1 days, // price expiry duration
nativeAssetToUsdOracle,
Expand Down Expand Up @@ -301,7 +304,7 @@ contract TestTokenPaymaster is TestBase {
vm.stopPrank();

PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE));
uint128 tokenPrice = 1e8; // Assume 1 token = 1 USD
uint128 tokenPrice = 1e18; // Assume 1 token = 1 native token = 1 native?

TokenPaymasterData memory pmData = TokenPaymasterData({
paymasterValGasLimit: 3e6,
Expand Down Expand Up @@ -346,7 +349,7 @@ contract TestTokenPaymaster is TestBase {
vm.stopPrank();

PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE));
uint128 tokenPrice = 1e8;
uint128 tokenPrice = 1e18;

TokenPaymasterData memory pmData = TokenPaymasterData({
paymasterValGasLimit: 3e6,
Expand Down Expand Up @@ -382,13 +385,17 @@ contract TestTokenPaymaster is TestBase {
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
}

function test_Success_TokenPaymaster_ExternalMode() external {
function test_Success_TokenPaymaster_ExternalMode_WithoutPremium() external {
tokenPaymaster.deposit{ value: 10 ether }();
testToken.mint(address(ALICE_ACCOUNT), 100_000 * (10 ** testToken.decimals()));
vm.startPrank(address(ALICE_ACCOUNT));
testToken.approve(address(tokenPaymaster), testToken.balanceOf(address(ALICE_ACCOUNT)));
vm.stopPrank();

vm.startPrank(PAYMASTER_OWNER.addr);
tokenPaymaster.setUnaccountedGas(50_000);
vm.stopPrank();

// Warm up the ERC20 balance slot for paymaster by making some tokens held initially
testToken.mint(address(tokenPaymaster), 100_000 * (10 ** testToken.decimals()));

Expand All @@ -402,7 +409,7 @@ contract TestTokenPaymaster is TestBase {
PackedUserOperation memory userOp = buildUserOpWithCalldata(ALICE, "", address(VALIDATOR_MODULE));
uint48 validUntil = uint48(block.timestamp + 1 days);
uint48 validAfter = uint48(block.timestamp);
uint128 tokenPrice = 1e8; // Assume 1 token = 1 USD
uint128 tokenPrice = 1e18; // Assume 1 token = 1 native token = 1 USD ?
uint32 externalPriceMarkup = 1e6; // no premium

TokenPaymasterData memory pmData = TokenPaymasterData({
Expand Down Expand Up @@ -437,7 +444,9 @@ contract TestTokenPaymaster is TestBase {
emit IBiconomyTokenPaymaster.PaidGasInTokens(address(ALICE_ACCOUNT), address(testToken), 0, 0, 1e6, bytes32(0));

// Execute the operation
startPrank(BUNDLER.addr);
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
stopPrank();

calculateAndAssertAdjustmentsForTokenPaymaster(
tokenPaymaster,
Expand All @@ -446,17 +455,22 @@ contract TestTokenPaymaster is TestBase {
initialPaymasterEpBalance,
initialUserTokenBalance,
initialPaymasterTokenBalance,
1e18, // tokenPrice
100000,
this.getMaxPenalty(ops[0]));
}

function test_Success_TokenPaymaster_IndependentMode() external {
function test_Success_TokenPaymaster_IndependentMode_WithoutPremium() external {
tokenPaymaster.deposit{ value: 10 ether }();
testToken.mint(address(ALICE_ACCOUNT), 100_000 * (10 ** testToken.decimals()));
vm.startPrank(address(ALICE_ACCOUNT));
testToken.approve(address(tokenPaymaster), testToken.balanceOf(address(ALICE_ACCOUNT)));
vm.stopPrank();

vm.startPrank(PAYMASTER_OWNER.addr);
tokenPaymaster.setUnaccountedGas(200_000);
vm.stopPrank();

uint256 initialBundlerBalance = BUNDLER.addr.balance;
uint256 initialPaymasterEpBalance = tokenPaymaster.getDeposit();
uint256 initialUserTokenBalance = testToken.balanceOf(address(ALICE_ACCOUNT));
Expand Down Expand Up @@ -485,9 +499,19 @@ contract TestTokenPaymaster is TestBase {
vm.expectEmit(true, true, false, false, address(tokenPaymaster));
emit IBiconomyTokenPaymaster.PaidGasInTokens(address(ALICE_ACCOUNT), address(testToken), 0, 0, 1e6, bytes32(0));

startPrank(BUNDLER.addr);
ENTRYPOINT.handleOps(ops, payable(BUNDLER.addr));
stopPrank();

assertLt(testToken.balanceOf(address(ALICE_ACCOUNT)), initialUserTokenBalance);
assertGt(testToken.balanceOf(address(tokenPaymaster)), initialPaymasterTokenBalance);
calculateAndAssertAdjustmentsForTokenPaymaster(
tokenPaymaster,
testToken,
initialBundlerBalance,
initialPaymasterEpBalance,
initialUserTokenBalance,
initialPaymasterTokenBalance,
1e18, // tokenPrice
100000,
this.getMaxPenalty(ops[0]));
}
}
1 change: 1 addition & 0 deletions test/unit/concrete/TestTokenPaymasterParserLib.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ contract TestTokenPaymasterParserLib is Test {
assertEq(parsedModeSpecificData, modeSpecificData, "Mode specific data should match");
}

// TODO: review prices added inline with MockOracle
function test_ParseExternalModeSpecificData() public view {
// Simulate valid external mode specific data
uint48 expectedValidUntil = uint48(block.timestamp + 1 days);
Expand Down

0 comments on commit f6a2c4c

Please sign in to comment.