Skip to content

Commit

Permalink
Fix/load (0xSpaceShard#202)
Browse files Browse the repository at this point in the history
* Fix loading via http if no file

* Refactor FeeToken

* Fix mint
  • Loading branch information
FabijanC authored Aug 2, 2022
1 parent 51342fa commit 300d37a
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 46 deletions.
34 changes: 22 additions & 12 deletions starknet_devnet/blueprints/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Base routes
"""
from pickle import UnpicklingError
from flask import Blueprint, Response, request, jsonify
from starknet_devnet.fee_token import FeeToken

Expand Down Expand Up @@ -78,7 +79,14 @@ def load():
if not load_path:
raise StarknetDevnetException(message="No path provided.", status_code=400)

state.load(load_path)
try:
state.load(load_path)
except (FileNotFoundError, UnpicklingError) as error:
raise StarknetDevnetException(
message=f"Error: Cannot load from {load_path}. Make sure the file exists and contains a Devnet dump.",
status_code=400
) from error

return Response(status=200)

@base.route("/increase_time", methods=["POST"])
Expand All @@ -104,9 +112,9 @@ def set_time():
@base.route("/account_balance", methods=["GET"])
async def get_balance():
"""Gets balance for the address"""

address = request.args.get("address", type=lambda x: int(x, 16))
balance = await FeeToken.get_balance(address)

balance = await state.starknet_wrapper.get_balance(address)
return jsonify({
"amount": balance,
"unit": "wei"
Expand All @@ -132,15 +140,17 @@ async def mint():

address = extract_hex_string(request_json, "address")
amount = extract_positive(request_json, "amount")

is_lite = request_json.get("lite", False)

tx_hash = None
if is_lite:
await FeeToken.mint_lite(address, amount)
else:
tx_hash_int = await FeeToken.mint(address, amount, state.starknet_wrapper)
tx_hash = hex(tx_hash_int)
tx_hash = await state.starknet_wrapper.mint(
to_address=address,
amount=amount,
lite=is_lite
)

new_balance = await FeeToken.get_balance(address)
return jsonify({"new_balance": new_balance, "unit": "wei", "tx_hash": tx_hash})
new_balance = await state.starknet_wrapper.get_balance(address)
return jsonify({
"new_balance": new_balance,
"unit": "wei",
"tx_hash": tx_hash
})
45 changes: 15 additions & 30 deletions starknet_devnet/fee_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,20 @@ def get_contract_class(cls):
cls.CONTRACT_CLASS = ContractClass.load(load_nearby_contract("ERC20_Mintable_OZ_0.2.0"))
return cls.CONTRACT_CLASS

@classmethod
async def deploy(cls, starknet: Starknet):
async def deploy(self, starknet: Starknet):
"""Deploy token contract for charging fees."""

fee_token_carried_state = starknet.state.state.contract_states[cls.ADDRESS]
fee_token_carried_state = starknet.state.state.contract_states[FeeToken.ADDRESS]
fee_token_state = fee_token_carried_state.state
assert not fee_token_state.initialized

starknet.state.state.contract_definitions[cls.HASH_BYTES] = cls.get_contract_class()
starknet.state.state.contract_definitions[FeeToken.HASH_BYTES] = FeeToken.get_contract_class()
newly_deployed_fee_token_state = await ContractState.create(
contract_hash=cls.HASH_BYTES,
contract_hash=FeeToken.HASH_BYTES,
storage_commitment_tree=fee_token_state.storage_commitment_tree
)

starknet.state.state.contract_states[cls.ADDRESS] = ContractCarriedState(
starknet.state.state.contract_states[FeeToken.ADDRESS] = ContractCarriedState(
state=newly_deployed_fee_token_state,
storage_updates={
# Running the constructor doesn't need to be simulated
Expand All @@ -63,18 +62,16 @@ async def deploy(cls, starknet: Starknet):
}
)

cls.contract = StarknetContract(
self.contract = StarknetContract(
state=starknet.state,
abi=cls.get_contract_class().abi,
contract_address=cls.ADDRESS,
abi=FeeToken.get_contract_class().abi,
contract_address=FeeToken.ADDRESS,
deploy_execution_info=None
)

@classmethod
async def get_balance(cls, address: int) -> int:
async def get_balance(self, address: int) -> int:
"""Return the balance of the contract under `address`."""
assert cls.contract
response = await cls.contract.balanceOf(address).call()
response = await self.contract.balanceOf(address).call()

balance = Uint256(
low=response.result.balance.low,
Expand All @@ -83,28 +80,16 @@ async def get_balance(cls, address: int) -> int:
return balance

@classmethod
async def mint_lite(cls, to_address: int, amount: int) -> None:
"""Mint `amount` of token at `to_address` without creating a tx."""
assert cls.contract
amount_uint256 = Uint256.from_felt(amount)
await cls.contract.mint(to_address, (amount_uint256.low, amount_uint256.high)).invoke()

@classmethod
async def mint(cls, to_address: int, amount: int, starknet_wrapper):
"""Mint `amount` of token at `to_address` with creating a tx."""
assert cls.contract
amount_uint256 = Uint256.from_felt(amount)

def get_mint_transaction(cls, to_address: int, amount: Uint256):
"""Construct a transaction object representing minting request"""
transaction_data = {
"entry_point_selector": hex(get_selector_from_name("mint")),
"calldata": [
str(to_address),
str(amount_uint256.low),
str(amount_uint256.high),
str(amount.low),
str(amount.high),
],
"signature": [],
"contract_address": hex(cls.ADDRESS)
}
transaction = InvokeFunction.load(transaction_data)
_, tx_hash, _ = await starknet_wrapper.invoke(transaction)
return tx_hash
return InvokeFunction.load(transaction_data)
31 changes: 29 additions & 2 deletions starknet_devnet/starknet_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from .origin import NullOrigin, Origin
from .util import (
DummyExecutionInfo,
Uint256,
enable_pickling,
generate_state_update,
to_bytes
Expand Down Expand Up @@ -69,6 +70,7 @@ def __init__(self, config: DevnetConfig):
self.__starknet = None
self.__current_carried_state = None
self.__initialized = False
self.__fee_token = FeeToken()

self.accounts: List[Account] = []
"""List of predefined accounts"""
Expand Down Expand Up @@ -170,8 +172,8 @@ async def __store_transaction(

async def __deploy_fee_token(self):
starknet = await self.__get_starknet()
await FeeToken.deploy(starknet)
self.contracts.store(FeeToken.ADDRESS, ContractWrapper(FeeToken.contract, FeeToken.get_contract_class()))
await self.__fee_token.deploy(starknet)
self.contracts.store(FeeToken.ADDRESS, ContractWrapper(self.__fee_token.contract, FeeToken.get_contract_class()))

async def __deploy_accounts(self):
starknet = await self.__get_starknet()
Expand Down Expand Up @@ -405,3 +407,28 @@ def set_block_time(self, time_s: int):
def set_gas_price(self, gas_price: int):
"""Sets gas price to `gas_price`."""
self.block_info_generator.set_gas_price(gas_price)

async def mint(self, to_address: int, amount: int, lite: bool):
"""
Mint `amount` tokens at address `to_address`.
Returns the `tx_hash` (as hex str) if not `lite`; else returns `None`
"""
amount_uint256 = Uint256.from_felt(amount)

tx_hash = None
if lite:
await self.__fee_token.contract.mint(
to_address,
(amount_uint256.low, amount_uint256.high)
).invoke()
else:
transaction = self.__fee_token.get_mint_transaction(to_address, amount_uint256)
_, tx_hash_int, _ = await self.invoke(transaction)
tx_hash = hex(tx_hash_int)

return tx_hash

async def get_balance(self, address: int):
"""Returns balance at `address` as stored in fee token contract."""

return await self.__fee_token.get_balance(address)
36 changes: 34 additions & 2 deletions test/test_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import pytest

from .test_account import get_account_balance
from .test_fee_token import mint
from .util import call, deploy, devnet_in_background, invoke, run_devnet_in_background, terminate_and_wait
from .settings import APP_URL
from .shared import CONTRACT_PATH, ABI_PATH
Expand Down Expand Up @@ -103,14 +105,44 @@ def deploy_empty_contract():
assert initial_balance == "0"
return contract_address

def test_load_if_no_file():
"""Test loading if dump file not present."""
def test_load_via_cli_if_no_file():
"""Test loading via CLI if dump file not present."""
assert_no_dump_present(DUMP_PATH)
devnet_proc = ACTIVE_DEVNET.start("--load-path", DUMP_PATH, stderr=subprocess.PIPE)
assert devnet_proc.returncode == 1
expected_msg = f"Error: Cannot load from {DUMP_PATH}. Make sure the file exists and contains a Devnet dump.\n"
assert expected_msg == devnet_proc.stderr.read().decode("utf-8")

def test_mint_after_load():
"""Assert that minting can be done after loading."""
devnet_proc = ACTIVE_DEVNET.start("--dump-path", DUMP_PATH, "--dump-on", "exit")
dummy_address = "0x1"
initial_balance = get_account_balance(dummy_address)
assert initial_balance == 0

terminate_and_wait(devnet_proc)
assert_dump_present(DUMP_PATH)

loaded_devnet_proc = ACTIVE_DEVNET.start("--load-path", DUMP_PATH)
dummy_amount = 1
resp_body = mint(dummy_address, dummy_amount)
assert resp_body["new_balance"] == dummy_amount

final_balance = get_account_balance(dummy_address)
assert final_balance == dummy_amount

terminate_and_wait(loaded_devnet_proc)

@devnet_in_background()
def test_load_via_http_if_no_file():
"""Test loading via HTTP if dump file not present."""
assert_no_dump_present(DUMP_PATH)

resp = send_load_request(load_path=DUMP_PATH)
expected_msg = f"Error: Cannot load from {DUMP_PATH}. Make sure the file exists and contains a Devnet dump."
assert resp.json()["message"] == expected_msg
assert resp.status_code == 400

@devnet_in_background()
def test_dumping_if_path_not_provided():
"""Assert failure if dumping attempted without a known path."""
Expand Down

0 comments on commit 300d37a

Please sign in to comment.