Skip to content
This repository has been archived by the owner on Dec 15, 2023. It is now read-only.

Mint tx v1 #384

Merged
merged 8 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions starknet_devnet/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
Account class and its predefined constants.
"""

from starkware.cairo.lang.vm.crypto import pedersen_hash
from starkware.starknet.core.os.contract_address.contract_address import (
calculate_contract_address_from_hash,
)
from starkware.starknet.public.abi import get_selector_from_name
from starkware.starknet.testing.contract import StarknetContract
from starkware.starknet.testing.starknet import Starknet

from starknet_devnet.account_util import set_balance
from starknet_devnet.contract_class_wrapper import ContractClassWrapper
from starknet_devnet.util import Uint256


class Account:
Expand All @@ -36,7 +35,7 @@ def __init__(
# the only thing that affects the account address
self.address = calculate_contract_address_from_hash(
salt=20,
class_hash=1803505466663265559571280894381905521939782500874858933595227108099796801620,
class_hash=0x3FCBF77B28C96F4F2FB5BD2D176AB083A12A5E123ADEB0DE955D7EE228C9854,
constructor_calldata=[public_key],
deployer_address=0,
)
Expand All @@ -52,7 +51,7 @@ def to_json(self):
}

async def deploy(self) -> StarknetContract:
"""Deploy this account."""
"""Deploy this account and set its balance."""
starknet: Starknet = self.starknet_wrapper.starknet
contract_class = self.contract_class
await starknet.state.state.set_contract_class(
Expand All @@ -64,15 +63,4 @@ async def deploy(self) -> StarknetContract:
self.address, get_selector_from_name("Account_public_key"), self.public_key
)

# set initial balance
fee_token_address = starknet.state.general_config.fee_token_address
balance_address = pedersen_hash(
get_selector_from_name("ERC20_balances"), self.address
)
initial_balance_uint256 = Uint256.from_felt(self.initial_balance)
await starknet.state.state.set_storage_at(
fee_token_address, balance_address, initial_balance_uint256.low
)
await starknet.state.state.set_storage_at(
fee_token_address, balance_address + 1, initial_balance_uint256.high
)
await set_balance(starknet.state, self.address, self.initial_balance)
131 changes: 131 additions & 0 deletions starknet_devnet/account_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
"""
Utilities for OZ (not Starknet CLI) implementation of Starknet Account
Latest changes based on https://github.com/OpenZeppelin/nile/pull/184
"""

from typing import List, NamedTuple, Sequence, Tuple

from starkware.cairo.lang.vm.crypto import pedersen_hash
from starkware.crypto.signature.signature import sign
from starkware.starknet.core.os.transaction_hash.transaction_hash import (
TransactionHashPrefix,
calculate_transaction_hash_common,
)
from starkware.starknet.definitions.general_config import StarknetChainId
from starkware.starknet.public.abi import get_selector_from_name
from starkware.starknet.testing.starknet import StarknetState

from .util import Uint256


class AccountCall(NamedTuple):
"""Things needed to interact through Account"""

to_address: str
"""The address of the called contract"""

function: str
inputs: List[str]


def _from_call_to_call_array(calls: List[AccountCall]):
"""Transforms calls to call_array and calldata."""
call_array = []
calldata = []

for call_tuple in calls:
call_tuple = AccountCall(*call_tuple)

entry = (
int(call_tuple.to_address, 16),
get_selector_from_name(call_tuple.function),
len(calldata),
len(call_tuple.inputs),
)
call_array.append(entry)
calldata.extend(int(data) for data in call_tuple.inputs)

return (call_array, calldata)


def _get_execute_calldata(call_array, calldata):
"""Get calldata for __execute__."""
return [
len(call_array),
*[x for t in call_array for x in t],
len(calldata),
*calldata,
]


# pylint: disable=too-many-arguments
def get_execute_args(
calls: List[AccountCall],
account_address: str,
private_key: int,
nonce: int,
version: int,
max_fee=None,
chain_id=StarknetChainId.TESTNET,
):
"""Returns signature and execute calldata"""

# get execute calldata
(call_array, calldata) = _from_call_to_call_array(calls)
execute_calldata = _get_execute_calldata(call_array, calldata)

# get signature
message_hash = _get_transaction_hash(
contract_address=int(account_address, 16),
calldata=execute_calldata,
nonce=nonce,
version=version,
max_fee=max_fee,
chain_id=chain_id,
)
signature = _get_signature(message_hash, private_key)

return signature, execute_calldata


def _get_transaction_hash(
contract_address: int,
calldata: Sequence[int],
nonce: int,
version: int,
max_fee: int,
chain_id=StarknetChainId.TESTNET,
) -> str:
"""Get transaction hash for execute transaction."""
return calculate_transaction_hash_common(
tx_hash_prefix=TransactionHashPrefix.INVOKE,
version=version,
contract_address=contract_address,
entry_point_selector=0,
calldata=calldata,
max_fee=max_fee,
chain_id=chain_id.value,
additional_data=[nonce],
)


def _get_signature(message_hash: int, private_key: int) -> Tuple[str, str]:
"""Get signature from message hash and private key."""
sig_r, sig_s = sign(message_hash, private_key)
return [str(sig_r), str(sig_s)]


async def set_balance(state: StarknetState, address: int, balance: int):
"""Modify `state` so that `address` has `balance`"""

fee_token_address = state.general_config.fee_token_address

balance_address = pedersen_hash(get_selector_from_name("ERC20_balances"), address)
balance_uint256 = Uint256.from_felt(balance)

await state.state.set_storage_at(
fee_token_address, balance_address, balance_uint256.low
)
await state.state.set_storage_at(
fee_token_address, balance_address + 1, balance_uint256.high
)
24 changes: 24 additions & 0 deletions starknet_devnet/chargeable_account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""
Account that is charged with a fee when nobody else can be charged.
"""

from starknet_devnet.account import Account


class ChargeableAccount(Account):
"""
A well-funded account that can be charged with a fee when no other account can.
mikiw marked this conversation as resolved.
Show resolved Hide resolved
"""

PRIVATE_KEY = 0x5FB2959E3011A873A7160F5BB32B0ECE
mikiw marked this conversation as resolved.
Show resolved Hide resolved
PUBLIC_KEY = 0x4C37AB4F0994879337BFD4EAD0800776DB57DA382B8ED8EFAA478C5D3B942A4
ADDRESS = 0x1CAF2DF5ED5DDE1AE3FAEF4ACD72522AC3CB16E23F6DC4C7F9FAED67124C511

def __init__(self, starknet_wrapper):
super().__init__(
starknet_wrapper,
private_key=ChargeableAccount.PRIVATE_KEY,
public_key=ChargeableAccount.PUBLIC_KEY,
initial_balance=2**251, # loads of cash
account_class_wrapper=starknet_wrapper.config.account_class,
)
52 changes: 40 additions & 12 deletions starknet_devnet/fee_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
from starkware.starknet.testing.contract import StarknetContract
from starkware.starknet.testing.starknet import Starknet

from starknet_devnet.account_util import get_execute_args
from starknet_devnet.chargeable_account import ChargeableAccount
from starknet_devnet.constants import SUPPORTED_TX_VERSION
from starknet_devnet.sequencer_api_utils import InternalInvokeFunction
from starknet_devnet.util import Uint256, str_to_felt

Expand All @@ -21,7 +24,7 @@ class FeeToken:

# Precalculated
# HASH = to_bytes(compute_class_hash(contract_class=FeeToken.get_contract_class()))
HASH = 3000409729603134799471314790024123407246450023546294072844903167350593031855
HASH = 0x6A22BF63C7BC07EFFA39A25DFBD21523D211DB0100A0AFD054D172B81840EAF
HASH_BYTES = to_bytes(HASH)

# Taken from
Expand Down Expand Up @@ -75,6 +78,11 @@ async def deploy(self):
get_selector_from_name("ERC20_decimals"),
18,
)
await starknet.state.state.set_storage_at(
FeeToken.ADDRESS,
get_selector_from_name("Ownable_owner"),
ChargeableAccount.ADDRESS,
)

self.contract = StarknetContract(
state=starknet.state,
Expand All @@ -92,18 +100,38 @@ async def get_balance(self, address: int) -> int:
).to_felt()
return balance

@classmethod
def get_mint_transaction(cls, to_address: int, amount: Uint256):
async def get_mint_transaction(self, fundable_address: int, amount: Uint256):
"""Construct a transaction object representing minting request"""

starknet: Starknet = self.starknet_wrapper.starknet
calldata = [
str(fundable_address),
str(amount.low),
str(amount.high),
]

version = SUPPORTED_TX_VERSION
max_fee = int(1e18) # big enough

nonce = await starknet.state.state.get_nonce_at(ChargeableAccount.ADDRESS)
chargeable_address = hex(ChargeableAccount.ADDRESS)
signature, execute_calldata = get_execute_args(
calls=[(hex(FeeToken.ADDRESS), "mint", calldata)],
account_address=chargeable_address,
private_key=ChargeableAccount.PRIVATE_KEY,
nonce=nonce,
version=version,
max_fee=max_fee,
chain_id=starknet.state.general_config.chain_id,
)

transaction_data = {
"entry_point_selector": hex(get_selector_from_name("mint")),
"calldata": [
str(to_address),
str(amount.low),
str(amount.high),
],
"signature": [],
"contract_address": hex(cls.ADDRESS),
"calldata": [str(v) for v in execute_calldata],
"contract_address": chargeable_address,
"nonce": hex(nonce),
"max_fee": hex(max_fee),
"signature": signature,
"version": hex(version),
}
return InvokeFunction.load(transaction_data)

Expand All @@ -115,7 +143,7 @@ async def mint(self, to_address: int, amount: int, lite: bool):
amount_uint256 = Uint256.from_felt(amount)

tx_hash = None
transaction = self.get_mint_transaction(to_address, amount_uint256)
transaction = await self.get_mint_transaction(to_address, amount_uint256)
starknet: Starknet = self.starknet_wrapper.starknet
if lite:
internal_tx = InternalInvokeFunction.from_external(
Expand Down
9 changes: 9 additions & 0 deletions starknet_devnet/starknet_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
from .block_info_generator import BlockInfoGenerator
from .blocks import DevnetBlocks
from .blueprints.rpc.structures.types import Felt
from .chargeable_account import ChargeableAccount
from .constants import DUMMY_STATE_ROOT, OZ_ACCOUNT_CLASS_HASH
from .devnet_config import DevnetConfig
from .fee_token import FeeToken
Expand All @@ -74,6 +75,7 @@
get_fee_estimation_info,
get_storage_diffs,
to_bytes,
warn,
)

enable_pickling()
Expand Down Expand Up @@ -124,6 +126,7 @@ async def initialize(self):

await self.fee_token.deploy()
await self.accounts.deploy()
await self.__deploy_chargeable_account()
await self.__predeclare_oz_account()
await self.__udc.deploy()

Expand Down Expand Up @@ -701,6 +704,12 @@ async def __predeclare_oz_account(self):
to_bytes(OZ_ACCOUNT_CLASS_HASH), oz_account_class
)

async def __deploy_chargeable_account(self):
if await self.is_deployed(ChargeableAccount.ADDRESS):
warn("Chargeable account already deployed")
else:
await ChargeableAccount(self).deploy()

async def is_deployed(self, address: int) -> bool:
"""
Check if the contract is deployed.
Expand Down
Loading