Browse Source

drk: fixed token minting and added its fee call

skoupidi 2 years ago
parent
commit
9e71055a9d

+ 11 - 12
bin/drk/src/main.rs

@@ -1627,7 +1627,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     exit(2);
                 }
 
-                let _rcpt = match PublicKey::from_str(&recipient) {
+                let rcpt = match PublicKey::from_str(&recipient) {
                     Ok(r) => r,
                     Err(e) => {
                         eprintln!("Invalid recipient: {e:?}");
@@ -1635,7 +1635,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     }
                 };
 
-                let _token_id = match drk.get_token(token).await {
+                let token_id = match drk.get_token(token).await {
                     Ok(t) => t,
                     Err(e) => {
                         eprintln!("Invalid Token ID: {e:?}");
@@ -1643,18 +1643,17 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     }
                 };
 
-                panic!("temporarily disabled due to change of API for drk.mint_token() fn");
-                //let tx = match drk.mint_token(&amount, rcpt, token_id).await {
-                //    Ok(tx) => tx,
-                //    Err(e) => {
-                //        eprintln!("Failed to create token mint transaction: {e:?}");
-                //        exit(2);
-                //    }
-                //};
+                let tx = match drk.mint_token(&amount, rcpt, token_id, None, None).await {
+                    Ok(tx) => tx,
+                    Err(e) => {
+                        eprintln!("Failed to create token mint transaction: {e:?}");
+                        exit(2);
+                    }
+                };
 
-                //println!("{}", base64::encode(&serialize_async(&tx).await));
+                println!("{}", base64::encode(&serialize_async(&tx).await));
 
-                //Ok(())
+                Ok(())
             }
 
             TokenSubcmd::Freeze { token } => {

+ 34 - 21
bin/drk/src/money.rs

@@ -35,9 +35,9 @@ use darkfi_money_contract::{
         MoneyNote, OwnCoin,
     },
     model::{
-        Coin, Input, MoneyFeeParamsV1, MoneyGenesisMintParamsV1, MoneyPoWRewardParamsV1,
-        MoneyTokenFreezeParamsV1, MoneyTokenMintParamsV1, MoneyTransferParamsV1, Nullifier, Output,
-        TokenId, DARK_TOKEN_ID,
+        Coin, Input, MoneyAuthTokenMintParamsV1, MoneyFeeParamsV1, MoneyGenesisMintParamsV1,
+        MoneyPoWRewardParamsV1, MoneyTokenFreezeParamsV1, MoneyTokenMintParamsV1,
+        MoneyTransferParamsV1, Nullifier, Output, TokenId, DARK_TOKEN_ID,
     },
     MoneyFunction,
 };
@@ -47,6 +47,7 @@ use darkfi_sdk::{
         note::AeadEncryptedNote, BaseBlind, FuncId, Keypair, MerkleNode, MerkleTree, PublicKey,
         ScalarBlind, SecretKey, MONEY_CONTRACT_ID,
     },
+    dark_tree::DarkLeaf,
     pasta::pallas,
     ContractCall,
 };
@@ -426,7 +427,7 @@ impl Drk {
         let query = self.wallet.query_multiple(
             &MONEY_COINS_TABLE,
             &[],
-            convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_TOKENS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await)},
+            convert_named_params! {(MONEY_COINS_COL_IS_SPENT, false), (MONEY_COINS_COL_TOKEN_ID, serialize_async(token_id).await), (MONEY_COINS_COL_SPEND_HOOK, serialize_async(&FuncId::none()).await)},
         )
         .await;
 
@@ -687,32 +688,35 @@ impl Drk {
     }
 
     /// Auxiliary function to  grab all the nullifiers, coins, notes and freezes from
-    /// transaction data.
-    async fn parse_call_data(
+    /// transaction money call.
+    async fn parse_money_call(
         &self,
-        data: &[u8],
+        call_idx: usize,
+        calls: &[DarkLeaf<ContractCall>],
     ) -> Result<(Vec<Nullifier>, Vec<Coin>, Vec<AeadEncryptedNote>, Vec<TokenId>)> {
         let mut nullifiers: Vec<Nullifier> = vec![];
         let mut coins: Vec<Coin> = vec![];
         let mut notes: Vec<AeadEncryptedNote> = vec![];
         let mut freezes: Vec<TokenId> = vec![];
 
+        let call = &calls[call_idx];
+        let data = &call.data.data;
         match MoneyFunction::try_from(data[0])? {
             MoneyFunction::FeeV1 => {
-                println!("[apply_tx_money_data] Found Money::FeeV1 call");
+                println!("[parse_money_call] Found Money::FeeV1 call");
                 let params: MoneyFeeParamsV1 = deserialize_async(&data[9..]).await?;
                 nullifiers.push(params.input.nullifier);
                 coins.push(params.output.coin);
                 notes.push(params.output.note);
             }
             MoneyFunction::GenesisMintV1 => {
-                println!("[apply_tx_money_data] Found Money::GenesisMintV1 call");
+                println!("[parse_money_call] Found Money::GenesisMintV1 call");
                 let params: MoneyGenesisMintParamsV1 = deserialize_async(&data[1..]).await?;
                 coins.push(params.output.coin);
                 notes.push(params.output.note);
             }
             MoneyFunction::TransferV1 => {
-                println!("[apply_tx_money_data] Found Money::TransferV1 call");
+                println!("[parse_money_call] Found Money::TransferV1 call");
                 let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
 
                 for input in params.inputs {
@@ -725,7 +729,7 @@ impl Drk {
                 }
             }
             MoneyFunction::OtcSwapV1 => {
-                println!("[apply_tx_money_data] Found Money::OtcSwapV1 call");
+                println!("[parse_money_call] Found Money::OtcSwapV1 call");
                 let params: MoneyTransferParamsV1 = deserialize_async(&data[1..]).await?;
 
                 for input in params.inputs {
@@ -738,27 +742,31 @@ impl Drk {
                 }
             }
             MoneyFunction::TokenMintV1 => {
-                println!("[apply_tx_money_data] Found Money::TokenMintV1 call");
+                println!("[parse_money_call] Found Money::TokenMintV1 call");
                 let params: MoneyTokenMintParamsV1 = deserialize_async(&data[1..]).await?;
                 coins.push(params.coin);
-                // TODO: why is this commented?
-                //notes.push(output.note);
+                // Grab the note from the parent auth call
+                let parent_idx = call.parent_index.unwrap();
+                let parent_call = &calls[parent_idx];
+                let params: MoneyAuthTokenMintParamsV1 =
+                    deserialize_async(&parent_call.data.data[1..]).await?;
+                notes.push(params.enc_note);
             }
             MoneyFunction::TokenFreezeV1 => {
-                println!("[apply_tx_money_data] Found Money::TokenFreezeV1 call");
+                println!("[parse_money_call] Found Money::TokenFreezeV1 call");
                 let params: MoneyTokenFreezeParamsV1 = deserialize_async(&data[1..]).await?;
                 let token_id = TokenId::derive_public(params.mint_public);
                 freezes.push(token_id);
             }
             MoneyFunction::PoWRewardV1 => {
-                println!("[apply_tx_money_data] Found Money::PoWRewardV1 call");
+                println!("[parse_money_call] Found Money::PoWRewardV1 call");
                 let params: MoneyPoWRewardParamsV1 = deserialize_async(&data[1..]).await?;
                 coins.push(params.output.coin);
                 notes.push(params.output.note);
             }
             MoneyFunction::AuthTokenMintV1 => {
-                println!("[apply_tx_money_data] Found Money::AuthTokenMintV1 call");
-                // TODO: implement
+                println!("[parse_money_call] Found Money::AuthTokenMintV1 call");
+                // Handled in TokenMint
             }
         }
 
@@ -766,8 +774,13 @@ impl Drk {
     }
 
     /// Append data related to Money contract transactions into the wallet database.
-    pub async fn apply_tx_money_data(&self, data: &[u8], tx_hash: &String) -> Result<()> {
-        let (nullifiers, coins, notes, freezes) = self.parse_call_data(data).await?;
+    pub async fn apply_tx_money_data(
+        &self,
+        call_idx: usize,
+        calls: &[DarkLeaf<ContractCall>],
+        tx_hash: &String,
+    ) -> Result<()> {
+        let (nullifiers, coins, notes, freezes) = self.parse_money_call(call_idx, calls).await?;
         let secrets = self.get_money_secrets().await?;
         let dao_secrets = self.get_dao_secrets().await?;
         let mut tree = self.get_money_tree().await?;
@@ -880,7 +893,7 @@ impl Drk {
             }
 
             println!("[mark_tx_spend] Found Money contract in call {i}");
-            let (nullifiers, _, _, _) = self.parse_call_data(&call.data.data).await?;
+            let (nullifiers, _, _, _) = self.parse_money_call(i, &tx.calls).await?;
             self.mark_spent_coins(&nullifiers, &tx_hash).await?;
         }
 

+ 1 - 1
bin/drk/src/rpc.rs

@@ -186,7 +186,7 @@ impl Drk {
             for (i, call) in tx.calls.iter().enumerate() {
                 if call.data.contract_id == *MONEY_CONTRACT_ID {
                     println!("[scan_block] Found Money contract in call {i}");
-                    self.apply_tx_money_data(&call.data.data, &tx_hash).await?;
+                    self.apply_tx_money_data(i, &tx.calls, &tx_hash).await?;
                     continue
                 }
 

+ 150 - 136
bin/drk/src/token.rs

@@ -22,7 +22,7 @@ use rusqlite::types::Value;
 use darkfi::{
     tx::{ContractCallLeaf, Transaction, TransactionBuilder},
     util::parse::decode_base10,
-    zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
+    zk::{halo2::Field, proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
     zkas::ZkBinary,
     Error, Result,
 };
@@ -32,21 +32,22 @@ use darkfi_money_contract::{
         token_mint_v1::TokenMintCallBuilder,
     },
     model::{CoinAttributes, TokenAttributes, TokenId},
-    MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1,
-    MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
+    MoneyFunction, MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
+    MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
 };
 use darkfi_sdk::{
     crypto::{
         contract_id::MONEY_CONTRACT_ID, poseidon_hash, BaseBlind, Blind, FuncId, FuncRef, Keypair,
         PublicKey, SecretKey,
     },
-    dark_tree::DarkLeaf,
+    dark_tree::DarkTree,
     pasta::pallas,
     tx::ContractCall,
 };
 use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
 
 use crate::{
+    convert_named_params,
     money::{
         BALANCE_BASE10_DECIMALS, MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_MINT_AUTHORITY,
         MONEY_TOKENS_COL_TOKEN_BLIND, MONEY_TOKENS_COL_TOKEN_ID, MONEY_TOKENS_TABLE,
@@ -118,50 +119,86 @@ impl Drk {
         Ok(token_id)
     }
 
+    /// Auxiliary function to parse a `MONEY_TOKENS_TABLE` records.
+    /// The boolean in the returned tuples notes if the token mint authority is frozen.
+    async fn parse_mint_authority_record(
+        &self,
+        row: &[Value],
+    ) -> Result<(TokenId, SecretKey, BaseBlind, bool)> {
+        let Value::Blob(ref token_bytes) = row[0] else {
+            return Err(Error::ParseFailed(
+                "[parse_mint_authority_record] Token ID bytes parsing failed",
+            ))
+        };
+        let token_id = deserialize_async(token_bytes).await?;
+
+        let Value::Blob(ref auth_bytes) = row[1] else {
+            return Err(Error::ParseFailed(
+                "[parse_mint_authority_record] Mint authority bytes parsing failed",
+            ))
+        };
+        let mint_authority = deserialize_async(auth_bytes).await?;
+
+        let Value::Blob(ref token_blind_bytes) = row[2] else {
+            return Err(Error::ParseFailed(
+                "[parse_mint_authority_record] Token blind bytes parsing failed",
+            ))
+        };
+        let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
+
+        let Value::Integer(frozen) = row[3] else {
+            return Err(Error::ParseFailed("[parse_mint_authority_record] Is frozen parsing failed"))
+        };
+        let Ok(frozen) = i32::try_from(frozen) else {
+            return Err(Error::ParseFailed("[parse_mint_authority_record] Is frozen parsing failed"))
+        };
+
+        Ok((token_id, mint_authority, token_blind, frozen != 0))
+    }
+
+    /// Fetch all token mint authorities from the wallet.
     pub async fn get_mint_authorities(&self) -> Result<Vec<(TokenId, SecretKey, BaseBlind, bool)>> {
         let rows = match self.wallet.query_multiple(&MONEY_TOKENS_TABLE, &[], &[]).await {
             Ok(r) => r,
             Err(e) => {
                 return Err(Error::RusqliteError(format!(
-                    "[get_mint_authorities] Tokens retrieval failed: {e:?}"
+                    "[get_mint_authorities] Tokens mint autorities retrieval failed: {e:?}"
                 )))
             }
         };
 
         let mut ret = Vec::with_capacity(rows.len());
         for row in rows {
-            let Value::Blob(ref token_bytes) = row[0] else {
-                return Err(Error::ParseFailed(
-                    "[get_mint_authorities] Token ID bytes parsing failed",
-                ))
-            };
-            let token_id = deserialize_async(token_bytes).await?;
+            ret.push(self.parse_mint_authority_record(&row).await?);
+        }
 
-            let Value::Blob(ref auth_bytes) = row[1] else {
-                return Err(Error::ParseFailed(
-                    "[get_mint_authorities] Mint authority bytes parsing failed",
-                ))
-            };
-            let mint_authority = deserialize_async(auth_bytes).await?;
+        Ok(ret)
+    }
 
-            let Value::Blob(ref token_blind_bytes) = row[2] else {
-                return Err(Error::ParseFailed(
-                    "[get_mint_authorities] Token blind bytes parsing failed",
-                ))
+    /// Fetch provided token unfrozen mint authority from the wallet.
+    async fn get_token_mint_authority(
+        &self,
+        token_id: &TokenId,
+    ) -> Result<(TokenId, SecretKey, BaseBlind, bool)> {
+        let row =
+            match self.wallet.query_single(&MONEY_TOKENS_TABLE, &[], convert_named_params! {(MONEY_TOKENS_COL_TOKEN_ID, serialize_async(token_id).await)}).await {
+                Ok(r) => r,
+                Err(e) => {
+                    return Err(Error::RusqliteError(format!(
+                        "[get_token_mint_authority] Token mint autority retrieval failed: {e:?}"
+                    )))
+                }
             };
-            let token_blind: BaseBlind = deserialize_async(token_blind_bytes).await?;
 
-            let Value::Integer(frozen) = row[3] else {
-                return Err(Error::ParseFailed("[get_mint_authorities] Is frozen parsing failed"))
-            };
-            let Ok(frozen) = i32::try_from(frozen) else {
-                return Err(Error::ParseFailed("[get_mint_authorities] Is frozen parsing failed"))
-            };
+        let token = self.parse_mint_authority_record(&row).await?;
 
-            ret.push((token_id, mint_authority, token_blind, frozen != 0));
+        if token.3 {
+            return Err(Error::Custom(
+                "This token mint is marked as frozen in the wallet".to_string(),
+            ))
         }
 
-        Ok(ret)
+        Ok(token)
     }
 
     /// Create a token mint transaction. Returns the transaction object on success.
@@ -169,117 +206,82 @@ impl Drk {
         &self,
         amount: &str,
         recipient: PublicKey,
-        token_attrs: TokenAttributes,
+        token_id: TokenId,
+        spend_hook: Option<FuncId>,
+        user_data: Option<pallas::Base>,
     ) -> Result<Transaction> {
-        // TODO: Mint directly into DAO treasury
-        let spend_hook = FuncId::none();
-        let user_data = pallas::Base::zero();
-
+        // Decode provided amount
         let amount = decode_base10(amount, BALANCE_BASE10_DECIMALS, false)?;
-        let token_id = token_attrs.to_token_id();
 
-        let mut tokens = self.get_mint_authorities().await?;
-        tokens.retain(|x| x.0 == token_id);
-        if tokens.is_empty() {
-            return Err(Error::Custom(format!(
-                "Did not find mint authority for token ID {token_id}"
-            )))
-        }
-        assert!(tokens.len() == 1);
-
-        let mint_authority = Keypair::new(tokens[0].1);
-
-        if tokens[0].3 {
-            return Err(Error::Custom(
-                "This token mint is marked as frozen in the wallet".to_string(),
-            ))
-        }
+        // Grab token ID mint authority
+        let token_mint_authority = self.get_token_mint_authority(&token_id).await?;
+        let mint_authority = Keypair::new(token_mint_authority.1);
 
         // Now we need to do a lookup for the zkas proof bincodes, and create
         // the circuit objects and proving keys so we can build the transaction.
         // We also do this through the RPC.
         let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
 
-        let (mint_zkbin, mint_pk) = {
-            let mint_zkas_ns = MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1;
-
-            let Some(token_mint_zkbin) = zkas_bins.iter().find(|x| x.0 == mint_zkas_ns) else {
-                return Err(Error::Custom("Token mint circuit not found".to_string()))
-            };
-
-            let mint_zkbin = ZkBinary::decode(&token_mint_zkbin.1)?;
-            let token_mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
-
-            println!("Creating token mint circuit proving keys");
-            let mint_pk = ProvingKey::build(mint_zkbin.k, &token_mint_circuit);
-
-            (mint_zkbin, mint_pk)
+        let Some(mint_zkbin) =
+            zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1)
+        else {
+            return Err(Error::Custom("Token mint circuit not found".to_string()))
         };
 
-        let (auth_mint_zkbin, auth_mint_pk) = {
-            let auth_zkas_ns = MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1;
-
-            let Some(token_auth_mint_zkbin) = zkas_bins.iter().find(|x| x.0 == auth_zkas_ns) else {
-                return Err(Error::Custom("Token mint circuit not found".to_string()))
-            };
-
-            let auth_mint_zkbin = ZkBinary::decode(&token_auth_mint_zkbin.1)?;
-            let token_auth_mint_circuit =
-                ZkCircuit::new(empty_witnesses(&auth_mint_zkbin)?, &auth_mint_zkbin);
-
-            println!("Creating token mint circuit proving keys");
-            let auth_mint_pk = ProvingKey::build(auth_mint_zkbin.k, &token_auth_mint_circuit);
-
-            (auth_mint_zkbin, auth_mint_pk)
+        let Some(auth_mint_zkbin) =
+            zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_AUTH_TOKEN_MINT_NS_V1)
+        else {
+            return Err(Error::Custom("Auth token mint circuit not found".to_string()))
         };
 
-        /*
-        let mint_builder = TokenMintCallBuilder {
-            mint_keypair: mint_authority,
-            recipient,
-            amount,
-            spend_hook,
-            user_data,
-            token_mint_zkbin,
-            token_mint_pk,
+        let Some(fee_zkbin) = zkas_bins.iter().find(|x| x.0 == MONEY_CONTRACT_ZKAS_FEE_NS_V1)
+        else {
+            return Err(Error::Custom("Fee circuit not found".to_string()))
         };
 
-        println!("Building transaction parameters");
-        let debris = mint_builder.build()?;
+        let mint_zkbin = ZkBinary::decode(&mint_zkbin.1)?;
+        let auth_mint_zkbin = ZkBinary::decode(&auth_mint_zkbin.1)?;
+        let fee_zkbin = ZkBinary::decode(&fee_zkbin.1)?;
 
-        // Encode and sign the transaction
-        let mut data = vec![MoneyFunction::TokenMintV1 as u8];
-        debris.params.encode_async(&mut data).await?;
-        let call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
-        let mut tx_builder =
-            TransactionBuilder::new(ContractCallLeaf { call, proofs: debris.proofs }, vec![])?;
-        let mut tx = tx_builder.build()?;
-        let sigs = tx.create_sigs(&mut OsRng, &[mint_authority.secret])?;
-        tx.signatures = vec![sigs];
-        */
+        let mint_circuit = ZkCircuit::new(empty_witnesses(&mint_zkbin)?, &mint_zkbin);
+        let auth_mint_circuit =
+            ZkCircuit::new(empty_witnesses(&auth_mint_zkbin)?, &auth_mint_zkbin);
+        let fee_circuit = ZkCircuit::new(empty_witnesses(&fee_zkbin)?, &fee_zkbin);
+
+        // Creating TokenMint, AuthTokenMint and Fee circuits proving keys
+        let mint_pk = ProvingKey::build(mint_zkbin.k, &mint_circuit);
+        let auth_mint_pk = ProvingKey::build(auth_mint_zkbin.k, &auth_mint_circuit);
+        let fee_pk = ProvingKey::build(fee_zkbin.k, &fee_circuit);
 
-        let _auth_func_id = FuncRef {
+        // Create the Auth FuncID
+        let auth_func_id = FuncRef {
             contract_id: *MONEY_CONTRACT_ID,
             func_code: MoneyFunction::AuthTokenMintV1 as u8,
         }
         .to_func_id();
 
-        //let token_attrs = TokenAttributes {
-        //    auth_parent: auth_func_id,
-        //    user_data: poseidon_hash([mint_authority.public.x(), mint_authority.public.y()]),
-        //    blind: token_blind,
-        //};
-        //let token_id = token_attrs.to_token_id();
+        let (mint_auth_x, mint_auth_y) = mint_authority.public.xy();
 
+        let token_attrs = TokenAttributes {
+            auth_parent: auth_func_id,
+            user_data: poseidon_hash([mint_auth_x, mint_auth_y]),
+            blind: token_mint_authority.2,
+        };
+
+        // Sanity check
+        assert_eq!(token_id, token_attrs.to_token_id());
+
+        // Build the coin attributes
         let coin_attrs = CoinAttributes {
             public_key: recipient,
             value: amount,
             token_id,
-            spend_hook,
-            user_data,
+            spend_hook: spend_hook.unwrap_or(FuncId::none()),
+            user_data: user_data.unwrap_or(pallas::Base::ZERO),
             blind: Blind::random(&mut OsRng),
         };
 
+        // Create the minting call
         let builder = TokenMintCallBuilder {
             coin_attrs: coin_attrs.clone(),
             token_attrs: token_attrs.clone(),
@@ -291,6 +293,7 @@ impl Drk {
         mint_debris.params.encode_async(&mut data).await?;
         let mint_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
+        // Create the auth call
         let builder = AuthTokenMintCallBuilder {
             coin_attrs,
             token_attrs,
@@ -303,40 +306,51 @@ impl Drk {
         auth_debris.params.encode_async(&mut data).await?;
         let auth_call = ContractCall { contract_id: *MONEY_CONTRACT_ID, data };
 
-        let mut tx = Transaction {
-            calls: vec![
-                DarkLeaf { data: mint_call, parent_index: Some(1), children_indexes: vec![] },
-                DarkLeaf { data: auth_call, parent_index: None, children_indexes: vec![0] },
-            ],
-            proofs: vec![mint_debris.proofs, auth_debris.proofs],
-            signatures: vec![],
-        };
+        // Create the TransactionBuilder containing above calls
+        let mut tx_builder = TransactionBuilder::new(
+            ContractCallLeaf { call: auth_call, proofs: auth_debris.proofs },
+            vec![DarkTree::new(
+                ContractCallLeaf { call: mint_call, proofs: mint_debris.proofs },
+                vec![],
+                None,
+                None,
+            )],
+        )?;
+
+        // We first have to execute the fee-less tx to gather its used gas, and then we feed
+        // it into the fee-creating function.
+        let mut tx = tx_builder.build()?;
         let mint_sigs = tx.create_sigs(&[])?;
         let auth_sigs = tx.create_sigs(&[mint_authority.secret])?;
         tx.signatures = vec![mint_sigs, auth_sigs];
 
+        let tree = self.get_money_tree().await?;
+        let secret = self.default_secret().await?;
+        let fee_public = PublicKey::from_secret(secret);
+        let (fee_call, fee_proofs, fee_secrets) =
+            self.append_fee_call(&tx, fee_public, &tree, &fee_pk, &fee_zkbin, None).await?;
+
+        // Append the fee call to the transaction
+        tx_builder.append(ContractCallLeaf { call: fee_call, proofs: fee_proofs }, vec![])?;
+
+        // Now build the actual transaction and sign it with all necessary keys.
+        let mut tx = tx_builder.build()?;
+        let sigs = tx.create_sigs(&[])?;
+        tx.signatures.push(sigs);
+        let sigs = tx.create_sigs(&[mint_authority.secret])?;
+        tx.signatures.push(sigs);
+        let sigs = tx.create_sigs(&fee_secrets)?;
+        tx.signatures.push(sigs);
+
         Ok(tx)
     }
 
     /// Create a token freeze transaction. Returns the transaction object on success.
     pub async fn freeze_token(&self, token_attrs: TokenAttributes) -> Result<Transaction> {
-        let token_id = token_attrs.to_token_id();
-        let mut tokens = self.get_mint_authorities().await?;
-        tokens.retain(|x| x.0 == token_id);
-        if tokens.is_empty() {
-            return Err(Error::Custom(format!(
-                "Did not find mint authority for token ID {token_id}"
-            )))
-        }
-        assert!(tokens.len() == 1);
-
-        let mint_authority = Keypair::new(tokens[0].1);
-
-        if tokens[0].3 {
-            return Err(Error::Custom(
-                "This token is already marked as frozen in the wallet".to_string(),
-            ))
-        }
+        // Grab token ID mint authority
+        let token_mint_authority =
+            self.get_token_mint_authority(&token_attrs.to_token_id()).await?;
+        let mint_authority = Keypair::new(token_mint_authority.1);
 
         let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
         let zkas_ns = MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1;

+ 1 - 1
contrib/localnet/darkfid-single-node/README.md

@@ -54,7 +54,7 @@ of the guide can be added for future regressions.
 | 9  | Mint auths retrieval      | token list                                       | Pass               |
 | 10 | Alias add                 | alias add {ALIAS} {TOKEN}                        | Pass               |
 | 11 | Aliases retrieval         | alias show                                       | Pass               |
-| 12 | Mint generation           | token mint {ALIAS} {AMOUNT} {ADDR}               | Failure: disabled  |
+| 12 | Mint generation           | token mint {ALIAS} {AMOUNT} {ADDR}               | Pass               |
 | 13 | Transfer                  | transfer {AMOUNT} {ALIAS} {ADDR}                 | Pass               |
 | 14 | Coins retrieval           | wallet --coins                                   | Pass               |
 | 15 | OTC initialization        | otc init -v {AMOUNT}:{AMOUNT} -t {ALIAS}:{ALIAS} | Failure: needs #12 |

+ 2 - 2
src/contract/money/src/client/auth_token_mint_v1.rs

@@ -25,7 +25,7 @@ use darkfi_sdk::{
     crypto::{note::AeadEncryptedNote, pasta_prelude::*, pedersen_commitment_u64, Blind, Keypair},
     pasta::pallas,
 };
-use log::info;
+use log::debug;
 use rand::rngs::OsRng;
 
 use crate::{
@@ -54,7 +54,7 @@ pub struct AuthTokenMintCallBuilder {
 
 impl AuthTokenMintCallBuilder {
     pub fn build(&self) -> Result<AuthTokenMintCallDebris> {
-        info!("Building Money::AuthTokenMintV1 contract call");
+        debug!("Building Money::AuthTokenMintV1 contract call");
 
         let value_blind = Blind::random(&mut OsRng);
         let value_commit = pedersen_commitment_u64(self.coin_attrs.value, value_blind);

+ 2 - 2
src/contract/money/src/client/token_mint_v1.rs

@@ -22,7 +22,7 @@ use darkfi::{
     Result,
 };
 use darkfi_sdk::pasta::pallas;
-use log::info;
+use log::debug;
 use rand::rngs::OsRng;
 
 use crate::model::{CoinAttributes, MoneyTokenMintParamsV1, TokenAttributes};
@@ -45,7 +45,7 @@ pub struct TokenMintCallBuilder {
 
 impl TokenMintCallBuilder {
     pub fn build(&self) -> Result<TokenMintCallDebris> {
-        info!("Building Money::TokenMintV1 contract call");
+        debug!("Building Money::TokenMintV1 contract call");
         let (public_x, public_y) = self.coin_attrs.public_key.xy();
 
         let prover_witnesses = vec![

+ 2 - 0
src/contract/money/src/entrypoint.rs

@@ -114,6 +114,7 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     let burn_v1_bincode = include_bytes!("../proof/burn_v1.zk.bin");
     let token_mint_v1_bincode = include_bytes!("../proof/token_mint_v1.zk.bin");
     let token_frz_v1_bincode = include_bytes!("../proof/token_freeze_v1.zk.bin");
+    let auth_token_mint_v1_bincode = include_bytes!("../proof/auth_token_mint_v1.zk.bin");
 
     // For that, we use `wasm::db::zkas_wasm::db::db_set` and pass in the bincode.
     wasm::db::zkas_db_set(&fee_v1_bincode[..])?;
@@ -121,6 +122,7 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
     wasm::db::zkas_db_set(&burn_v1_bincode[..])?;
     wasm::db::zkas_db_set(&token_mint_v1_bincode[..])?;
     wasm::db::zkas_db_set(&token_frz_v1_bincode[..])?;
+    wasm::db::zkas_db_set(&auth_token_mint_v1_bincode[..])?;
 
     let tx_hash = wasm::util::get_tx_hash()?;
     // The max outputs for a tx in BTC is 2501