Skip to content

Commit

Permalink
feat: add account abstraction and paymaster features
Browse files Browse the repository at this point in the history
  • Loading branch information
danijelTxFusion committed Jun 30, 2023
1 parent a9b1a21 commit c10bbc5
Show file tree
Hide file tree
Showing 28 changed files with 2,020 additions and 140 deletions.
2 changes: 1 addition & 1 deletion examples/02_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def transfer_eth(
Desired ETH amount that you want to transfer.
:return:
The transaction hash of the deposit transaction.
The transaction hash of the transfer transaction.
"""

Expand Down
162 changes: 81 additions & 81 deletions examples/03_transfer_erc20_token.py
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()}")
6 changes: 2 additions & 4 deletions examples/05_deploy_create_with_constructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ def deploy_contract(
)

# Get deployment nonce
nonce_holder = NonceHolder(zk_web3, account)
deployment_nonce = nonce_holder.get_deployment_nonce(account.address)
deployment_nonce = NonceHolder(zk_web3, account).get_deployment_nonce(account.address)

# Precompute the address of smart contract
# Use this if there is a case where contract address should be known before deployment
Expand All @@ -76,7 +75,6 @@ def deploy_contract(
chain_id=chain_id,
nonce=nonce,
from_=account.address,
gas_limit=0, # UNKNOWN AT THIS STATE,
gas_price=gas_price,
bytecode=incrementer_contract.bytecode,
call_data=encoded_constructor,
Expand Down Expand Up @@ -105,7 +103,7 @@ def deploy_contract(

print(f"Tx status: {tx_receipt['status']}")
contract_address = tx_receipt["contractAddress"]
print(f"contract address: {contract_address}")
print(f"Contract address: {contract_address}")

# Check does precompute address match with deployed address
if precomputed_address.lower() != contract_address.lower():
Expand Down
3 changes: 1 addition & 2 deletions examples/06_deploy_create_with_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ def deploy_contract(
chain_id=chain_id,
nonce=nonce,
from_=account.address,
gas_limit=0,
gas_price=gas_price,
bytecode=demo_contract.bytecode,
deps=[foo_contract.bytecode])
Expand Down Expand Up @@ -94,7 +93,7 @@ def deploy_contract(

print(f"Tx status: {tx_receipt['status']}")
contract_address = tx_receipt["contractAddress"]
print(f"contract address: {contract_address}")
print(f"Contract address: {contract_address}")

# Check does precompute address match with deployed address
if precomputed_address.lower() != contract_address.lower():
Expand Down
2 changes: 1 addition & 1 deletion examples/07_deploy_create2.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def deploy_contract(

print(f"Tx status: {tx_receipt['status']}")
contract_address = tx_receipt["contractAddress"]
print(f"contract address: {contract_address}")
print(f"Contract address: {contract_address}")

# Check does precompute address match with deployed address
if precomputed_address.lower() != contract_address.lower():
Expand Down
2 changes: 1 addition & 1 deletion examples/08_deploy_create2_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def deploy_contract(

print(f"Tx status: {tx_receipt['status']}")
contract_address = tx_receipt["contractAddress"]
print(f"contract address: {contract_address}")
print(f"Contract address: {contract_address}")

# Check does precompute address match with deployed address
if precomputed_address.lower() != contract_address.lower():
Expand Down
140 changes: 140 additions & 0 deletions examples/12_deploy_token.py
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)
Loading

0 comments on commit c10bbc5

Please sign in to comment.