From c4187cc46302d885b276bd7bca65d5b68699769f Mon Sep 17 00:00:00 2001 From: Mihailo Milenkovic Date: Fri, 12 Jul 2024 13:36:26 +0200 Subject: [PATCH] Add decimal denomination metadata to assets --- src/issuance.cpp | 1 + src/primitives/confidential.cpp | 1 + src/primitives/confidential.h | 5 +++- src/rpc/client.cpp | 1 + src/rpc/rawtransaction.cpp | 13 ++++++++-- src/wallet/rpc/elements.cpp | 24 ++++++++++++------- src/wallet/spend.cpp | 4 +++- src/wallet/wallet.h | 3 +++ .../example_elements_code_tutorial.py | 4 ++-- test/functional/feature_txwitness.py | 5 ++-- test/functional/test_framework/messages.py | 5 +++- 11 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/issuance.cpp b/src/issuance.cpp index 5ecb31c883..2f255ecbb1 100644 --- a/src/issuance.cpp +++ b/src/issuance.cpp @@ -85,6 +85,7 @@ void AppendInitialIssuance(CBlock& genesis_block, const COutPoint& prevout, cons txNew.vin[0].assetIssuance.assetEntropy = contract; txNew.vin[0].assetIssuance.nAmount = CConfidentialValue(asset_values * asset_outputs); txNew.vin[0].assetIssuance.nInflationKeys = CConfidentialValue(reissuance_values * reissuance_outputs); + txNew.vin[0].assetIssuance.denomination = 8; for (unsigned int i = 0; i < asset_outputs; i++) { txNew.vout.push_back(CTxOut(asset, CConfidentialValue(asset_values), issuance_destination)); diff --git a/src/primitives/confidential.cpp b/src/primitives/confidential.cpp index fd1a9eca0d..5bce1746c5 100644 --- a/src/primitives/confidential.cpp +++ b/src/primitives/confidential.cpp @@ -29,6 +29,7 @@ std::string CAssetIssuance::ToString() const str += strprintf(", amount=%s", (nAmount.IsExplicit() ? strprintf("%d.%08d", nAmount.GetAmount() / COIN, nAmount.GetAmount() % COIN) : std::string("CONFIDENTIAL"))); if (!nInflationKeys.IsNull()) str += strprintf(", inflationkeys=%s", (nInflationKeys.IsExplicit() ? strprintf("%d.%08d", nInflationKeys.GetAmount() / COIN, nInflationKeys.GetAmount() % COIN) : std::string("CONFIDENTIAL"))); + str += std::to_string(denomination); str += ")"; return str; } diff --git a/src/primitives/confidential.h b/src/primitives/confidential.h index a85703cca0..2da6256b15 100644 --- a/src/primitives/confidential.h +++ b/src/primitives/confidential.h @@ -192,13 +192,15 @@ class CAssetIssuance // generating transaction. CConfidentialValue nInflationKeys; + uint8_t denomination = 8; + public: CAssetIssuance() { SetNull(); } - SERIALIZE_METHODS(CAssetIssuance, obj) { READWRITE(obj.assetBlindingNonce, obj.assetEntropy, obj.nAmount, obj.nInflationKeys); } + SERIALIZE_METHODS(CAssetIssuance, obj) { READWRITE(obj.assetBlindingNonce, obj.assetEntropy, obj.nAmount, obj.nInflationKeys, obj.denomination); } void SetNull() { nAmount.SetNull(); nInflationKeys.SetNull(); } bool IsNull() const { return (nAmount.IsNull() && nInflationKeys.IsNull()); } @@ -209,6 +211,7 @@ class CAssetIssuance a.assetEntropy == b.assetEntropy && a.nAmount == b.nAmount && a.nInflationKeys == b.nInflationKeys; + a.denomination == b.denomination; } friend bool operator!=(const CAssetIssuance& a, const CAssetIssuance& b) diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 8b09f0d3fe..ee6eef2d4c 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -225,6 +225,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "issueasset", 0, "assetamount" }, { "issueasset", 1, "tokenamount" }, { "issueasset", 2, "blind" }, + { "issueasset", 5, "denomination" }, { "reissueasset", 1, "assetamount" }, { "initpegoutwallet", 1, "bip32_counter"}, { "rawblindrawtransaction", 1, "inputamountblinders" }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index f9250baf01..9c8fd9b6ac 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -2886,12 +2886,13 @@ struct RawIssuanceDetails uint256 entropy; CAsset asset; CAsset token; + uint8_t denomination = 8; }; // Appends a single issuance to the first input that doesn't have one, and includes // a single output per asset type in shuffled positions. Requires at least one output // to exist (the fee output, which must be last). -void issueasset_base(CMutableTransaction& mtx, RawIssuanceDetails& issuance_details, const CAmount asset_amount, const CAmount token_amount, const CTxDestination& asset_dest, const CTxDestination& token_dest, const bool blind_issuance, const uint256& contract_hash) +void issueasset_base(CMutableTransaction& mtx, RawIssuanceDetails& issuance_details, const CAmount asset_amount, const CAmount token_amount, const CTxDestination& asset_dest, const CTxDestination& token_dest, const bool blind_issuance, const uint256& contract_hash, const uint8_t denomination) { CHECK_NONFATAL(asset_amount > 0 || token_amount > 0); CHECK_NONFATAL(mtx.vout.size() > 0); @@ -2923,6 +2924,8 @@ void issueasset_base(CMutableTransaction& mtx, RawIssuanceDetails& issuance_deta issuance_details.entropy = entropy; issuance_details.asset = asset; issuance_details.token = token; + if (denomination) + issuance_details.denomination = denomination; mtx.vin[issuance_input_index].assetIssuance.assetEntropy = contract_hash; @@ -3007,6 +3010,7 @@ static RPCHelpMan rawissueasset() {"token_address", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Destination address of generated reissuance tokens. Required if `token_amount` given."}, {"blind", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to mark the issuance input for blinding or not. Only affects issuances with re-issuance tokens."}, {"contract_hash", RPCArg::Type::STR_HEX, RPCArg::Default{"0000...0000"}, "Contract hash that is put into issuance definition. Must be 32 bytes worth in hex string form. This will affect the asset id."}, + {"denomination", RPCArg::Type::NUM, RPCArg::Default{8}, "Number of decimals to denominate the asset - default: 8\n"}, } } } @@ -3108,9 +3112,14 @@ static RPCHelpMan rawissueasset() contract_hash = ParseHashV(issuance_o["contract_hash"], "contract_hash"); } + uint8_t denomination = 0; + if (!issuance_o["denomination"].isNull()) { + denomination = issuance_o["denomination"].get_int(); + } + RawIssuanceDetails details; - issueasset_base(mtx, details, asset_amount, token_amount, asset_dest, token_dest, blind_issuance, contract_hash); + issueasset_base(mtx, details, asset_amount, token_amount, asset_dest, token_dest, blind_issuance, contract_hash, denomination); if (details.input_index == -1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Failed to find enough blank inputs for listed issuances."); } diff --git a/src/wallet/rpc/elements.cpp b/src/wallet/rpc/elements.cpp index 1bd50aaa3d..cdc1a72542 100644 --- a/src/wallet/rpc/elements.cpp +++ b/src/wallet/rpc/elements.cpp @@ -1398,7 +1398,8 @@ RPCHelpMan issueasset() {"tokenamount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "Amount of reissuance tokens to generate. Note that the amount is BTC-like, with 8 decimal places. These will allow you to reissue the asset if in wallet using `reissueasset`. These tokens are not consumed during reissuance."}, {"blind", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to blind the issuances."}, {"contract_hash", RPCArg::Type::STR_HEX, RPCArg::Default{"0000...0000"}, "Contract hash that is put into issuance definition. Must be 32 bytes worth in hex string form. This will affect the asset id."}, - {"fee_asset", RPCArg::Type::STR, RPCArg::DefaultHint{"not set, fall back to fee asset in existing transaction"}, "Asset to use to pay fees\n"}, + {"fee_asset", RPCArg::Type::STR, RPCArg::DefaultHint{"not set, fall back to fee asset in existing transaction"}, "Asset to use to pay the fees"}, + {"denomination", RPCArg::Type::NUM, RPCArg::Default{8}, "Number of decimals to denominate the asset - default: 8\n"}, }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -1469,14 +1470,19 @@ RPCHelpMan issueasset() issuance_details.blind_issuance = blind_issuances; issuance_details.contract_hash = contract_hash; CCoinControl coin_control; - if (g_con_any_asset_fees && request.params.size() > 4) { - CAsset fee_asset = ::policyAsset; - std::string feeAssetString = request.params[4].get_str(); - fee_asset = GetAssetFromString(feeAssetString); - if (fee_asset.IsNull()) { - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Unknown label and invalid asset hex for fee: %s", feeAssetString)); + if (g_con_any_asset_fees) { + if (request.params.size() >= 5) { + CAsset fee_asset = ::policyAsset; + std::string feeAssetString = request.params[4].get_str(); + fee_asset = GetAssetFromString(feeAssetString); + if (fee_asset.IsNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Unknown label and invalid asset hex for fee: %s", feeAssetString)); + } + coin_control.m_fee_asset = fee_asset; + } + if (request.params.size() >= 6) { + issuance_details.denomination = request.params[5].get_int(); } - coin_control.m_fee_asset = fee_asset; } CTransactionRef tx_ref = SendGenerationTransaction(GetScriptForDestination(asset_dest), asset_dest_blindpub, GetScriptForDestination(token_dest), token_dest_blindpub, nAmount, nTokens, &issuance_details, coin_control, pwallet); @@ -1624,6 +1630,7 @@ RPCHelpMan listissuances() {RPCResult::Type::STR_HEX, "token", "Token type for issuancen"}, {RPCResult::Type::NUM, "vin", "The input position of the issuance in the transaction"}, {RPCResult::Type::STR_AMOUNT, "assetamount", "The amount of asset issued. Is -1 if blinded and unknown to wallet"}, + {RPCResult::Type::NUM, "denomination", "Asset decimal denomination"}, {RPCResult::Type::STR_AMOUNT, "tokenamount", "The reissuance token amount issued. Is -1 if blinded and unknown to wallet"}, {RPCResult::Type::BOOL, "isreissuance", "Whether this is a reissuance"}, {RPCResult::Type::STR_HEX, "assetblinds", "Blinding factor for asset amounts"}, @@ -1687,6 +1694,7 @@ RPCHelpMan listissuances() } CAmount iaamount = pcoin->GetIssuanceAmount(*pwallet, vinIndex, false); item.pushKV("assetamount", (iaamount == -1 ) ? -1 : ValueFromAmount(iaamount)); + item.pushKV("denomination", issuance.denomination); item.pushKV("assetblinds", pcoin->GetIssuanceBlindingFactor(*pwallet, vinIndex, false).GetHex()); if (!asset_filter.IsNull() && asset_filter != asset) { continue; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp index 5b6990926b..4ad9b73ed5 100644 --- a/src/wallet/spend.cpp +++ b/src/wallet/spend.cpp @@ -1410,6 +1410,8 @@ static bool CreateTransactionInternal( } } } + // SEQUENTIA: Add denomination in the asset issuance + txNew.vin[0].assetIssuance.denomination = issuance_details->denomination; // Asset being reissued with explicitly named asset/token } else if (asset_index != -1) { assert(reissuance_index != -1); @@ -1693,7 +1695,7 @@ static bool CreateTransactionInternal( } if (g_con_any_asset_fees) { - CAmount nFeeRetValue = ExchangeRateMap::GetInstance().ConvertAmountToValue(nFeeRet, coin_selection_params.m_fee_asset).GetValue(); + CAmount nFeeRetValue = ExchangeRateMap::GetInstance().ConvertAmountToValue(nFeeRet, coin_selection_params.m_fee_asset).GetValue(); if (nFeeRetValue > wallet.m_default_max_tx_fee) { error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); return false; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 330fd17d7e..6be5fa86b0 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -154,6 +154,9 @@ struct IssuanceDetails { CAsset reissuance_asset; CAsset reissuance_token; uint256 entropy; + + // SEQUENTIA: Asset denomination + uint8_t denomination = 8; }; //end ELEMENTS diff --git a/test/functional/example_elements_code_tutorial.py b/test/functional/example_elements_code_tutorial.py index feb645f1f1..6e24983120 100755 --- a/test/functional/example_elements_code_tutorial.py +++ b/test/functional/example_elements_code_tutorial.py @@ -64,8 +64,8 @@ def run_test(self): expected_amt = { 'bitcoin': 0, - '8f1560e209f6bcac318569a935a0b2513c54f326ee4820ccd5b8c1b1b4632373': 0, - '4fa41f2929d4bf6975a55967d9da5b650b6b9bfddeae4d7b54b04394be328f7f': 99 + '884071e106da92ff53b432340c8d160066502c781f50dfac5afc67459f946d6f': 0, + 'daa8284c0d06cb02ef28b75ffa74c3b512131884d9c72e4f11dac634703d4fc4': 99 } assert self.nodes[0].gettransaction(reissuance_txid)['amount'] == expected_amt diff --git a/test/functional/feature_txwitness.py b/test/functional/feature_txwitness.py index 60c1502868..4bba388165 100755 --- a/test/functional/feature_txwitness.py +++ b/test/functional/feature_txwitness.py @@ -203,9 +203,10 @@ def serialize(self): block_witness_stuffed.vtx[0].vin[0].scriptSig = coinbase_orig.scriptSig block_witness_stuffed.vtx[0].vin[0].nSequence = coinbase_orig.nSequence block_witness_stuffed.vtx[0].vin[0].assetIssuance.nAmount.setToAmount(1) + block_witness_stuffed.vtx[0].vin[0].assetIssuance.denomination = 8 bad_coinbase_ser_size = len(block_witness_stuffed.vtx[0].vin[0].serialize()) - # 32+32+9+1 should be serialized for each assetIssuance field - assert_equal(bad_coinbase_ser_size, coinbase_ser_size+32+32+9+1) + # 32+32+9+1+1 should be serialized for each assetIssuance field + assert_equal(bad_coinbase_ser_size, coinbase_ser_size+32+32+9+1+1) assert not block_witness_stuffed.vtx[0].vin[0].assetIssuance.isNull() assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, block_witness_stuffed.vtx[0].serialize().hex()) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index db440c04d5..6a4a77b98a 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -391,13 +391,14 @@ def __repr__(self): OUTPOINT_INDEX_MASK = 0x3fffffff class CAssetIssuance(): - __slots__ = ("assetBlindingNonce", "assetEntropy", "nAmount", "nInflationKeys") + __slots__ = ("assetBlindingNonce", "assetEntropy", "nAmount", "nInflationKeys", "denomination") def __init__(self): self.assetBlindingNonce = 0 self.assetEntropy = 0 self.nAmount = CTxOutValue() self.nInflationKeys = CTxOutValue() + self.denomination = 8 def isNull(self): return self.nAmount.isNull() and self.nInflationKeys.isNull() @@ -409,6 +410,7 @@ def deserialize(self, f): self.nAmount.deserialize(f) self.nInflationKeys = CTxOutValue() self.nInflationKeys.deserialize(f) + self.denomination = deser_compact_size(f) def serialize(self): r = b"" @@ -416,6 +418,7 @@ def serialize(self): r += ser_uint256(self.assetEntropy) r += self.nAmount.serialize() r += self.nInflationKeys.serialize() + r += ser_compact_size(self.denomination) return r # serialization of asset issuance used in taproot sighash