Skip to content

Commit

Permalink
gas opt: token paymaster and oracle aggregator
Browse files Browse the repository at this point in the history
  • Loading branch information
ankurdubey521 committed Nov 17, 2023
1 parent 73546a3 commit d5969f5
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 47 deletions.
29 changes: 15 additions & 14 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
TokenPaymasterTest:test2TokenPaymasterFailInvalidPaymasteDataLength() (gas: 278584)
TokenPaymasterTest:testCall() (gas: 313171)
TokenPaymasterTest:testCheckStates() (gas: 18600)
TokenPaymasterTest:testDeploy() (gas: 1604349)
TokenPaymasterTest:testOwnershipTransfer() (gas: 23443)
TokenPaymasterTest:testParsePaymasterData() (gas: 621)
TokenPaymasterTest:testTokenPaymasterFailHighPriceMarkup() (gas: 300775)
TokenPaymasterTest:testTokenPaymasterFailInvalidPMSignature() (gas: 283184)
TokenPaymasterTest:testTokenPaymasterFailInvalidPMSignatureLength() (gas: 283496)
TokenPaymasterTest:testTokenPaymasterFailInvalidPaymasteDataLength() (gas: 221406)
TokenPaymasterTest:testTokenPaymasterFailWrongPMSignature() (gas: 295014)
TokenPaymasterTest:testTokenPaymasterRefund() (gas: 399655)
TokenPaymasterTest:testWithdrawERC20(uint256) (runs: 256, μ: 61802, ~: 64060)
TokenPaymasterTest:testWithdrawERC20FailNotOwner(uint256) (runs: 256, μ: 48871, ~: 51000)
TokenPaymasterTest:test2TokenPaymasterFailInvalidPaymasteDataLength() (gas: 292916)
TokenPaymasterTest:testCall() (gas: 327004)
TokenPaymasterTest:testCheckStates() (gas: 18629)
TokenPaymasterTest:testDecode() (gas: 11952)
TokenPaymasterTest:testDeploy() (gas: 1642864)
TokenPaymasterTest:testOwnershipTransfer() (gas: 23457)
TokenPaymasterTest:testParsePaymasterData() (gas: 658)
TokenPaymasterTest:testTokenPaymasterFailHighPriceMarkup() (gas: 315354)
TokenPaymasterTest:testTokenPaymasterFailInvalidPMSignature() (gas: 297563)
TokenPaymasterTest:testTokenPaymasterFailInvalidPMSignatureLength() (gas: 297878)
TokenPaymasterTest:testTokenPaymasterFailInvalidPaymasteDataLength() (gas: 230920)
TokenPaymasterTest:testTokenPaymasterFailWrongPMSignature() (gas: 309240)
TokenPaymasterTest:testTokenPaymasterRefund() (gas: 413708)
TokenPaymasterTest:testWithdrawERC20(uint256) (runs: 256, μ: 61908, ~: 64071)
TokenPaymasterTest:testWithdrawERC20FailNotOwner(uint256) (runs: 256, μ: 48851, ~: 50980)
101 changes: 79 additions & 22 deletions contracts/token/BiconomyTokenPaymaster.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import "../utils/SafeTransferLib.sol";
import {TokenPaymasterErrors} from "./TokenPaymasterErrors.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "hardhat/console.sol";

// Biconomy Token Paymaster
/**
Expand Down Expand Up @@ -232,11 +233,13 @@ contract BiconomyTokenPaymaster is
address _token,
address _oracleAggregator
) internal view virtual returns (uint256) {
uint256 gas = gasleft();
try
IOracleAggregator(_oracleAggregator).getTokenValueOfOneNativeToken(
_token
)
returns (uint256 exchangeRate) {
console.log("gas used for exchangePrice: %s", gas - gasleft());
return exchangeRate;
} catch {
return 0;
Expand Down Expand Up @@ -349,8 +352,8 @@ contract BiconomyTokenPaymaster is
abi.encode(
userOp.getSender(),
userOp.nonce,
keccak256(userOp.initCode),
keccak256(userOp.callData),
userOp.initCode,
userOp.callData,
userOp.callGasLimit,
userOp.verificationGasLimit,
userOp.preVerificationGas,
Expand Down Expand Up @@ -451,6 +454,7 @@ contract BiconomyTokenPaymaster is
);

// review: in this method try to resolve stack too deep (though via-ir is good enough)
uint256 gas = gasleft();
(
ExchangeRateSource priceSource,
uint48 validUntil,
Expand All @@ -461,13 +465,15 @@ contract BiconomyTokenPaymaster is
uint32 priceMarkup,
bytes calldata signature
) = parsePaymasterAndData(userOp.paymasterAndData);
console.log("gas used for parsePmd: %s", gas - gasleft());

// we only "require" it here so that the revert reason on invalid signature will be of "VerifyingPaymaster", and not "ECDSA"
require(
signature.length == 65,
"BTPM: invalid signature length in paymasterAndData"
);

gas = gasleft();
bytes32 _hash = getHash(
userOp,
priceSource,
Expand All @@ -478,6 +484,7 @@ contract BiconomyTokenPaymaster is
exchangeRate,
priceMarkup
).toEthSignedMessageHash();
console.log("gas used for getHash: %s", gas - gasleft());

context = "";

Expand Down Expand Up @@ -511,6 +518,7 @@ contract BiconomyTokenPaymaster is
"BTPM: account does not have enough token balance"
);

gas = gasleft();
context = abi.encode(
account,
feeToken,
Expand All @@ -520,6 +528,15 @@ contract BiconomyTokenPaymaster is
priceMarkup,
userOpHash
);
console.log("gas used for context encode: %s", gas - gasleft());

// console.log("account: %s", account);
// console.log("feeToken: %s", address(feeToken));
// console.log("oracleAggregator: %s", oracleAggregator);
// console.log("priceSource: %s", uint8(priceSource));
// console.log("exchangeRate: %s", exchangeRate);
// console.log("priceMarkup: %s", priceMarkup);
// console.log("userOpHash: %s", uint256(userOpHash));

return (
context,
Expand All @@ -538,26 +555,66 @@ contract BiconomyTokenPaymaster is
bytes calldata context,
uint256 actualGasCost
) internal virtual override {
(
address account,
IERC20 feeToken,
address oracleAggregator,
ExchangeRateSource priceSource,
uint256 exchangeRate,
uint32 priceMarkup,
bytes32 userOpHash
) = abi.decode(
context,
(
address,
IERC20,
address,
ExchangeRateSource,
uint256,
uint32,
bytes32
)
);
uint256 gas = gasleft();
// (
// address account,
// IERC20 feeToken,
// address oracleAggregator,
// ExchangeRateSource priceSource,
// uint256 exchangeRate,
// uint32 priceMarkup,
// bytes32 userOpHash
// ) = abi.decode(
// context,
// (
// address,
// IERC20,
// address,
// ExchangeRateSource,
// uint256,
// uint32,
// bytes32
// )
// );
address account;
IERC20 feeToken;
address oracleAggregator;
ExchangeRateSource priceSource;
uint256 exchangeRate;
uint32 priceMarkup;
bytes32 userOpHash;
assembly ("memory-safe") {
let offset := context.offset

account := calldataload(context.offset)
offset := add(offset, 0x20)

feeToken := calldataload(offset)
offset := add(offset, 0x20)

oracleAggregator := calldataload(offset)
offset := add(offset, 0x20)

priceSource := calldataload(offset)
offset := add(offset, 0x20)

exchangeRate := calldataload(offset)
offset := add(offset, 0x20)

priceMarkup := calldataload(offset)
offset := add(offset, 0x20)

userOpHash := calldataload(offset)
}
console.log("gas used for context decode: %s", gas - gasleft());

// console.log("account: %s", account);
// console.log("feeToken: %s", address(feeToken));
// console.log("oracleAggregator: %s", oracleAggregator);
// console.log("priceSource: %s", uint8(priceSource));
// console.log("exchangeRate: %s", exchangeRate);
// console.log("priceMarkup: %s", priceMarkup);
// console.log("userOpHash: %s", uint256(userOpHash));

uint256 effectiveExchangeRate = exchangeRate;

Expand Down
29 changes: 18 additions & 11 deletions contracts/token/oracles/ChainlinkOracleAggregator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ contract ChainlinkOracleAggregator is Ownable, IOracleAggregator {
address token
) external view returns (uint256 tokenPrice) {
// usually token / native (depends on price feed)
tokenPrice = _getTokenPrice(token);
(tokenPrice, ) = _getTokenPriceAndDecimals(token);
}

/**
Expand All @@ -84,23 +84,30 @@ contract ChainlinkOracleAggregator is Ownable, IOracleAggregator {
address token
) external view virtual returns (uint256 exchangeRate) {
// we'd actually want eth / token
uint256 tokenPriceUnadjusted = _getTokenPrice(token);
uint8 _tokenOracleDecimals = tokensInfo[token].decimals;
(
uint256 tokenPriceUnadjusted,
uint8 tokenOracleDecimals
) = _getTokenPriceAndDecimals(token);
exchangeRate =
((10 ** _tokenOracleDecimals) *
(10 ** IERC20Metadata(token).decimals())) /
10 ** (tokenOracleDecimals + IERC20Metadata(token).decimals()) /
tokenPriceUnadjusted;
}

function _getTokenPrice(
function _getTokenPriceAndDecimals(
address token
) internal view returns (uint256 tokenPriceUnadjusted) {
)
internal
view
returns (uint256 tokenPriceUnadjusted, uint8 tokenOracleDecimals)
{
// Note // If the callData is for latestAnswer, it could be for latestRoundData and then validateRound and extract price then
(bool success, bytes memory ret) = tokensInfo[token]
.callAddress
.staticcall(tokensInfo[token].callData);
TokenInfo storage tokenInfo = tokensInfo[token];
tokenOracleDecimals = tokenInfo.decimals;
(bool success, bytes memory ret) = tokenInfo.callAddress.staticcall(
tokenInfo.callData
);
require(success, "ChainlinkOracleAggregator:: query failed");
if (tokensInfo[token].dataSigned) {
if (tokenInfo.dataSigned) {
tokenPriceUnadjusted = uint256(abi.decode(ret, (int256)));
} else {
tokenPriceUnadjusted = abi.decode(ret, (uint256));
Expand Down

0 comments on commit d5969f5

Please sign in to comment.