From 0b4a200944da2b58e91c8acfaded704bd75ef22b Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva Date: Wed, 25 Oct 2023 11:36:23 -0700 Subject: [PATCH] fix: Update transaction type for gas estimation if one is not provided for EIP712 transactions, this fixes paymasters for era-test-node. (#195) --- .vscode/launch.json | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- e2e-tests/contracts/ERC20.sol | 33 +++++ e2e-tests/contracts/ERC20FixedPaymaster.sol | 129 ++++++++++++++++++ e2e-tests/contracts/ERC721.sol | 33 +++++ e2e-tests/contracts/ERC721GatedPaymaster.sol | 96 +++++++++++++ e2e-tests/contracts/Greeter.sol | 9 +- e2e-tests/package.json | 1 + e2e-tests/test/erc20-fixed-paymaster.test.ts | 115 ++++++++++++++++ e2e-tests/test/erc721-gated-paymaster.test.ts | 118 ++++++++++++++++ e2e-tests/test/main.test.ts | 2 +- e2e-tests/yarn.lock | 5 + src/node.rs | 34 ++++- 14 files changed, 569 insertions(+), 12 deletions(-) create mode 100644 e2e-tests/contracts/ERC20.sol create mode 100644 e2e-tests/contracts/ERC20FixedPaymaster.sol create mode 100644 e2e-tests/contracts/ERC721.sol create mode 100644 e2e-tests/contracts/ERC721GatedPaymaster.sol create mode 100644 e2e-tests/test/erc20-fixed-paymaster.test.ts create mode 100644 e2e-tests/test/erc721-gated-paymaster.test.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 134bcd4a..e613f305 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -44,7 +44,7 @@ "ZKSYNC_HOME": "${workspaceFolder}" }, "args": [ - "--dev-use-local-contracts", + "--dev-system-contracts=local", "run" ], "preLaunchTask": "rebuild-contracts", diff --git a/Cargo.lock b/Cargo.lock index 96ac4209..48c5c3a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2120,7 +2120,7 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "era_test_node" -version = "0.1.0-alpha.5" +version = "0.1.0-alpha.9" dependencies = [ "anyhow", "bigdecimal", diff --git a/Cargo.toml b/Cargo.toml index d6890df8..8b5d51d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "era_test_node" -version = "0.1.0-alpha.5" +version = "0.1.0-alpha.9" edition = "2018" authors = ["The Matter Labs Team "] homepage = "https://zksync.io/" diff --git a/e2e-tests/contracts/ERC20.sol b/e2e-tests/contracts/ERC20.sol new file mode 100644 index 00000000..bd56500f --- /dev/null +++ b/e2e-tests/contracts/ERC20.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @dev This contract is for basic demonstration purposes only. It should not be used in production. + * It is for the convenience of the ERC20FixedPaymaster.sol contract and its corresponding test file. + */ +contract MyERC20 is ERC20 { + uint8 private _decimals; + + constructor( + string memory name, + string memory symbol, + uint8 decimals_ + ) payable ERC20(name, symbol) { + _decimals = decimals_; + } + + function mint(address _to, uint256 _amount) public returns (bool) { + _mint(_to, _amount); + return true; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } + + function burn(address from, uint256 amount) public { + _burn(from, amount); + } +} \ No newline at end of file diff --git a/e2e-tests/contracts/ERC20FixedPaymaster.sol b/e2e-tests/contracts/ERC20FixedPaymaster.sol new file mode 100644 index 00000000..67c4a938 --- /dev/null +++ b/e2e-tests/contracts/ERC20FixedPaymaster.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol"; +import {IPaymasterFlow} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol"; +import {TransactionHelper, Transaction} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; + +import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +/// @author Matter Labs +/// @notice This smart contract pays the gas fees for accounts with balance of a specific ERC20 token. It makes use of the approval-based flow paymaster. +contract ERC20FixedPaymaster is IPaymaster, Ownable { + uint256 constant PRICE_FOR_PAYING_FEES = 1; + + address public allowedToken; + + modifier onlyBootloader() { + require( + msg.sender == BOOTLOADER_FORMAL_ADDRESS, + "Only bootloader can call this method" + ); + // Continue execution if called from the bootloader. + _; + } + + constructor(address _erc20) { + allowedToken = _erc20; + } + + function validateAndPayForPaymasterTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) + external + payable + onlyBootloader + returns (bytes4 magic, bytes memory context) + { + // By default we consider the transaction as accepted. + magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; + require( + _transaction.paymasterInput.length >= 4, + "The standard paymaster input must be at least 4 bytes long" + ); + + bytes4 paymasterInputSelector = bytes4( + _transaction.paymasterInput[0:4] + ); + if (paymasterInputSelector == IPaymasterFlow.approvalBased.selector) { + // While the transaction data consists of address, uint256 and bytes data, + // the data is not needed for this paymaster + (address token, uint256 amount, bytes memory data) = abi.decode( + _transaction.paymasterInput[4:], + (address, uint256, bytes) + ); + + // Verify if token is the correct one + require(token == allowedToken, "Invalid token"); + + // We verify that the user has provided enough allowance + address userAddress = address(uint160(_transaction.from)); + + address thisAddress = address(this); + + uint256 providedAllowance = IERC20(token).allowance( + userAddress, + thisAddress + ); + require( + providedAllowance >= PRICE_FOR_PAYING_FEES, + "Min allowance too low" + ); + + // Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit, + // neither paymaster nor account are allowed to access this context variable. + uint256 requiredETH = _transaction.gasLimit * + _transaction.maxFeePerGas; + + try + IERC20(token).transferFrom(userAddress, thisAddress, amount) + {} catch (bytes memory revertReason) { + // If the revert reason is empty or represented by just a function selector, + // we replace the error with a more user-friendly message + if (revertReason.length <= 4) { + revert("Failed to transferFrom from users' account"); + } else { + assembly { + revert(add(0x20, revertReason), mload(revertReason)) + } + } + } + + // The bootloader never returns any data, so it can safely be ignored here. + (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ + value: requiredETH + }(""); + require( + success, + "Failed to transfer tx fee to the bootloader. Paymaster balance might not be enough." + ); + } else { + revert("Unsupported paymaster flow"); + } + } + + function postTransaction( + bytes calldata _context, + Transaction calldata _transaction, + bytes32, + bytes32, + ExecutionResult _txResult, + uint256 _maxRefundedGas + ) external payable override onlyBootloader { + } + + function withdraw(address _to) external onlyOwner { + // send paymaster funds to the owner + (bool success, ) = payable(_to).call{value: address(this).balance}(""); + require(success, "Failed to withdraw funds from paymaster."); + } + + receive() external payable {} + +} \ No newline at end of file diff --git a/e2e-tests/contracts/ERC721.sol b/e2e-tests/contracts/ERC721.sol new file mode 100644 index 00000000..2f352f6a --- /dev/null +++ b/e2e-tests/contracts/ERC721.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Importing OpenZeppelin's ERC721 Implementation +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +// Importing OpenZeppelin's Ownable contract to control ownership +import "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @dev This contract is for basic demonstration purposes only. It should not be used in production. + * It is for the convenience of the ERC721GatedPaymaster.sol contract and its corresponding test file. + */ +contract MyNFT is ERC721, Ownable { + // Maintains a counter of token IDs for uniqueness + uint256 public tokenCounter; + + // A constructor that gives my NFT a name and a symbol + constructor () ERC721 ("MyNFT", "MNFT"){ + // Initializes the tokenCounter to 0. Every new token has a unique ID starting from 1 + tokenCounter = 0; + } + + // Creates an NFT collection, with a unique token ID + function mint(address recipient) public onlyOwner returns (uint256) { + // Increases the tokenCounter by 1 and then mints the token with this new ID + _safeMint(recipient, tokenCounter); + + // Increments the token counter for the next token to be minted + tokenCounter = tokenCounter + 1; + + return tokenCounter; + } +} \ No newline at end of file diff --git a/e2e-tests/contracts/ERC721GatedPaymaster.sol b/e2e-tests/contracts/ERC721GatedPaymaster.sol new file mode 100644 index 00000000..b7b032fc --- /dev/null +++ b/e2e-tests/contracts/ERC721GatedPaymaster.sol @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; + +import {IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol"; +import {IPaymasterFlow} from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol"; +import {TransactionHelper, Transaction} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol"; + +import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol"; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +/// @author Matter Labs +/// @notice This smart contract pays the gas fees on behalf of users that are the owner of a specific NFT asset +contract ERC721GatedPaymaster is IPaymaster, Ownable { + IERC721 private immutable nft_asset; + + modifier onlyBootloader() { + require( + msg.sender == BOOTLOADER_FORMAL_ADDRESS, + "Only bootloader can call this method" + ); + // Continue execution if called from the bootloader. + _; + } + + // The constructor takes the address of the ERC721 contract as an argument. + // The ERC721 contract is the asset that the user must hold in order to use the paymaster. + constructor(address _erc721) { + nft_asset = IERC721(_erc721); // Initialize the ERC721 contract + } + + // The gas fees will be paid for by the paymaster if the user is the owner of the required NFT asset. + function validateAndPayForPaymasterTransaction( + bytes32, + bytes32, + Transaction calldata _transaction + ) + external + payable + onlyBootloader + returns (bytes4 magic, bytes memory context) + { + // By default we consider the transaction as accepted. + magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC; + require( + _transaction.paymasterInput.length >= 4, + "The standard paymaster input must be at least 4 bytes long" + ); + + bytes4 paymasterInputSelector = bytes4( + _transaction.paymasterInput[0:4] + ); + + // Use the general paymaster flow + if (paymasterInputSelector == IPaymasterFlow.general.selector) { + address userAddress = address(uint160(_transaction.from)); + // Verify if user has the required NFT asset in order to use paymaster + require( + nft_asset.balanceOf(userAddress) > 0, + "User does not hold the required NFT asset and therefore must for their own gas!" + ); + // Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit, + // neither paymaster nor account are allowed to access this context variable. + uint256 requiredETH = _transaction.gasLimit * + _transaction.maxFeePerGas; + + // The bootloader never returns any data, so it can safely be ignored here. + (bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ + value: requiredETH + }(""); + } else { + revert("Invalid paymaster flow"); + } + } + + function postTransaction( + bytes calldata _context, + Transaction calldata _transaction, + bytes32, + bytes32, + ExecutionResult _txResult, + uint256 _maxRefundedGas + ) external payable override onlyBootloader { + } + + function withdraw(address payable _to) external onlyOwner { + // send paymaster funds to the owner + uint256 balance = address(this).balance; + (bool success, ) = _to.call{value: balance}(""); + require(success, "Failed to withdraw funds from paymaster."); + } + + receive() external payable {} +} \ No newline at end of file diff --git a/e2e-tests/contracts/Greeter.sol b/e2e-tests/contracts/Greeter.sol index 252da0c4..abbbc8a7 100644 --- a/e2e-tests/contracts/Greeter.sol +++ b/e2e-tests/contracts/Greeter.sol @@ -16,7 +16,7 @@ contract Greeter is Ownable { return greeting; } - function setGreeting(string memory _greeting) public onlyOwner { + function setGreeting(string memory _greeting) public { console.log("setGreeting called"); console.log(_greeting); emit LogString(string.concat("Greeting is being updated to ", _greeting)); @@ -27,4 +27,11 @@ contract Greeter is Ownable { ); greeting = _greeting; } + + function setGreetingByOwner(string memory _greeting) public onlyOwner { + console.log("setGreetingByOwner called"); + console.log(_greeting); + emit LogString(string.concat("Greeting is being updated to ", _greeting)); + greeting = _greeting; + } } diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 1b88c87f..88ef6bee 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -10,6 +10,7 @@ "@matterlabs/hardhat-zksync-deploy": "^0.6.5", "@matterlabs/hardhat-zksync-solc": "^0.4.2", "@matterlabs/prettier-config": "^1.0.3", + "@matterlabs/zksync-contracts": "0.6.1", "@nomiclabs/hardhat-ethers": "^2.2.3", "@openzeppelin/contracts": "^4.9.3", "@types/chai": "^4.3.4", diff --git a/e2e-tests/test/erc20-fixed-paymaster.test.ts b/e2e-tests/test/erc20-fixed-paymaster.test.ts new file mode 100644 index 00000000..6cbc3551 --- /dev/null +++ b/e2e-tests/test/erc20-fixed-paymaster.test.ts @@ -0,0 +1,115 @@ +import { expect } from "chai"; +import { Wallet, Provider, Contract, utils } from "zksync-web3"; +import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; +import * as ethers from "ethers"; +import * as hre from "hardhat"; + +import { expectThrowsAsync, getTestProvider } from "../helpers/utils"; +import { RichAccounts } from "../helpers/constants"; + +describe("ERC20FixedPaymaster", function () { + let provider: Provider; + let richWallet: Wallet; + let deployer: Deployer; + let userWallet: Wallet; + let paymaster: Contract; + let greeter: Contract; + let token: Contract; + + before(async function () { + provider = getTestProvider(); + richWallet = new Wallet(RichAccounts[0].PrivateKey, provider); + deployer = new Deployer(hre, richWallet); + + // Setup new wallet + const emptyWallet = Wallet.createRandom(); + userWallet = new Wallet(emptyWallet.privateKey, provider); + + // Deploy ERC20 token, Paymaster, and Greeter + let artifact = await deployer.loadArtifact("MyERC20"); + token = await deployer.deploy(artifact, ["MyToken", "MyToken", 18]); + artifact = await deployer.loadArtifact("ERC20FixedPaymaster"); + paymaster = await deployer.deploy(artifact, [token.address]); + artifact = await deployer.loadArtifact("Greeter"); + greeter = await deployer.deploy(artifact, ["Hi"]); + + // Fund Paymaster + await provider.send("hardhat_setBalance", [paymaster.address, ethers.utils.parseEther("10")._hex]); + }); + + async function executeGreetingTransaction(user: Wallet, greeting: string) { + const gasPrice = await provider.getGasPrice(); + const token_address = token.address.toString(); + + const paymasterParams = utils.getPaymasterParams(paymaster.address, { + type: "ApprovalBased", + token: token_address, + minimalAllowance: ethers.BigNumber.from(1), + // empty bytes as testnet paymaster does not use innerInput + innerInput: new Uint8Array(), + }); + + // Estimate gasLimit via paymaster + const gasLimit = await greeter.connect(user).estimateGas.setGreeting(greeting, { + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + paymasterParams, + }, + }); + + const setGreetingTx = await greeter.connect(user).setGreeting(greeting, { + maxPriorityFeePerGas: ethers.BigNumber.from(0), + maxFeePerGas: gasPrice, + gasLimit, + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + paymasterParams, + }, + }); + + await setGreetingTx.wait(); + } + + it("user with MyERC20 token can update message for free", async function () { + // Arrange + const initialMintAmount = ethers.utils.parseEther("3"); + const success = await token.mint(userWallet.address, initialMintAmount); + await success.wait(); + + const userInitialTokenBalance = await token.balanceOf(userWallet.address); + const userInitialETHBalance = await userWallet.getBalance(); + const initialPaymasterBalance = await provider.getBalance(paymaster.address); + + // Act + await executeGreetingTransaction(userWallet, "Hola, mundo!"); + + // Assert + const finalETHBalance = await userWallet.getBalance(); + const finalUserTokenBalance = await token.balanceOf(userWallet.address); + const finalPaymasterBalance = await provider.getBalance(paymaster.address); + + expect(await greeter.greet()).to.equal("Hola, mundo!"); + expect(initialPaymasterBalance.gt(finalPaymasterBalance)).to.be.true; + expect(userInitialETHBalance).to.eql(finalETHBalance); + expect(userInitialTokenBalance.gt(finalUserTokenBalance)).to.be.true; + }); + + it("should allow owner to withdraw all funds", async function () { + // Arrange + // Act + const tx = await paymaster.connect(richWallet).withdraw(userWallet.address); + await tx.wait(); + + // Assert + const finalContractBalance = await provider.getBalance(paymaster.address); + expect(finalContractBalance).to.eql(ethers.BigNumber.from(0)); + }); + + it("should prevent non-owners from withdrawing funds", async function () { + const action = async () => { + await paymaster.connect(userWallet).withdraw(userWallet.address); + }; + + await expectThrowsAsync(action, "Ownable: caller is not the owner"); + }); +}); diff --git a/e2e-tests/test/erc721-gated-paymaster.test.ts b/e2e-tests/test/erc721-gated-paymaster.test.ts new file mode 100644 index 00000000..3229136b --- /dev/null +++ b/e2e-tests/test/erc721-gated-paymaster.test.ts @@ -0,0 +1,118 @@ +import { expect } from "chai"; +import { Wallet, Provider, Contract, utils } from "zksync-web3"; +import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; +import * as ethers from "ethers"; +import * as hre from "hardhat"; + +import { expectThrowsAsync, getTestProvider } from "../helpers/utils"; +import { RichAccounts } from "../helpers/constants"; + +describe("ERC721GatedPaymaster", function () { + let provider: Provider; + let wallet: Wallet; + let deployer: Deployer; + let nftUserWallet: Wallet; + let paymaster: Contract; + let greeter: Contract; + let erc721: Contract; + + before(async function () { + provider = getTestProvider(); + wallet = new Wallet(RichAccounts[0].PrivateKey, provider); + deployer = new Deployer(hre, wallet); + + // Setup new wallets + nftUserWallet = Wallet.createRandom(); + nftUserWallet = new Wallet(nftUserWallet.privateKey, provider); + + // Deploy NFT and Paymaster + let artifact = await deployer.loadArtifact("MyNFT"); + erc721 = await deployer.deploy(artifact, []); + artifact = await deployer.loadArtifact("ERC721GatedPaymaster"); + paymaster = await deployer.deploy(artifact, [erc721.address]); + artifact = await deployer.loadArtifact("Greeter"); + greeter = await deployer.deploy(artifact, ["Hi"]); + + // Fund Paymaster + await provider.send("hardhat_setBalance", [paymaster.address, ethers.utils.parseEther("10")._hex]); + + // Assign NFT to nftUserWallet + const tx = await erc721.mint(nftUserWallet.address); + await tx.wait(); + }); + + async function executeGreetingTransaction(user: Wallet, greeting: string) { + const gasPrice = await provider.getGasPrice(); + const paymasterParams = utils.getPaymasterParams(paymaster.address, { + type: "General", + // empty bytes as paymaster does not use innerInput + innerInput: new Uint8Array(), + }); + + // estimate gasLimit via paymaster + const gasLimit = await greeter.connect(user).estimateGas.setGreeting(greeting, { + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + paymasterParams: paymasterParams, + }, + }); + + const setGreetingTx = await greeter.connect(user).setGreeting(greeting, { + maxPriorityFeePerGas: ethers.BigNumber.from(0), + maxFeePerGas: gasPrice, + gasLimit, + customData: { + gasPerPubdata: utils.DEFAULT_GAS_PER_PUBDATA_LIMIT, + paymasterParams, + }, + }); + + await setGreetingTx.wait(); + } + + it("should not pay for gas fees when user has NFT", async function () { + // Arrange + const initialBalance = await nftUserWallet.getBalance(); + + // Act + await executeGreetingTransaction(nftUserWallet, "Hello World"); + + // Assert + expect(await greeter.greet()).to.equal("Hello World"); + const newBalance = await nftUserWallet.getBalance(); + expect(newBalance).to.eql(initialBalance); + }); + + it("should require the user to have the NFT", async function () { + // Arrange + let normalUserWallet = Wallet.createRandom(); + normalUserWallet = new Wallet(normalUserWallet.privateKey, provider); + + // Act + const action = async () => { + await executeGreetingTransaction(normalUserWallet, "Hello World"); + }; + + // Assert + await expectThrowsAsync(action, "User does not hold the required NFT asset and therefore must for their own gas!"); + }); + + it("should allow owner to withdraw all funds", async function () { + // Arrange + // Act + const tx = await paymaster.connect(wallet).withdraw(nftUserWallet.address); + await tx.wait(); + + // Assert + const finalContractBalance = await provider.getBalance(paymaster.address); + expect(finalContractBalance).to.eql(ethers.BigNumber.from(0)); + }); + + it("should prevent non-owners from withdrawing funds", async function () { + const action = async () => { + await paymaster.connect(nftUserWallet).withdraw(nftUserWallet.address); + }; + + await expectThrowsAsync(action, "Ownable: caller is not the owner"); + }); +}); diff --git a/e2e-tests/test/main.test.ts b/e2e-tests/test/main.test.ts index d2412dff..847b434a 100644 --- a/e2e-tests/test/main.test.ts +++ b/e2e-tests/test/main.test.ts @@ -42,7 +42,7 @@ describe("Greeter Smart Contract", function () { const greeter = await deployer.deploy(artifact, ["Hello, world!"]); // should revert - const tx = await greeter.connect(userWallet).setGreeting("Hola, mundo!"); + const tx = await greeter.connect(userWallet).setGreetingByOwner("Hola, mundo!"); await tx.wait(); }; diff --git a/e2e-tests/yarn.lock b/e2e-tests/yarn.lock index b417312f..2fe3c168 100644 --- a/e2e-tests/yarn.lock +++ b/e2e-tests/yarn.lock @@ -507,6 +507,11 @@ resolved "https://registry.yarnpkg.com/@matterlabs/prettier-config/-/prettier-config-1.0.3.tgz#3e2eb559c0112bbe9671895f935700dad2a15d38" integrity sha512-JW7nHREPqEtjBWz3EfxLarkmJBD8vi7Kx/1AQ6eBZnz12eHc1VkOyrc6mpR5ogTf0dOUNXFAfZut+cDe2dn4kQ== +"@matterlabs/zksync-contracts@0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@matterlabs/zksync-contracts/-/zksync-contracts-0.6.1.tgz#39f061959d5890fd0043a2f1ae710f764b172230" + integrity sha512-+hucLw4DhGmTmQlXOTEtpboYCaOm/X2VJcWmnW4abNcOgQXEHX+mTxQrxEfPjIZT0ZE6z5FTUrOK9+RgUZwBMQ== + "@metamask/eth-sig-util@^4.0.0": version "4.0.1" resolved "https://registry.yarnpkg.com/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz#3ad61f6ea9ad73ba5b19db780d40d9aae5157088" diff --git a/src/node.rs b/src/node.rs index b2fa90d7..63a8cae3 100644 --- a/src/node.rs +++ b/src/node.rs @@ -49,18 +49,19 @@ use zksync_core::api_server::web3::backend_jsonrpc::{ error::into_jsrpc_error, namespaces::eth::EthNamespaceT, }; use zksync_state::{ReadStorage, StoragePtr, StorageView, WriteStorage}; -use zksync_types::vm_trace::Call; use zksync_types::{ api::{Block, DebugCall, Log, TransactionReceipt, TransactionVariant}, block::legacy_miniblock_hash, fee::Fee, get_code_key, get_nonce_key, l2::L2Tx, + l2::TransactionType, transaction_request::TransactionRequest, utils::{ decompose_full_nonce, nonces_to_full_nonce, storage_key_for_eth_balance, storage_key_for_standard_token_balance, }, + vm_trace::Call, PackedEthSignature, StorageKey, StorageLogQueryType, StorageValue, Transaction, ACCOUNT_CODE_STORAGE_ADDRESS, EIP_712_TX_TYPE, L2_ETH_TOKEN_ADDRESS, MAX_GAS_PER_PUBDATA_BYTE, MAX_L2_TX_GAS_LIMIT, @@ -401,13 +402,26 @@ impl InMemoryNodeInner { &self, req: zksync_types::transaction_request::CallRequest, ) -> jsonrpc_core::Result { - let mut l2_tx = match L2Tx::from_request(req.into(), MAX_TX_SIZE) { - Ok(tx) => tx, - Err(e) => { - let error = Web3Error::SerializationError(e); - return Err(into_jsrpc_error(error)); + let mut request_with_gas_per_pubdata_overridden = req; + + if let Some(ref mut eip712_meta) = request_with_gas_per_pubdata_overridden.eip712_meta { + if eip712_meta.gas_per_pubdata == U256::zero() { + eip712_meta.gas_per_pubdata = MAX_GAS_PER_PUBDATA_BYTE.into(); } - }; + } + + let is_eip712 = request_with_gas_per_pubdata_overridden + .eip712_meta + .is_some(); + + let mut l2_tx = + match L2Tx::from_request(request_with_gas_per_pubdata_overridden.into(), MAX_TX_SIZE) { + Ok(tx) => tx, + Err(e) => { + let error = Web3Error::SerializationError(e); + return Err(into_jsrpc_error(error)); + } + }; let tx: Transaction = l2_tx.clone().into(); let fair_l2_gas_price = L2_GAS_PRICE; @@ -435,6 +449,12 @@ impl InMemoryNodeInner { l2_tx.common_data.signature[64] = 27; } + // The user may not include the proper transaction type during the estimation of + // the gas fee. However, it is needed for the bootloader checks to pass properly. + if is_eip712 { + l2_tx.common_data.transaction_type = TransactionType::EIP712Transaction; + } + l2_tx.common_data.fee.gas_per_pubdata_limit = MAX_GAS_PER_PUBDATA_BYTE.into(); l2_tx.common_data.fee.max_fee_per_gas = base_fee.into(); l2_tx.common_data.fee.max_priority_fee_per_gas = base_fee.into();