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

Mint improve #149

Merged
merged 5 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
33 changes: 29 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,11 +411,13 @@ Response:
}
```

## Mintable
## Mint token - Local faucet

Used ERC20 Mintable fee Token
Other than using prefunded predeployed accounts, you can also add funds to an account that you deployed yourself.

### Mint fee token
### Mint with a transaction

By not setting the `lite` parameter or by setting it to `false`, new tokens will be minted in a separate transaction. You will receive the hash of this transaction, as well as the new balance after minting in the response.

```
POST /mint
Expand All @@ -431,7 +433,30 @@ Response:
{
"new_balance": 500000,
"unit": "wei",
"tx_hash": 242245223...
"tx_hash": "0xa24f23..."
}
```

### Mint lite

By setting the `lite` parameter, new tokens will be minted without generating a transaction, thus executing faster.

```
POST /mint
{
"address": "0x6e3205f...",
"amount": 500000,
"lite": true
}
```

Response:

```
{
"new_balance": 500000,
"unit": "wei",
"tx_hash": null
}
```

Expand Down
58 changes: 37 additions & 21 deletions starknet_devnet/blueprints/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,34 @@

base = Blueprint("base", __name__)

def extract_positive(request_json, prop_name: str):
"""Expects `prop_name` from `request_json` and expects it to be positive"""
value = request_json.get(prop_name)

if value is None:
raise StarknetDevnetException(message=f"{prop_name} value must be provided.", status_code=400)

if not isinstance(value, int):
raise StarknetDevnetException(message=f"{prop_name} value must be an integer.", status_code=400)

if value < 0:
raise StarknetDevnetException(message=f"{prop_name} value must be greater than 0.", status_code=400)

return value

def extract_hex_string(request_json, prop_name: str) -> int:
"""Parse value from hex string to int"""
value = request_json.get(prop_name)
if value is None:
raise StarknetDevnetException(status_code=400, message=f"{prop_name} value must be provided.")

try:
return int(value, 16)
except (ValueError, TypeError) as error:
message = f"{prop_name} value must be a hex string."
raise StarknetDevnetException(status_code=400, message=message) from error


@base.route("/is_alive", methods=["GET"])
def is_alive():
"""Health check endpoint."""
Expand Down Expand Up @@ -44,24 +72,11 @@ def load():
state.load(load_path)
return Response(status=200)

def validate_time_value(value: int):
"""Validates the time value"""
if value is None:
raise StarknetDevnetException(message="Time value must be provided.", status_code=400)

if not isinstance(value, int):
raise StarknetDevnetException(message="Time value must be an integer.", status_code=400)

if value < 0:
raise StarknetDevnetException(message="Time value must be greater than 0.", status_code=400)


@base.route("/increase_time", methods=["POST"])
def increase_time():
"""Increases the block timestamp offset"""
request_dict = request.json or {}
time_s = request_dict.get("time")
validate_time_value(time_s)
time_s = extract_positive(request_dict, "time")

state.starknet_wrapper.increase_block_time(time_s)

Expand All @@ -71,8 +86,7 @@ def increase_time():
def set_time():
"""Sets the block timestamp offset"""
request_dict = request.json or {}
time_s = request_dict.get("time")
validate_time_value(time_s)
time_s = extract_positive(request_dict, "time")

state.starknet_wrapper.set_block_time(time_s)

Expand Down Expand Up @@ -100,15 +114,17 @@ async def mint():
"""Mint token and transfer to the provided address"""
request_json = request.json or {}

address = request_json["address"]
amount = request_json["amount"]
is_lite = request_json.get('lite', False)
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, _ = await FeeToken.mint(address, amount, state.starknet_wrapper)
tx_hash_int = await FeeToken.mint(address, amount, state.starknet_wrapper)
tx_hash = hex(tx_hash_int)

new_balance = await FeeToken.get_balance(int(address, 16))
new_balance = await FeeToken.get_balance(address)
return jsonify({"new_balance": new_balance, "unit": "wei", "tx_hash": tx_hash})
15 changes: 8 additions & 7 deletions starknet_devnet/fee_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,27 +88,28 @@ async def get_balance(cls, address: int) -> int:
return balance

@classmethod
async def mint_lite(cls, to_address: str, amount: int) -> None:
"""Mint `amount` of token at `address`."""
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)
return await cls.contract.mint(int(to_address, 16), (amount_uint256.low, amount_uint256.high)).invoke()
await cls.contract.mint(to_address, (amount_uint256.low, amount_uint256.high)).invoke()

@classmethod
async def mint(cls, to_address: str, amount: int, starknet_wrapper) -> None:
"""Mint with internal transaction"""
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)

transaction_data = {
"entry_point_selector": hex(get_selector_from_name("mint")),
"calldata": [
str(int(to_address, 0)),
str(to_address),
str(amount_uint256.low),
str(amount_uint256.high),
],
"signature": [],
"contract_address": hex(cls.ADDRESS)
}
transaction = InvokeFunction.load(transaction_data)
return await starknet_wrapper.invoke(transaction)
_, tx_hash, _ = await starknet_wrapper.invoke(transaction)
return tx_hash
140 changes: 104 additions & 36 deletions test/test_fee_token.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"""Fee token related tests."""
from test.settings import GATEWAY_URL
from test.test_account import deploy_empty_contract, execute, assert_tx_status, get_transaction_receipt, get_account_balance, call, ABI_PATH

from test.settings import APP_URL
from test.test_account import deploy_empty_contract, execute, assert_tx_status, get_transaction_receipt, get_account_balance
import json
import pytest
import requests
from starkware.starknet.core.os.class_hash import compute_class_hash
from starkware.starknet.core.os.contract_address.contract_address import calculate_contract_address_from_hash
from starknet_devnet.fee_token import FeeToken
from .util import assert_equal, devnet_in_background
from starknet_devnet.server import app
from .util import assert_equal, devnet_in_background, get_block

@pytest.mark.fee_token
def test_precomputed_contract_hash():
Expand All @@ -25,40 +28,105 @@ def test_precomputed_address():
)
assert_equal(recalculated_address, FeeToken.ADDRESS)

def mint(address: str, amount: int, lite=False):
"""Sends mint request; returns parsed json body"""
response = requests.post(f"{APP_URL}/mint", json={
"address": address,
"amount": amount,
"lite": lite
})
assert response.status_code == 200
return response.json()

def mint_client(data: dict):
"""Send mint request to app test client"""
return app.test_client().post(
"/mint",
content_type="application/json",
data=json.dumps(data)
)

def test_negative_mint():
"""Assert failure if mint amount negative"""
resp = mint_client({
"amount": -10,
"address": "0x1"
})

assert resp.status_code == 400
assert resp.json["message"] == "amount value must be greater than 0."

def test_mint_amount_not_int():
"""Assert failure if mint amount not int"""
resp = mint_client({
"amount": "abc",
"address": "0x1"
})

assert resp.status_code == 400
assert resp.json["message"] == "amount value must be an integer."

def test_missing_mint_amount():
"""Assert failure if mint amount missing"""
resp = mint_client({
"address": "0x1"
})

assert resp.status_code == 400
assert resp.json["message"] == "amount value must be provided."

def test_wrong_mint_address_format():
"""Assert failure if mint address of wrong format"""
resp = mint_client({
"amount": 10,
"address": "invalid_address"
})

assert resp.status_code == 400
assert resp.json["message"] == "address value must be a hex string."

def test_missing_mint_address():
"""Assert failure if mint address missing"""
resp = mint_client({
"amount": 10
})

assert resp.status_code == 400
assert resp.json["message"] == "address value must be provided."

@pytest.mark.fee_token
@devnet_in_background()
def test_mint():
"""Assert that mint will increase account balance and latest block created with correct transaction amount"""

json_load = {
"address": "0x6e3205f9b7c4328f00f718fdecf56ab31acfb3cd6ffeb999dcbac4123655502",
"amount": 50_000
}
response = requests.post(f"{GATEWAY_URL}/mint", json=json_load)
assert response.status_code == 200
assert response.json().get('new_balance') == 50_000
account_address = "0x6e3205f9b7c4328f00f718fdecf56ab31acfb3cd6ffeb999dcbac4123655502"
response = mint(address=account_address, amount=50_000)
assert response.get("new_balance") == 50_000
assert response.get("unit") == "wei"
assert response.get("tx_hash").startswith("0x")

response = requests.get(f"{GATEWAY_URL}/feeder_gateway/get_block?blockNumber=latest")
get_block(block_number="latest")
response = requests.get(f"{APP_URL}/feeder_gateway/get_block?blockNumber=latest")
assert response.status_code == 200
assert response.json().get('block_number') == 0
assert int(response.json().get('transactions')[0].get('calldata')[1], 16) == 50_000
assert response.json().get("block_number") == 0
assert int(response.json().get("transactions")[0].get("calldata")[1], 16) == 50_000

@pytest.mark.fee_token
@devnet_in_background()
def test_mint_lite():
"""Assert that mint lite will increase account balance without producing block"""
json_load = {
"address": "0x34d09711b5c047471fd21d424afbf405c09fd584057e1d69c77223b535cf769",
"amount": 50_000,
"lite": True
}
response = requests.post(f"{GATEWAY_URL}/mint", json=json_load)
assert response.status_code == 200
assert response.json().get('new_balance') == 50000
response = mint(
address="0x34d09711b5c047471fd21d424afbf405c09fd584057e1d69c77223b535cf769",
amount=50_000,
lite=True
)
assert response.get("new_balance") == 50000
assert response.get("unit") == "wei"
assert response.get("tx_hash") is None

response = requests.get(f"{GATEWAY_URL}/feeder_gateway/get_block?blockNumber=latest")
response = requests.get(f"{APP_URL}/feeder_gateway/get_block?blockNumber=latest")
assert response.status_code == 500
assert response.json().get('message') == 'Requested the latest block, but there are no blocks so far.'
assert response.json().get("message") == "Requested the latest block, but there are no blocks so far."

@pytest.mark.fee_token
@devnet_in_background(
Expand All @@ -68,13 +136,13 @@ def test_mint_lite():
"--initial-balance", "10"
)
def test_increase_balance():
'''Assert no funds for transaction than mint funds and success transaction'''
"""Assert tx failure if insufficient funds; assert tx success after mint"""

deploy_info = deploy_empty_contract()
account_address = "0x347be35996a21f6bf0623e75dbce52baba918ad5ae8d83b6f416045ab22961a"
private_key = 0xbdd640fb06671ad11c80317fa3b1799d
to_address = int(deploy_info["address"], 16)
initial_account_balance = get_account_balance(account_address)
initial_contract_balance = call("get_balance", deploy_info["address"], abi_path=ABI_PATH)

args = [10, 20]
calls = [(to_address, "increase_balance", args)]
Expand All @@ -84,19 +152,19 @@ def test_increase_balance():
invoke_receipt = get_transaction_receipt(invoke_tx_hash)
assert "subtraction overflow" in invoke_receipt["transaction_failure_reason"]["error_message"]

final_contract_balance = call("get_balance", deploy_info["address"], abi_path=ABI_PATH)
assert_equal(final_contract_balance, initial_contract_balance)
intermediate_account_balance = get_account_balance(account_address)
assert_equal(initial_account_balance, intermediate_account_balance)

final_account_balance = get_account_balance(account_address)
assert_equal(initial_account_balance, final_account_balance)
mint_amount = 200_000_000_000_000
mint(address=account_address, amount=mint_amount)
balance_after_mint = get_account_balance(account_address)
assert_equal(balance_after_mint, initial_account_balance + mint_amount)

json_load = {
"address": "0x347be35996a21f6bf0623e75dbce52baba918ad5ae8d83b6f416045ab22961a",
"amount": 200_000_000_000_000
}
response = requests.post(f"{GATEWAY_URL}/mint", json=json_load)
assert response.status_code == 200
invoke_tx_hash = execute(calls, account_address, private_key, max_fee=10 ** 21) # big enough
assert_tx_status(invoke_tx_hash, "ACCEPTED_ON_L2")

invoke_receipt = get_transaction_receipt(invoke_tx_hash)
actual_fee = int(invoke_receipt["actual_fee"], 16)

final_account_balance = get_account_balance(account_address)
assert initial_account_balance != final_account_balance
assert_equal(final_account_balance, initial_account_balance + mint_amount - actual_fee)
Loading