Skip to content

Commit

Permalink
Fix amount checks, utils and constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
azarovh committed Sep 14, 2023
1 parent eddad34 commit bc9a64f
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@
use std::collections::BTreeMap;

use common::{
chain::{timelock::OutputTimeLock, AccountSpending, ChainConfig, PoolId, TxInput, TxOutput},
chain::{
output_value::OutputValue,
timelock::OutputTimeLock,
tokens::{TokenData, TokenId},
AccountSpending, ChainConfig, PoolId, TxInput, TxOutput,
},
primitives::{Amount, BlockDistance, BlockHeight},
};
use utils::ensure;
Expand All @@ -33,13 +38,15 @@ use super::IOPolicyError;
pub struct ConstrainedValueAccumulator {
unconstrained_value: Amount,
timelock_constrained: BTreeMap<BlockDistance, Amount>,
burn_constrained: BTreeMap<TokenId, Amount>,
}

impl ConstrainedValueAccumulator {
pub fn new() -> Self {
Self {
unconstrained_value: Amount::ZERO,
timelock_constrained: Default::default(),
burn_constrained: Default::default(),
}
}

Expand All @@ -49,6 +56,7 @@ impl ConstrainedValueAccumulator {
pub fn consume(self) -> Result<Amount, IOPolicyError> {
self.timelock_constrained
.into_values()
.chain(self.burn_constrained.into_values())
.sum::<Option<Amount>>()
.and_then(|v| v + self.unconstrained_value)
.ok_or(IOPolicyError::AmountOverflow)
Expand Down Expand Up @@ -120,14 +128,16 @@ impl ConstrainedValueAccumulator {
*balance =
(*balance + *spend_amount).ok_or(IOPolicyError::AmountOverflow)?;
}
AccountSpending::TokenUnrealizedSupply(_token_id, _amount) => {
// FIXME: unconstrained_value should support tokens
// use CoinOrTokenId::TokenId to insert into unconstrained_value
AccountSpending::TokenCirculatingSupply(token_id, amount_to_burn) => {
let amount =
self.burn_constrained.entry(*token_id).or_insert(Amount::ZERO);
*amount =
(*amount + *amount_to_burn).ok_or(IOPolicyError::AmountOverflow)?;
}
AccountSpending::TokenCirculatingSupply(_, _) => {
todo!("Add burn requirement")
AccountSpending::TokenUnrealizedSupply(_, _) => {
// TODO: do nothing for now but should be used to check transferred amount later
}
AccountSpending::TokenSupplyLock(_) => todo!(),
AccountSpending::TokenSupplyLock(_) => { /* do nothing*/ }
};
}
}
Expand All @@ -138,13 +148,33 @@ impl ConstrainedValueAccumulator {
pub fn process_outputs(&mut self, outputs: &[TxOutput]) -> Result<(), IOPolicyError> {
for output in outputs {
match output {
TxOutput::Transfer(value, _) | TxOutput::Burn(value) => {
TxOutput::Transfer(value, _) => {
if let Some(coins) = value.coin_amount() {
self.unconstrained_value = (self.unconstrained_value - coins).ok_or(
IOPolicyError::AttemptToPrintMoneyOrViolateTimelockConstraints,
)?;
}
}
TxOutput::Burn(value) => match value {
OutputValue::Coin(coins) => {
self.unconstrained_value = (self.unconstrained_value - *coins).ok_or(
IOPolicyError::AttemptToPrintMoneyOrViolateTimelockConstraints,
)?;
}
OutputValue::Token(token_data) => match token_data.as_ref() {
TokenData::TokenTransfer(data) => {
let constrained_amount = self
.burn_constrained
.get_mut(&data.token_id)
.ok_or(IOPolicyError::AmountOverflow)?;
*constrained_amount = (*constrained_amount - data.amount)
.ok_or(IOPolicyError::AmountOverflow)?;
}
TokenData::TokenIssuance(_)
| TokenData::NftIssuance(_)
| TokenData::TokenIssuanceV1(_) => todo!("Error??"),

Check failure on line 175 in chainstate/tx-verifier/src/transaction_verifier/input_output_policy/constraints_accumulator.rs

View workflow job for this annotation

GitHub Actions / clippy

`todo` should not be present in production code

error: `todo` should not be present in production code --> chainstate/tx-verifier/src/transaction_verifier/input_output_policy/constraints_accumulator.rs:175:60 | 175 | | TokenData::TokenIssuanceV1(_) => todo!("Error??"), | ^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#todo = note: `-D clippy::todo` implied by `-D warnings`
},
},
TxOutput::DelegateStaking(coins, _) => {
self.unconstrained_value = (self.unconstrained_value - *coins)
.ok_or(IOPolicyError::AttemptToPrintMoneyOrViolateTimelockConstraints)?;
Expand Down
63 changes: 26 additions & 37 deletions chainstate/tx-verifier/src/transaction_verifier/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ use chainstate_types::BlockIndex;
use common::{
chain::{
block::{timestamp::BlockTimestamp, BlockRewardTransactable, ConsensusData},
output_value::OutputValue,
signature::Signable,
signed_transaction::SignedTransaction,
tokens::{
get_tokens_issuance_count, get_tokens_reissuance_count, token_id, TokenData, TokenId,
get_token_supply_change_count, get_tokens_issuance_count, get_tokens_issuance_v1,
token_id, TokenId,
},
AccountNonce, AccountOutPoint, AccountSpending, AccountType, Block, ChainConfig,
DelegationId, GenBlock, OutPointSourceId, PoolId, Transaction, TxInput, TxMainChainIndex,
Expand Down Expand Up @@ -268,8 +268,8 @@ where
}

// Check if the fee is enough for reissuance
let reissuance_count = get_tokens_reissuance_count(tx.inputs());
if reissuance_count > 0 {
let supply_change_count = get_token_supply_change_count(tx.inputs());
if supply_change_count > 0 {
let total_burned = tx
.outputs()
.iter()
Expand All @@ -285,8 +285,8 @@ where
.sum::<Option<Amount>>()
.ok_or_else(|| ConnectTransactionError::BurnAmountSumError(tx.get_id()))?;

let required_fee = (self.chain_config.as_ref().token_min_reissuance_fee()
* reissuance_count as u128)
let required_fee = (self.chain_config.as_ref().token_min_supply_change_fee()
* supply_change_count as u128)
.expect("overflow");
ensure!(
total_burned >= required_fee,
Expand Down Expand Up @@ -728,39 +728,27 @@ where
})
.collect::<Result<Vec<_>, _>>()?;

let outputs_undos =
tx.outputs()
.iter()
.filter_map(|output| match output {
TxOutput::Transfer(v, _) | TxOutput::LockThenTransfer(v, _, _) => match v {
OutputValue::Coin(_) => None,
OutputValue::Token(token_data) => match token_data.as_ref() {
TokenData::TokenTransfer(_)
| TokenData::TokenIssuance(_)
| TokenData::NftIssuance(_) => None,
TokenData::TokenIssuanceV1(issuance_data) => {
let res: Result<tokens_accounting::TokenAccountingUndo, _> =
token_id(tx).ok_or(ConnectTransactionError::TokensError(TokensError::TokenIdCantBeCalculated)).and_then(
|token_id| -> Result<tokens_accounting::TokenAccountingUndo, _>{
let data = tokens_accounting::TokenData::FungibleToken(
issuance_data.as_ref().clone().into(),
);
self.tokens_accounting_cache.issue_token(token_id, data).map_err(ConnectTransactionError::TokensAccountingError)
},
);
Some(res)
}
let outputs_undos = get_tokens_issuance_v1(tx.outputs())

Check failure on line 731 in chainstate/tx-verifier/src/transaction_verifier/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

unused variable: `outputs_undos`

error: unused variable: `outputs_undos` --> chainstate/tx-verifier/src/transaction_verifier/mod.rs:731:13 | 731 | let outputs_undos = get_tokens_issuance_v1(tx.outputs()) | ^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_outputs_undos`

Check warning on line 731 in chainstate/tx-verifier/src/transaction_verifier/mod.rs

View workflow job for this annotation

GitHub Actions / coverage

unused variable: `outputs_undos`
.iter()
.map(|issuance_data| {
token_id(tx)
.ok_or(ConnectTransactionError::TokensError(
TokensError::TokenIdCantBeCalculated,
))
.and_then(
|token_id| -> Result<tokens_accounting::TokenAccountingUndo, _> {
let data = tokens_accounting::TokenData::FungibleToken(
(*issuance_data).clone().into(),
);
self.tokens_accounting_cache
.issue_token(token_id, data)
.map_err(ConnectTransactionError::TokensAccountingError)
},
},
TxOutput::Burn(_)
| TxOutput::CreateStakePool(_, _)
| TxOutput::ProduceBlockFromStake(_, _)
| TxOutput::CreateDelegationId(_, _)
| TxOutput::DelegateStaking(_, _) => None,
})
.collect::<Result<Vec<_>, _>>()?;
)
})
.collect::<Result<Vec<_>, _>>()?;

// FIXME: save undos
// FIXME: save undos to cache
Ok(())
}

Expand All @@ -773,6 +761,7 @@ where
) -> Result<Fee, ConnectTransactionError> {
let block_id = tx_source.chain_block_index().map(|c| *c.block_id());

// FIXME: forbid v0 tokens at some height?
// pre-cache token ids to check ensure it's not in the db when issuing
self.token_issuance_cache.precache_token_issuance(
|id| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use std::collections::{btree_map::Entry, BTreeMap};

use common::{
chain::{
tokens::{get_tokens_issuance_count, token_id, TokenAuxiliaryData, TokenId},
tokens::{get_tokens_issuance_v0_count, token_id, TokenAuxiliaryData, TokenId},
Block, Transaction,
},
primitives::{Id, Idable, H256},
Expand Down Expand Up @@ -74,7 +74,7 @@ impl TokenIssuanceCache {
block_id: Option<Id<Block>>,
tx: &Transaction,
) -> Result<(), TokensError> {
let was_token_issued = get_tokens_issuance_count(tx.outputs()) > 0;
let was_token_issued = get_tokens_issuance_v0_count(tx.outputs()) > 0;

if was_token_issued {
self.write_issuance(&block_id.unwrap_or_else(|| H256::zero().into()), tx)?;
Expand All @@ -83,7 +83,7 @@ impl TokenIssuanceCache {
}

pub fn unregister(&mut self, tx: &Transaction) -> Result<(), TokensError> {
let was_token_issued = get_tokens_issuance_count(tx.outputs()) > 0;
let was_token_issued = get_tokens_issuance_v0_count(tx.outputs()) > 0;

if was_token_issued {
self.write_undo_issuance(tx)?;
Expand Down Expand Up @@ -151,7 +151,7 @@ impl TokenIssuanceCache {
where
ConnectTransactionError: From<E>,
{
let has_token_issuance = get_tokens_issuance_count(tx.outputs()) > 0;
let has_token_issuance = get_tokens_issuance_v0_count(tx.outputs()) > 0;

if has_token_issuance {
let token_id = token_id(tx).ok_or(TokensError::TokenIdCantBeCalculated)?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,13 @@ where
);
Ok((CoinOrTokenId::Coin, *withdraw_amount))
}
AccountSpending::TokenUnrealizedSupply(token_id, amount) => {
// FIXME: check if supply can be increased
AccountSpending::TokenUnrealizedSupply(token_id, amount)
| AccountSpending::TokenCirculatingSupply(token_id, amount) => {
Ok((CoinOrTokenId::TokenId(*token_id), *amount))
}
AccountSpending::TokenCirculatingSupply(_, _) => todo!(),
AccountSpending::TokenSupplyLock(_) => todo!(),
AccountSpending::TokenSupplyLock(token_id) => {
Ok((CoinOrTokenId::TokenId(*token_id), Amount::ZERO))
}
},
});

Expand Down
8 changes: 4 additions & 4 deletions common/src/chain/config/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ pub struct Builder {
genesis_block: GenesisBlockInit,
emission_schedule: EmissionScheduleInit,
token_min_issuance_fee: Amount,
token_min_reissuance_fee: Amount,
token_min_supply_change_fee: Amount,
token_max_uri_len: usize,
token_max_dec_count: u8,
token_max_ticker_len: usize,
Expand Down Expand Up @@ -173,7 +173,7 @@ impl Builder {
emission_schedule: EmissionScheduleInit::Mainnet,
net_upgrades: chain_type.default_net_upgrades(),
token_min_issuance_fee: super::TOKEN_MIN_ISSUANCE_FEE,
token_min_reissuance_fee: super::TOKEN_MIN_REISSUANCE_FEE,
token_min_supply_change_fee: super::TOKEN_MIN_SUPPLY_CHANGE_FEE,
token_max_uri_len: super::TOKEN_MAX_URI_LEN,
token_max_dec_count: super::TOKEN_MAX_DEC_COUNT,
token_max_ticker_len: super::TOKEN_MAX_TICKER_LEN,
Expand Down Expand Up @@ -218,7 +218,7 @@ impl Builder {
emission_schedule,
net_upgrades,
token_min_issuance_fee,
token_min_reissuance_fee,
token_min_supply_change_fee,
token_max_uri_len,
token_max_dec_count,
token_max_ticker_len,
Expand Down Expand Up @@ -299,7 +299,7 @@ impl Builder {
emission_schedule,
net_upgrades,
token_min_issuance_fee,
token_min_reissuance_fee,
token_min_supply_change_fee,
token_max_uri_len,
token_max_dec_count,
token_max_ticker_len,
Expand Down
10 changes: 5 additions & 5 deletions common/src/chain/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ pub struct ChainConfig {
sealed_epoch_distance_from_tip: usize,
initial_randomness: H256,
token_min_issuance_fee: Amount,
token_min_reissuance_fee: Amount,
token_min_supply_change_fee: Amount,
token_max_uri_len: usize,
token_max_dec_count: u8,
token_max_ticker_len: usize,
Expand Down Expand Up @@ -397,9 +397,9 @@ impl ChainConfig {
self.token_min_issuance_fee
}

/// The fee for reissuing a token
pub fn token_min_reissuance_fee(&self) -> Amount {
self.token_min_reissuance_fee
/// The fee for changing supply of a token
pub fn token_min_supply_change_fee(&self) -> Amount {
self.token_min_supply_change_fee
}

/// The maximum length of a URI contained in a token
Expand Down Expand Up @@ -510,7 +510,7 @@ const MAX_BLOCK_TXS_SIZE: usize = 1_048_576;
const MAX_BLOCK_CONTRACTS_SIZE: usize = 1_048_576;
const MAX_TX_NO_SIG_WITNESS_SIZE: usize = 128;
const TOKEN_MIN_ISSUANCE_FEE: Amount = Mlt::from_mlt(100).to_amount_atoms();
const TOKEN_MIN_REISSUANCE_FEE: Amount = Mlt::from_mlt(100).to_amount_atoms();
const TOKEN_MIN_SUPPLY_CHANGE_FEE: Amount = Mlt::from_mlt(100).to_amount_atoms();
const TOKEN_MAX_DEC_COUNT: u8 = 18;
const TOKEN_MAX_TICKER_LEN: usize = 5;
const TOKEN_MIN_HASH_LEN: usize = 4;
Expand Down
24 changes: 22 additions & 2 deletions common/src/chain/tokens/tokens_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,28 @@ pub fn get_tokens_issuance_count(outputs: &[TxOutput]) -> usize {
outputs.iter().filter(|&output| is_token_or_nft_issuance(output)).count()
}

// FIXME: reissuance is not the common name for mint and burn
pub fn get_tokens_reissuance_count(inputs: &[TxInput]) -> usize {
pub fn get_tokens_issuance_v0_count(outputs: &[TxOutput]) -> usize {
outputs
.iter()
.filter(|&output| match output {
TxOutput::Transfer(v, _) | TxOutput::LockThenTransfer(v, _, _) | TxOutput::Burn(v) => {
match v {
OutputValue::Token(data) => match data.as_ref() {
TokenData::TokenIssuance(_) | TokenData::NftIssuance(_) => true,
TokenData::TokenTransfer(_) | TokenData::TokenIssuanceV1(_) => false,
},
OutputValue::Coin(_) => false,
}
}
TxOutput::CreateStakePool(_, _)
| TxOutput::ProduceBlockFromStake(_, _)
| TxOutput::CreateDelegationId(_, _)
| TxOutput::DelegateStaking(_, _) => false,
})
.count()
}

pub fn get_token_supply_change_count(inputs: &[TxInput]) -> usize {
inputs
.iter()
.filter(|&input| match input {
Expand Down
4 changes: 2 additions & 2 deletions tokens-accounting/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl<P: TokensAccountingView> TokensAccountingView for TokensAccountingCache<P>
type Error = Error;

fn get_token_data(&self, id: &TokenId) -> Result<Option<TokenData>, Self::Error> {
match self.data.token_data.get_data(&id) {
match self.data.token_data.get_data(id) {
accounting::GetDataResult::Present(d) => Ok(Some(d.clone())),
accounting::GetDataResult::Deleted => Ok(None),
accounting::GetDataResult::Missing => {
Expand All @@ -67,7 +67,7 @@ impl<P: TokensAccountingView> TokensAccountingView for TokensAccountingCache<P>

fn get_circulating_supply(&self, id: &TokenId) -> Result<Option<Amount>, Self::Error> {
let parent_supply = self.parent.get_circulating_supply(id).map_err(|_| Error::ViewFail)?;
let local_delta = self.data.circulating_supply.data().get(&id).cloned();
let local_delta = self.data.circulating_supply.data().get(id).cloned();
combine_amount_delta(&parent_supply, &local_delta).map_err(Error::AccountingError)
}
}
Expand Down
1 change: 1 addition & 0 deletions wallet/src/account/output_cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ impl OutputCache {
(
output,
token_id.and_then(|token_id| {
// FIXME: is this correct for v1?
is_token_or_nft_issuance(output).then_some(token_id)
}),
),
Expand Down

0 comments on commit bc9a64f

Please sign in to comment.