-
Notifications
You must be signed in to change notification settings - Fork 200
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add account abstraction and paymaster features
- Loading branch information
1 parent
a9b1a21
commit c10bbc5
Showing
28 changed files
with
2,020 additions
and
140 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
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,90 +1,90 @@ | ||
import os | ||
|
||
from eth_account import Account | ||
from eth_account.signers.local import LocalAccount | ||
from web3 import Web3 | ||
from eth_typing import HexAddress, HexStr | ||
|
||
from examples.utils import EnvPrivateKey | ||
from zksync2.core.types import ZkBlockParams, ADDRESS_DEFAULT, Token | ||
from zksync2.manage_contracts.erc20_contract import ERC20Contract, ERC20Encoder | ||
from zksync2.core.types import EthBlockParams | ||
from zksync2.manage_contracts.erc20_contract import get_erc20_abi | ||
from zksync2.module.module_builder import ZkSyncBuilder | ||
from zksync2.signer.eth_signer import PrivateKeyEthSigner | ||
from zksync2.transaction.transaction_builders import TxFunctionCall | ||
|
||
ZKSYNC_TEST_URL = "https://testnet.era.zksync.dev" | ||
ETH_TEST_URL = "https://rpc.ankr.com/eth_goerli" | ||
SERC20_Address = Web3.to_checksum_address("0xd782e03F4818A7eDb0bc5f70748F67B4e59CdB33") | ||
|
||
|
||
class Colors: | ||
HEADER = '\033[95m' | ||
OKBLUE = '\033[94m' | ||
OKCYAN = '\033[96m' | ||
OKGREEN = '\033[92m' | ||
WARNING = '\033[93m' | ||
FAIL = '\033[91m' | ||
ENDC = '\033[0m' | ||
BOLD = '\033[1m' | ||
UNDERLINE = '\033[4m' | ||
|
||
|
||
def transfer_erc20(amount: float): | ||
env1 = EnvPrivateKey("ZKSYNC_TEST_KEY") | ||
env2 = EnvPrivateKey("ZKSYNC_TEST_KEY2") | ||
web3 = ZkSyncBuilder.build(ZKSYNC_TEST_URL) | ||
alice: LocalAccount = Account.from_key(env1.key) | ||
bob: LocalAccount = Account.from_key(env2.key) | ||
chain_id = web3.zksync.chain_id | ||
signer = PrivateKeyEthSigner(alice, chain_id) | ||
|
||
erc20_token = Token(l1_address=ADDRESS_DEFAULT, | ||
l2_address=SERC20_Address, | ||
symbol="SERC20", | ||
decimals=18) | ||
erc20 = ERC20Contract(web3=web3.zksync, | ||
contract_address=erc20_token.l2_address, | ||
account=alice) | ||
|
||
alice_balance_before = erc20.balance_of(alice.address) | ||
bob_balance_before = erc20.balance_of(bob.address) | ||
print(f"Alice {erc20_token.symbol} balance before : {erc20_token.format_token(alice_balance_before)}") | ||
print(f"Bob {erc20_token.symbol} balance before : {erc20_token.format_token(bob_balance_before)}") | ||
|
||
erc20_encoder = ERC20Encoder(web3) | ||
transfer_params = (bob.address, erc20_token.to_int(amount)) | ||
call_data = erc20_encoder.encode_method("transfer", args=transfer_params) | ||
|
||
nonce = web3.zksync.get_transaction_count(alice.address, ZkBlockParams.COMMITTED.value) | ||
|
||
gas_price = web3.zksync.gas_price | ||
func_call = TxFunctionCall(chain_id=chain_id, | ||
nonce=nonce, | ||
from_=alice.address, | ||
to=erc20_token.l2_address, | ||
data=call_data, | ||
gas_limit=0, # UNKNOWN AT THIS STATE | ||
gas_price=gas_price, | ||
max_priority_fee_per_gas=100000000) | ||
|
||
estimate_gas = web3.zksync.eth_estimate_gas(func_call.tx) | ||
print(f"Fee for transaction is: {estimate_gas * gas_price}") | ||
tx_712 = func_call.tx712(estimated_gas=estimate_gas) | ||
singed_message = signer.sign_typed_data(tx_712.to_eip712_struct()) | ||
msg = tx_712.encode(singed_message) | ||
tx_hash = web3.zksync.send_raw_transaction(msg) | ||
tx_receipt = web3.zksync.wait_for_transaction_receipt(tx_hash, timeout=240, poll_latency=0.5) | ||
print(f"Tx status: {tx_receipt['status']}") | ||
print(f"Tx hash: {tx_receipt['transactionHash'].hex()}") | ||
|
||
alice_balance_after = erc20.balance_of(alice.address) | ||
bob_balance_after = erc20.balance_of(bob.address) | ||
print(f"Alice {erc20_token.symbol} balance before : {erc20_token.format_token(alice_balance_after)}") | ||
print(f"Bob {erc20_token.symbol} balance before : {erc20_token.format_token(bob_balance_after)}") | ||
|
||
if bob_balance_after == bob_balance_before + erc20_token.to_int(amount) and \ | ||
alice_balance_after == alice_balance_before - erc20_token.to_int(amount): | ||
print(f"{Colors.OKGREEN}{amount} of {erc20_token.symbol} tokens have been transferred{Colors.ENDC}") | ||
else: | ||
print(f"{Colors.FAIL}{erc20_token.symbol} transfer has failed{Colors.ENDC}") | ||
def transfer_erc20( | ||
token_contract, | ||
account: LocalAccount, | ||
address: HexAddress, | ||
amount: float) -> HexStr: | ||
""" | ||
Transfer ETH to a desired address on zkSync network | ||
:param token_contract: | ||
Instance of ERC20 contract | ||
:param account: | ||
From which account the transfer will be made | ||
:param address: | ||
Desired ETH address that you want to transfer to. | ||
:param amount: | ||
Desired ETH amount that you want to transfer. | ||
:return: | ||
The transaction hash of the transfer transaction. | ||
""" | ||
tx = token_contract.functions.transfer(address, amount).build_transaction({ | ||
"nonce": zk_web3.zksync.get_transaction_count(account.address, EthBlockParams.LATEST.value), | ||
"from": account.address, | ||
"maxPriorityFeePerGas": 1_000_000, | ||
"maxFeePerGas": zk_web3.zksync.gas_price, | ||
}) | ||
|
||
signed = account.sign_transaction(tx) | ||
|
||
# Send transaction to zkSync network | ||
tx_hash = zk_web3.zksync.send_raw_transaction(signed.rawTransaction) | ||
print(f"Tx: {tx_hash.hex()}") | ||
|
||
tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( | ||
tx_hash, timeout=240, poll_latency=0.5 | ||
) | ||
print(f"Tx status: {tx_receipt['status']}") | ||
|
||
return tx_hash | ||
|
||
|
||
if __name__ == "__main__": | ||
transfer_erc20(1) | ||
# Byte-format private key | ||
# PRIVATE_KEY = bytes.fromhex(os.environ.get("PRIVATE_KEY")) | ||
PRIVATE_KEY = bytes.fromhex("7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110") | ||
|
||
# Set a provider | ||
# PROVIDER = "https://testnet.era.zksync.dev" | ||
PROVIDER = "http://127.0.0.1:3050" | ||
|
||
# Connect to zkSync network | ||
zk_web3 = ZkSyncBuilder.build(PROVIDER) | ||
|
||
# Get account object by providing from private key | ||
account1: LocalAccount = Account.from_key(PRIVATE_KEY) | ||
account2_address = zk_web3.to_checksum_address("0xa61464658AfeAf65CccaaFD3a512b69A83B77618") | ||
|
||
token_address = zk_web3.to_checksum_address("0x2Ed5EfAB90d161DdCC65693bd77c3344200c9a00") | ||
token_contract = zk_web3.zksync.contract(token_address, abi=get_erc20_abi()) | ||
|
||
# Show balance before token transfer | ||
print(f"Account1 Crown balance before transfer: {token_contract.functions.balanceOf(account1.address).call()}") | ||
print(f"Account2 Crown balance before transfer: {token_contract.functions.balanceOf(account2_address).call()}") | ||
|
||
# Perform the ETH transfer | ||
transfer_erc20( | ||
token_contract, | ||
account1, | ||
account2_address, | ||
3 | ||
) | ||
|
||
# Show balance after token transfer | ||
print(f"Account1 Crown balance after transfer: {token_contract.functions.balanceOf(account1.address).call()}") | ||
print(f"Account2 Crown balance after transfer: {token_contract.functions.balanceOf(account2_address).call()}") |
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
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
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,140 @@ | ||
import os | ||
from pathlib import Path | ||
|
||
from eth_account import Account | ||
from eth_account.signers.local import LocalAccount | ||
from eth_typing import HexAddress | ||
from web3 import Web3 | ||
|
||
from zksync2.core.types import EthBlockParams | ||
from zksync2.manage_contracts.contract_encoder_base import ContractEncoder, JsonConfiguration | ||
from zksync2.manage_contracts.precompute_contract_deployer import PrecomputeContractDeployer | ||
from zksync2.module.module_builder import ZkSyncBuilder | ||
from zksync2.signer.eth_signer import PrivateKeyEthSigner | ||
from zksync2.transaction.transaction_builders import TxCreate2Contract | ||
|
||
|
||
def generate_random_salt() -> bytes: | ||
return os.urandom(32) | ||
|
||
|
||
def deploy_contract( | ||
zk_web3: Web3, account: LocalAccount, compiled_contract: Path, constructor_args: [dict | tuple] | ||
) -> HexAddress: | ||
"""Deploy compiled contract on zkSync network using create2() opcode | ||
:param zk_web3: | ||
Instance of ZkSyncBuilder that interacts with zkSync network | ||
:param account: | ||
From which account the deployment contract tx will be made | ||
:param compiled_contract: | ||
Compiled contract source. | ||
:param constructor_args: | ||
Constructor arguments that can be provided via: | ||
dictionary: {"name_": "Crown", "symbol_": "Crown", "decimals": 18} | ||
tuple: ("Crown", "Crown", 18) | ||
:return: | ||
Address of deployed contract. | ||
""" | ||
# Get chain id of zkSync network | ||
chain_id = zk_web3.zksync.chain_id | ||
|
||
# Signer is used to generate signature of provided transaction | ||
signer = PrivateKeyEthSigner(account, chain_id) | ||
|
||
# Get nonce of ETH address on zkSync network | ||
nonce = zk_web3.zksync.get_transaction_count( | ||
account.address, EthBlockParams.PENDING.value | ||
) | ||
|
||
# Deployment of same smart contract (same bytecode) without salt cannot be done twice | ||
# Remove salt if you want to deploy contract only once | ||
random_salt = generate_random_salt() | ||
|
||
# Precompute the address of smart contract | ||
# Use this if there is a case where contract address should be known before deployment | ||
deployer = PrecomputeContractDeployer(zk_web3) | ||
|
||
# Get contract ABI and bytecode information | ||
token_contract = ContractEncoder.from_json(zk_web3, compiled_contract, JsonConfiguration.STANDARD) | ||
|
||
# Encode the constructor arguments | ||
encoded_constructor = token_contract.encode_constructor(**constructor_args) | ||
|
||
# Get precomputed contract address | ||
precomputed_address = deployer.compute_l2_create2_address(sender=account.address, | ||
bytecode=token_contract.bytecode, | ||
constructor=encoded_constructor, | ||
salt=random_salt) | ||
|
||
# Get current gas price in Wei | ||
gas_price = zk_web3.zksync.gas_price | ||
|
||
# Create2 deployment contract transaction | ||
create2_contract = TxCreate2Contract(web3=zk_web3, | ||
chain_id=chain_id, | ||
nonce=nonce, | ||
from_=account.address, | ||
gas_limit=0, | ||
gas_price=gas_price, | ||
bytecode=token_contract.bytecode, | ||
salt=random_salt, | ||
call_data=encoded_constructor) | ||
|
||
# ZkSync transaction gas estimation | ||
estimate_gas = zk_web3.zksync.eth_estimate_gas(create2_contract.tx) | ||
print(f"Fee for transaction is: {Web3.from_wei(estimate_gas * gas_price, 'ether')} ETH") | ||
|
||
# Convert transaction to EIP-712 format | ||
tx_712 = create2_contract.tx712(estimate_gas) | ||
|
||
# Sign message | ||
signed_message = signer.sign_typed_data(tx_712.to_eip712_struct()) | ||
|
||
# Encode signed message | ||
msg = tx_712.encode(signed_message) | ||
|
||
# Deploy contract | ||
tx_hash = zk_web3.zksync.send_raw_transaction(msg) | ||
|
||
# Wait for deployment contract transaction to be included in a block | ||
tx_receipt = zk_web3.zksync.wait_for_transaction_receipt( | ||
tx_hash, timeout=240, poll_latency=0.5 | ||
) | ||
|
||
print(f"Tx status: {tx_receipt['status']}") | ||
contract_address = tx_receipt["contractAddress"] | ||
print(f"Contract address: {contract_address}") | ||
|
||
# Check does precompute address match with deployed address | ||
if precomputed_address.lower() != contract_address.lower(): | ||
raise RuntimeError("Precomputed contract address does now match with deployed contract address") | ||
|
||
return contract_address | ||
|
||
|
||
if __name__ == "__main__": | ||
# Set a provider | ||
PROVIDER = "https://testnet.era.zksync.dev" | ||
|
||
# Byte-format private key | ||
PRIVATE_KEY = bytes.fromhex(os.environ.get("PRIVATE_KEY")) | ||
|
||
# Connect to zkSync network | ||
zk_web3 = ZkSyncBuilder.build(PROVIDER) | ||
|
||
# Get account object by providing from private key | ||
account: LocalAccount = Account.from_key(PRIVATE_KEY) | ||
|
||
# Provide a compiled JSON source contract | ||
contract_path = Path("solidity/custom_paymaster/build/Token.json") | ||
|
||
constructor_arguments = {"name_": "Crown", "symbol_": "Crown", "decimals_": 18} | ||
|
||
# Perform contract deployment | ||
deploy_contract(zk_web3, account, contract_path, constructor_arguments) |
Oops, something went wrong.