-
Notifications
You must be signed in to change notification settings - Fork 78
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Update transaction type for gas estimation if one is not provide…
…d for EIP712 transactions, this fixes paymasters for era-test-node. (#195)
- Loading branch information
1 parent
c8fa0b1
commit 0b4a200
Showing
14 changed files
with
569 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <[email protected]>"] | ||
homepage = "https://zksync.io/" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 {} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.