Skip to content

Feature/wallet token supply change #1272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Oct 24, 2023
78 changes: 48 additions & 30 deletions chainstate/src/detail/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use common::{
chain::{
block::{signed_block_header::SignedBlockHeader, BlockReward},
tokens::{
RPCFungibleTokenInfo, RPCNonFungibleTokenInfo, RPCTokenInfo, RPCTokenTotalSupply,
TokenAuxiliaryData, TokenData, TokenId,
NftIssuance, RPCFungibleTokenInfo, RPCNonFungibleTokenInfo, RPCTokenInfo,
RPCTokenTotalSupply, TokenAuxiliaryData, TokenData, TokenId,
},
Block, GenBlock, OutPointSourceId, SignedTransaction, Transaction, TxMainChainIndex,
TxOutput,
Expand Down Expand Up @@ -298,39 +298,29 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
.issuance_tx()
.outputs()
.iter()
// Filter tokens
.filter_map(|output| match output {
// find tokens
.find_map(|output| match output {
TxOutput::Transfer(v, _)
| TxOutput::LockThenTransfer(v, _, _)
| TxOutput::Burn(v) => v.token_data(),
| TxOutput::Burn(v) => v.token_data().and_then(|token_data| {
to_rpc_token_info(token_data, token_id, &token_aux_data)
}),
TxOutput::CreateStakePool(_, _)
| TxOutput::ProduceBlockFromStake(_, _)
| TxOutput::CreateDelegationId(_, _)
| TxOutput::DelegateStaking(_, _)
| TxOutput::IssueFungibleToken(_)
| TxOutput::IssueNft(_, _, _) => None,
})
// Find issuance data and return RPCTokenInfo
.find_map(|token_data| match token_data {
TokenData::TokenIssuance(issuance) => {
Some(RPCTokenInfo::new_fungible(RPCFungibleTokenInfo::new(
token_id,
issuance.token_ticker.clone(),
issuance.number_of_decimals,
issuance.metadata_uri.clone(),
issuance.amount_to_issue,
RPCTokenTotalSupply::Fixed(issuance.amount_to_issue),
)))
}
TokenData::NftIssuance(nft) => {
Some(RPCTokenInfo::new_nonfungible(RPCNonFungibleTokenInfo::new(
token_id,
token_aux_data.issuance_tx().get_id(),
token_aux_data.issuance_block_id(),
&nft.metadata,
)))
}
TokenData::TokenTransfer(_) => None,
| TxOutput::DelegateStaking(_, _) => None,
TxOutput::IssueNft(_, issuance, _) => match issuance.as_ref() {
NftIssuance::V0(nft) => {
Some(RPCTokenInfo::new_nonfungible(RPCNonFungibleTokenInfo::new(
token_id,
token_aux_data.issuance_tx().get_id(),
token_aux_data.issuance_block_id(),
&nft.metadata,
)))
}
},
// Should be handled by the token data branch
TxOutput::IssueFungibleToken(_) => None,
}))
}
}
Expand Down Expand Up @@ -371,3 +361,31 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
self.chainstate_ref.get_circulating_supply(id).map_err(PropertyQueryError::from)
}
}

fn to_rpc_token_info(
token_data: &TokenData,
token_id: TokenId,
token_aux_data: &TokenAuxiliaryData,
) -> Option<RPCTokenInfo> {
match token_data {
TokenData::TokenIssuance(issuance) => {
Some(RPCTokenInfo::new_fungible(RPCFungibleTokenInfo::new(
token_id,
issuance.token_ticker.clone(),
issuance.number_of_decimals,
issuance.metadata_uri.clone(),
issuance.amount_to_issue,
RPCTokenTotalSupply::Fixed(issuance.amount_to_issue),
)))
}
TokenData::NftIssuance(nft) => {
Some(RPCTokenInfo::new_nonfungible(RPCNonFungibleTokenInfo::new(
token_id,
token_aux_data.issuance_tx().get_id(),
token_aux_data.issuance_block_id(),
&nft.metadata,
)))
}
TokenData::TokenTransfer(_) => None,
}
}
19 changes: 14 additions & 5 deletions test/functional/test_framework/wallet_cli_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,16 +175,25 @@ async def send_tokens_to_address(self, token_id: str, address: str, amount: floa

async def issue_new_token(self,
token_ticker: str,
amount_to_issue: str,
number_of_decimals: int,
metadata_uri: str,
destination_address: str) -> Tuple[Optional[str], Optional[str]]:
output = await self._write_command(f'issuenewtoken "{token_ticker}" "{amount_to_issue}" "{number_of_decimals}" "{metadata_uri}" {destination_address}\n')
destination_address: str,
token_supply: str = 'unlimited') -> Tuple[Optional[str], Optional[str]]:
output = await self._write_command(f'issuenewtoken "{token_ticker}" "{number_of_decimals}" "{metadata_uri}" {destination_address} {token_supply}\n')
if output.startswith("A new token has been issued with ID"):
return output[output.find(':')+2:], None

return None, output

async def mint_tokens(self, token_id: str, address: str, amount: int) -> str:
return await self._write_command(f"minttokens {token_id} {address} {amount}\n")

async def unmint_tokens(self, token_id: str, amount: int) -> str:
return await self._write_command(f"unminttokens {token_id} {amount}\n")

async def lock_token_supply(self, token_id: str) -> str:
return await self._write_command(f"locktokensupply {token_id}\n")

async def issue_new_nft(self,
destination_address: str,
media_hash: str,
Expand Down Expand Up @@ -248,5 +257,5 @@ async def stop_staking(self) -> str:
async def get_addresses_usage(self) -> str:
return await self._write_command("showreceiveaddresses\n")

async def get_balance(self, with_locked: str = 'unlocked') -> str:
return await self._write_command(f"getbalance {with_locked}\n")
async def get_balance(self, with_locked: str = 'unlocked', utxo_states: List[str] = ['confirmed']) -> str:
return await self._write_command(f"getbalance {with_locked} {' '.join(utxo_states)}\n")
6 changes: 5 additions & 1 deletion test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import datetime
import locale
import os
import random
import time
import shutil
import signal
Expand Down Expand Up @@ -132,6 +133,7 @@ class UnicodeOnWindowsError(ValueError):
'wallet_recover_accounts.py',
'wallet_get_address_usage.py',
'wallet_tokens.py',
'wallet_tokens_change_supply.py',
'wallet_nfts.py',
'wallet_delegations.py',
'wallet_high_fee.py',
Expand Down Expand Up @@ -443,6 +445,7 @@ def __init__(self, *, num_tests_parallel, tests_dir, tmpdir, test_list, flags, u
self.num_running = 0
self.jobs = []
self.use_term_control = use_term_control
self.randomseed = random.randrange(sys.maxsize)

def get_next(self):
while self.num_running < self.num_jobs and self.test_list:
Expand All @@ -451,14 +454,15 @@ def get_next(self):
test = self.test_list.pop(0)
portseed = len(self.test_list)
portseed_arg = ["--portseed={}".format(portseed)]
randomseed_arg = [f"--randomseed={self.randomseed}"]
log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16)
log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16)
test_argv = test.split()
testdir = "{}/{}_{}".format(self.tmpdir, re.sub(".py$", "", test_argv[0]), portseed)
tmpdir_arg = ["--tmpdir={}".format(testdir)]
self.jobs.append((test,
time.time(),
subprocess.Popen([sys.executable, self.tests_dir + test_argv[0]] + test_argv[1:] + self.flags + portseed_arg + tmpdir_arg,
subprocess.Popen([sys.executable, self.tests_dir + test_argv[0]] + test_argv[1:] + self.flags + portseed_arg + tmpdir_arg + randomseed_arg,
universal_newlines=True,
stdout=log_stdout,
stderr=log_stderr),
Expand Down
4 changes: 2 additions & 2 deletions test/functional/wallet_delegations.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ async def async_test(self):
self.wait_until(lambda: node.chainstate_best_block_id() != tip_id, timeout = 5)
assert_in("Success", await wallet.sync())

# check that we still don't have any delagations for this account
# check that we still don't have any delegations for this account
delegations = await wallet.list_delegation_ids()
assert_equal(len(delegations), 0)

Expand All @@ -399,7 +399,7 @@ async def async_test(self):
self.wait_until(lambda: node.chainstate_best_block_id() != tip_id, timeout = 5)
assert_in("Success", await wallet.sync())

# check that we still don't have any delagations for this account
# check that we still don't have any delegations for this account
delegations = await wallet.list_delegation_ids()
assert_equal(len(delegations), 0)

Expand Down
40 changes: 19 additions & 21 deletions test/functional/wallet_nfts.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,27 +119,25 @@ async def async_test(self):
assert_in("Success", await wallet.sync())


# TODO: add support for tokens v1
# See https://github.com/mintlayer/mintlayer-core/issues/1237
#self.log.info(await wallet.get_balance())
#assert_in(f"{nft_id} amount: 1", await wallet.get_balance())

## create a new account and send some tokens to it
#await wallet.create_new_account()
#await wallet.select_account(1)
#address = await wallet.new_address()

#await wallet.select_account(0)
#assert_in(f"{nft_id} amount: 1", await wallet.get_balance())
#output = await wallet.send_tokens_to_address(nft_id, address, 1)
#self.log.info(output)
#assert_in("The transaction was submitted successfully", output)

#self.generate_block()
#assert_in("Success", await wallet.sync())

## check the new balance nft is not present
#assert nft_id not in await wallet.get_balance()
self.log.info(await wallet.get_balance())
assert_in(f"{nft_id} amount: 1", await wallet.get_balance())

# create a new account and send some tokens to it
await wallet.create_new_account()
await wallet.select_account(1)
address = await wallet.new_address()

await wallet.select_account(0)
assert_in(f"{nft_id} amount: 1", await wallet.get_balance())
output = await wallet.send_tokens_to_address(nft_id, address, 1)
self.log.info(output)
assert_in("The transaction was submitted successfully", output)

self.generate_block()
assert_in("Success", await wallet.sync())

# check the new balance nft is not present
assert nft_id not in await wallet.get_balance()

if __name__ == '__main__':
WalletNfts().main()
Expand Down
52 changes: 27 additions & 25 deletions test/functional/wallet_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from test_framework.mintlayer import (make_tx, reward_input, tx_input, ATOMS_PER_COIN)
from test_framework.util import assert_in, assert_equal
from test_framework.mintlayer import mintlayer_hash, block_input_data_obj
from test_framework.wallet_cli_controller import WalletCliController
from test_framework.wallet_cli_controller import DEFAULT_ACCOUNT_INDEX, WalletCliController

import asyncio
import sys
Expand Down Expand Up @@ -90,7 +90,7 @@ async def async_test(self):

# Submit a valid transaction
output = {
'Transfer': [ { 'Coin': 101 * ATOMS_PER_COIN }, { 'PublicKey': {'key': {'Secp256k1Schnorr' : {'pubkey_data': pub_key_bytes}}} } ],
'Transfer': [ { 'Coin': 201 * ATOMS_PER_COIN }, { 'PublicKey': {'key': {'Secp256k1Schnorr' : {'pubkey_data': pub_key_bytes}}} } ],
}
encoded_tx, tx_id = make_tx([reward_input(tip_id)], [output], 0)

Expand All @@ -107,68 +107,70 @@ async def async_test(self):
assert_equal(await wallet.get_best_block_height(), '1')
assert_equal(await wallet.get_best_block(), block_id)

assert_in("Coins amount: 101", await wallet.get_balance())
assert_in("Coins amount: 201", await wallet.get_balance())

address = await wallet.new_address()

# invalid ticker
# > max len
token_id, err = await wallet.issue_new_token("asdddd", "10000", 2, "http://uri", address)
token_id, err = await wallet.issue_new_token("asdddd", 2, "http://uri", address)
assert token_id is None
assert err is not None
assert_in("Invalid ticker length", err)
# non alphanumeric
token_id, err = await wallet.issue_new_token("asd#", "10000", 2, "http://uri", address)
token_id, err = await wallet.issue_new_token("asd#", 2, "http://uri", address)
assert token_id is None
assert err is not None
assert_in("Invalid character in token ticker", err)

# invalid url
token_id, err = await wallet.issue_new_token("XXX", "10000", 2, "123 123", address)
token_id, err = await wallet.issue_new_token("XXX", 2, "123 123", address)
assert token_id is None
assert err is not None
assert_in("Incorrect metadata URI", err)

# invalid num decimals
token_id, err = await wallet.issue_new_token("XXX", "10000", 99, "http://uri", address)
token_id, err = await wallet.issue_new_token("XXX", 99, "http://uri", address)
assert token_id is None
assert err is not None
assert_in("Too many decimals", err)

# issue a valid token
token_id, err = await wallet.issue_new_token("XXX", "10000", 2, "http://uri", address)
token_id, err = await wallet.issue_new_token("XXX", 2, "http://uri", address)
assert token_id is not None
assert err is None
self.log.info(f"new token id: {token_id}")

self.generate_block()
assert_in("Success", await wallet.sync())

# TODO: add support for tokens v1
# Hint with tokens v1 they have to be minted before any could be sent
# See https://github.com/mintlayer/mintlayer-core/issues/1237
#assert_in(f"{token_id} amount: 10000", await wallet.get_balance())
assert_in("The transaction was submitted successfully", await wallet.mint_tokens(token_id, address, 10000))

self.generate_block()
assert_in("Success", await wallet.sync())

assert_in(f"{token_id} amount: 10000", await wallet.get_balance())

## create a new account and send some tokens to it
#await wallet.create_new_account()
#await wallet.select_account(1)
#address = await wallet.new_address()
await wallet.create_new_account()
await wallet.select_account(1)
address = await wallet.new_address()

#await wallet.select_account(0)
#output = await wallet.send_tokens_to_address(token_id, address, 10.01)
#assert_in("The transaction was submitted successfully", output)
await wallet.select_account(DEFAULT_ACCOUNT_INDEX)
output = await wallet.send_tokens_to_address(token_id, address, 10.01)
assert_in("The transaction was submitted successfully", output)

#self.generate_block()
#assert_in("Success", await wallet.sync())
self.generate_block()
assert_in("Success", await wallet.sync())

## check the new balance
#assert_in(f"{token_id} amount: 9989.99", await wallet.get_balance())
assert_in(f"{token_id} amount: 9989.99", await wallet.get_balance())

## try to issue a new token, should fail with not enough coins
#token_id, err = await wallet.issue_new_token("XXX", "10000", 2, "http://uri", address)
#assert token_id is None
#assert err is not None
#assert_in("Not enough funds", err)
token_id, err = await wallet.issue_new_token("XXX", 2, "http://uri", address)
assert token_id is None
assert err is not None
assert_in("Not enough funds", err)

if __name__ == '__main__':
WalletTokens().main()
Expand Down
Loading