|
@@ -0,0 +1,225 @@
|
|
|
|
|
+/* This file is part of DarkFi (https://dark.fi)
|
|
|
|
|
+ *
|
|
|
|
|
+ * Copyright (C) 2020-2024 Dyne.org foundation
|
|
|
|
|
+ *
|
|
|
|
|
+ * This program is free software: you can redistribute it and/or modify
|
|
|
|
|
+ * it under the terms of the GNU Affero General Public License as
|
|
|
|
|
+ * published by the Free Software Foundation, either version 3 of the
|
|
|
|
|
+ * License, or (at your option) any later version.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This program is distributed in the hope that it will be useful,
|
|
|
|
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
+ * GNU Affero General Public License for more details.
|
|
|
|
|
+ *
|
|
|
|
|
+ * You should have received a copy of the GNU Affero General Public License
|
|
|
|
|
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+use rand::rngs::OsRng;
|
|
|
|
|
+use rusqlite::types::Value;
|
|
|
|
|
+
|
|
|
|
|
+use darkfi::{
|
|
|
|
|
+ tx::{ContractCallLeaf, Transaction, TransactionBuilder},
|
|
|
|
|
+ util::parse::decode_base10,
|
|
|
|
|
+ zk::{proof::ProvingKey, vm::ZkCircuit, vm_heap::empty_witnesses},
|
|
|
|
|
+ zkas::ZkBinary,
|
|
|
|
|
+ Error, Result,
|
|
|
|
|
+};
|
|
|
|
|
+use darkfi_money_contract::{
|
|
|
|
|
+ client::{
|
|
|
|
|
+ token_freeze_v1::TokenFreezeCallBuilder, token_mint_v1::TokenMintCallBuilder,
|
|
|
|
|
+ MONEY_TOKENS_COL_IS_FROZEN, MONEY_TOKENS_COL_MINT_AUTHORITY, MONEY_TOKENS_COL_TOKEN_ID,
|
|
|
|
|
+ MONEY_TOKENS_TABLE,
|
|
|
|
|
+ },
|
|
|
|
|
+ MoneyFunction, MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
|
|
|
|
|
+};
|
|
|
|
|
+use darkfi_sdk::{
|
|
|
|
|
+ crypto::{contract_id::MONEY_CONTRACT_ID, Keypair, PublicKey, SecretKey, TokenId},
|
|
|
|
|
+ pasta::pallas,
|
|
|
|
|
+ tx::ContractCall,
|
|
|
|
|
+};
|
|
|
|
|
+use darkfi_serial::{deserialize, serialize, Encodable};
|
|
|
|
|
+
|
|
|
|
|
+use crate::{error::WalletDbResult, Drk};
|
|
|
|
|
+
|
|
|
|
|
+impl Drk {
|
|
|
|
|
+ /// Import a token mint authority into the wallet
|
|
|
|
|
+ pub async fn import_mint_authority(&self, mint_authority: SecretKey) -> WalletDbResult<()> {
|
|
|
|
|
+ let token_id = TokenId::derive(mint_authority);
|
|
|
|
|
+ let is_frozen = 0;
|
|
|
|
|
+
|
|
|
|
|
+ let query = format!(
|
|
|
|
|
+ "INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
|
|
|
|
|
+ MONEY_TOKENS_TABLE,
|
|
|
|
|
+ MONEY_TOKENS_COL_MINT_AUTHORITY,
|
|
|
|
|
+ MONEY_TOKENS_COL_TOKEN_ID,
|
|
|
|
|
+ MONEY_TOKENS_COL_IS_FROZEN,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ self.wallet
|
|
|
|
|
+ .exec_sql(
|
|
|
|
|
+ &query,
|
|
|
|
|
+ rusqlite::params![serialize(&mint_authority), serialize(&token_id), is_frozen,],
|
|
|
|
|
+ )
|
|
|
|
|
+ .await
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pub async fn list_tokens(&self) -> Result<Vec<(TokenId, SecretKey, bool)>> {
|
|
|
|
|
+ let rows = match self.wallet.query_multiple(MONEY_TOKENS_TABLE, &[], &[]).await {
|
|
|
|
|
+ Ok(r) => r,
|
|
|
|
|
+ Err(e) => {
|
|
|
|
|
+ return Err(Error::RusqliteError(format!(
|
|
|
|
|
+ "[list_tokens] Tokens retrieval failed: {e:?}"
|
|
|
|
|
+ )))
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ let mut ret = Vec::with_capacity(rows.len());
|
|
|
|
|
+ for row in rows {
|
|
|
|
|
+ let Value::Blob(ref auth_bytes) = row[0] else {
|
|
|
|
|
+ return Err(Error::ParseFailed("[list_tokens] Mint authority bytes parsing failed"))
|
|
|
|
|
+ };
|
|
|
|
|
+ let mint_authority = deserialize(auth_bytes)?;
|
|
|
|
|
+
|
|
|
|
|
+ let Value::Blob(ref token_bytes) = row[1] else {
|
|
|
|
|
+ return Err(Error::ParseFailed("[list_tokens] Token ID bytes parsing failed"))
|
|
|
|
|
+ };
|
|
|
|
|
+ let token_id = deserialize(token_bytes)?;
|
|
|
|
|
+
|
|
|
|
|
+ let Value::Integer(frozen) = row[2] else {
|
|
|
|
|
+ return Err(Error::ParseFailed("[list_tokens] Is frozen parsing failed"))
|
|
|
|
|
+ };
|
|
|
|
|
+ let Ok(frozen) = i32::try_from(frozen) else {
|
|
|
|
|
+ return Err(Error::ParseFailed("[list_tokens] Is frozen parsing failed"))
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ ret.push((token_id, mint_authority, frozen != 0));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Ok(ret)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Create a token mint transaction. Returns the transaction object on success.
|
|
|
|
|
+ pub async fn mint_token(
|
|
|
|
|
+ &self,
|
|
|
|
|
+ amount: &str,
|
|
|
|
|
+ recipient: PublicKey,
|
|
|
|
|
+ token_id: TokenId,
|
|
|
|
|
+ ) -> Result<Transaction> {
|
|
|
|
|
+ // TODO: Mint directly into DAO treasury
|
|
|
|
|
+ let spend_hook = pallas::Base::zero();
|
|
|
|
|
+ let user_data = pallas::Base::zero();
|
|
|
|
|
+
|
|
|
|
|
+ let amount = decode_base10(amount, 8, false)?;
|
|
|
|
|
+
|
|
|
|
|
+ let mut tokens = self.list_tokens().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].2 {
|
|
|
|
|
+ return Err(Error::Custom(
|
|
|
|
|
+ "This token mint is marked as frozen in the wallet".to_string(),
|
|
|
|
|
+ ))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 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 zkas_ns = MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1;
|
|
|
|
|
+
|
|
|
|
|
+ let Some(token_mint_zkbin) = zkas_bins.iter().find(|x| x.0 == zkas_ns) else {
|
|
|
|
|
+ return Err(Error::Custom("Token mint circuit not found".to_string()))
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ let token_mint_zkbin = ZkBinary::decode(&token_mint_zkbin.1)?;
|
|
|
|
|
+ let token_mint_circuit =
|
|
|
|
|
+ ZkCircuit::new(empty_witnesses(&token_mint_zkbin)?, &token_mint_zkbin);
|
|
|
|
|
+
|
|
|
|
|
+ eprintln!("Creating token mint circuit proving keys");
|
|
|
|
|
+ let token_mint_pk = ProvingKey::build(token_mint_zkbin.k, &token_mint_circuit);
|
|
|
|
|
+ let mint_builder = TokenMintCallBuilder {
|
|
|
|
|
+ mint_authority,
|
|
|
|
|
+ recipient,
|
|
|
|
|
+ amount,
|
|
|
|
|
+ spend_hook,
|
|
|
|
|
+ user_data,
|
|
|
|
|
+ token_mint_zkbin,
|
|
|
|
|
+ token_mint_pk,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ eprintln!("Building transaction parameters");
|
|
|
|
|
+ let debris = mint_builder.build()?;
|
|
|
|
|
+
|
|
|
|
|
+ // Encode and sign the transaction
|
|
|
|
|
+ let mut data = vec![MoneyFunction::TokenMintV1 as u8];
|
|
|
|
|
+ debris.params.encode(&mut data)?;
|
|
|
|
|
+ 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];
|
|
|
|
|
+
|
|
|
|
|
+ Ok(tx)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /// Create a token freeze transaction. Returns the transaction object on success.
|
|
|
|
|
+ pub async fn freeze_token(&self, token_id: TokenId) -> Result<Transaction> {
|
|
|
|
|
+ let mut tokens = self.list_tokens().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].2 {
|
|
|
|
|
+ return Err(Error::Custom(
|
|
|
|
|
+ "This token is already marked as frozen in the wallet".to_string(),
|
|
|
|
|
+ ))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let zkas_bins = self.lookup_zkas(&MONEY_CONTRACT_ID).await?;
|
|
|
|
|
+ let zkas_ns = MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1;
|
|
|
|
|
+
|
|
|
|
|
+ let Some(token_freeze_zkbin) = zkas_bins.iter().find(|x| x.0 == zkas_ns) else {
|
|
|
|
|
+ return Err(Error::Custom("Token freeze circuit not found".to_string()))
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ let token_freeze_zkbin = ZkBinary::decode(&token_freeze_zkbin.1)?;
|
|
|
|
|
+ let token_freeze_circuit =
|
|
|
|
|
+ ZkCircuit::new(empty_witnesses(&token_freeze_zkbin)?, &token_freeze_zkbin);
|
|
|
|
|
+
|
|
|
|
|
+ eprintln!("Creating token freeze circuit proving keys");
|
|
|
|
|
+ let token_freeze_pk = ProvingKey::build(token_freeze_zkbin.k, &token_freeze_circuit);
|
|
|
|
|
+ let freeze_builder =
|
|
|
|
|
+ TokenFreezeCallBuilder { mint_authority, token_freeze_zkbin, token_freeze_pk };
|
|
|
|
|
+
|
|
|
|
|
+ eprintln!("Building transaction parameters");
|
|
|
|
|
+ let debris = freeze_builder.build()?;
|
|
|
|
|
+
|
|
|
|
|
+ // Encode and sign the transaction
|
|
|
|
|
+ let mut data = vec![MoneyFunction::TokenFreezeV1 as u8];
|
|
|
|
|
+ debris.params.encode(&mut data)?;
|
|
|
|
|
+ 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];
|
|
|
|
|
+
|
|
|
|
|
+ Ok(tx)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|