Просмотр исходного кода

drk: provide liquidity to dao with transfer and token mint

skoupidi 2 лет назад
Родитель
Сommit
fb5865fce4

+ 25 - 6
bin/drk/src/cli_util.rs

@@ -188,9 +188,13 @@ pub fn generate_completions(shell: &str) -> Result<()> {
 
     let recipient = Arg::with_name("recipient").help("Recipient address");
 
+    let spend_hook = Arg::with_name("spend-hook").help("Optional contract spend hook to use");
+
+    let user_data = Arg::with_name("user-data").help("Optional user data to use");
+
     let transfer = SubCommand::with_name("transfer")
         .about("Create a payment transaction")
-        .args(&vec![amount, token, recipient]);
+        .args(&vec![amount, token, recipient, spend_hook.clone(), user_data.clone()]);
 
     // Otc
     let value_pair = Arg::with_name("value-pair")
@@ -318,8 +322,22 @@ pub fn generate_completions(shell: &str) -> Result<()> {
         .about("Execute a DAO proposal")
         .args(&vec![name, proposal_id]);
 
+    let spend_hook_cmd = SubCommand::with_name("spend-hook")
+        .about("Print the DAO contract base58-encoded spend hook");
+
     let dao = SubCommand::with_name("dao").about("DAO functionalities").subcommands(vec![
-        create, view, import, list, balance, mint, propose, proposals, proposal, vote, exec,
+        create,
+        view,
+        import,
+        list,
+        balance,
+        mint,
+        propose,
+        proposals,
+        proposal,
+        vote,
+        exec,
+        spend_hook_cmd,
     ]);
 
     // Scan
@@ -401,9 +419,9 @@ pub fn generate_completions(shell: &str) -> Result<()> {
         .subcommands(vec![add, show, remove]);
 
     // Token
-    let secret_key = Arg::with_name("secret_key").help("Mint authority secret key");
+    let secret_key = Arg::with_name("secret-key").help("Mint authority secret key");
 
-    let token_blind = Arg::with_name("token_blind").help("Mint authority token blind");
+    let token_blind = Arg::with_name("token-blind").help("Mint authority token blind");
 
     let import = SubCommand::with_name("import")
         .about("Import a mint authority")
@@ -421,8 +439,9 @@ pub fn generate_completions(shell: &str) -> Result<()> {
 
     let recipient = Arg::with_name("recipient").help("Recipient of the minted tokens");
 
-    let mint =
-        SubCommand::with_name("mint").about("Mint tokens").args(&vec![token, amount, recipient]);
+    let mint = SubCommand::with_name("mint")
+        .about("Mint tokens")
+        .args(&vec![token, amount, recipient, spend_hook, user_data]);
 
     let token = Arg::with_name("token").help("Token ID to freeze");
 

+ 96 - 9
bin/drk/src/main.rs

@@ -42,9 +42,10 @@ use darkfi::{
     zk::halo2::Field,
     Result,
 };
+use darkfi_dao_contract::DaoFunction;
 use darkfi_money_contract::model::{Coin, TokenId};
 use darkfi_sdk::{
-    crypto::{BaseBlind, FuncId, PublicKey, SecretKey},
+    crypto::{BaseBlind, FuncId, FuncRef, PublicKey, SecretKey, DAO_CONTRACT_ID},
     pasta::{group::ff::PrimeField, pallas},
     tx::TransactionHash,
 };
@@ -206,6 +207,12 @@ enum Subcmd {
 
         /// Recipient address
         recipient: String,
+
+        /// Optional contract spend hook to use
+        spend_hook: Option<String>,
+
+        /// Optional user data to use
+        user_data: Option<String>,
     },
 
     /// OTC atomic swap
@@ -398,6 +405,9 @@ enum DaoSubcmd {
         /// Numeric identifier for the proposal
         proposal_id: u64,
     },
+
+    /// Print the DAO contract base58-encoded spend hook
+    SpendHook,
 }
 
 #[derive(Clone, Debug, Deserialize, StructOpt)]
@@ -487,6 +497,12 @@ enum TokenSubcmd {
 
         /// Recipient of the minted tokens
         recipient: String,
+
+        /// Optional contract spend hook to use
+        spend_hook: Option<String>,
+
+        /// Optional user data to use
+        user_data: Option<String>,
     },
 
     /// Freeze a token mint
@@ -815,9 +831,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     };
 
                     let spend_hook = if coin.0.note.spend_hook != FuncId::none() {
-                        bs58::encode(&serialize_async(&coin.0.note.spend_hook.inner()).await)
-                            .into_string()
-                            .to_string()
+                        format!("{}", coin.0.note.spend_hook)
                     } else {
                         String::from("-")
                     };
@@ -896,7 +910,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
             Ok(())
         }
 
-        Subcmd::Transfer { amount, token, recipient } => {
+        Subcmd::Transfer { amount, token, recipient, spend_hook, user_data } => {
             let drk = Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
 
             if let Err(e) = f64::from_str(&amount) {
@@ -920,7 +934,39 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 }
             };
 
-            let tx = match drk.transfer(&amount, token_id, rcpt).await {
+            let spend_hook = match spend_hook {
+                Some(s) => match FuncId::from_str(&s) {
+                    Ok(s) => Some(s),
+                    Err(e) => {
+                        eprintln!("Invalid spend hook: {e:?}");
+                        exit(2);
+                    }
+                },
+                None => None,
+            };
+
+            let user_data = match user_data {
+                Some(u) => {
+                    let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
+                        Ok(b) => b,
+                        Err(e) => {
+                            eprintln!("Invalid user data: {e:?}");
+                            exit(2);
+                        }
+                    };
+
+                    match pallas::Base::from_repr(bytes).into() {
+                        Some(v) => Some(v),
+                        None => {
+                            eprintln!("Invalid user data");
+                            exit(2);
+                        }
+                    }
+                }
+                None => None,
+            };
+
+            let tx = match drk.transfer(&amount, token_id, rcpt, spend_hook, user_data).await {
                 Ok(t) => t,
                 Err(e) => {
                     eprintln!("Failed to create payment transaction: {e:?}");
@@ -1265,6 +1311,15 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
 
                 Ok(())
             }
+
+            DaoSubcmd::SpendHook => {
+                let spend_hook =
+                    FuncRef { contract_id: *DAO_CONTRACT_ID, func_code: DaoFunction::Exec as u8 }
+                        .to_func_id();
+                println!("{spend_hook}");
+
+                Ok(())
+            }
         },
 
         Subcmd::AttachFee => {
@@ -1610,8 +1665,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                 Ok(())
             }
 
-            // TODO: Mint directly into DAO treasury
-            TokenSubcmd::Mint { token, amount, recipient } => {
+            TokenSubcmd::Mint { token, amount, recipient, spend_hook, user_data } => {
                 let drk =
                     Drk::new(args.wallet_path, args.wallet_pass, Some(args.endpoint), ex).await?;
 
@@ -1636,7 +1690,40 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
                     }
                 };
 
-                let tx = match drk.mint_token(&amount, rcpt, token_id, None, None).await {
+                let spend_hook = match spend_hook {
+                    Some(s) => match FuncId::from_str(&s) {
+                        Ok(s) => Some(s),
+                        Err(e) => {
+                            eprintln!("Invalid spend hook: {e:?}");
+                            exit(2);
+                        }
+                    },
+                    None => None,
+                };
+
+                let user_data = match user_data {
+                    Some(u) => {
+                        let bytes: [u8; 32] = match bs58::decode(&u).into_vec()?.try_into() {
+                            Ok(b) => b,
+                            Err(e) => {
+                                eprintln!("Invalid user data: {e:?}");
+                                exit(2);
+                            }
+                        };
+
+                        match pallas::Base::from_repr(bytes).into() {
+                            Some(v) => Some(v),
+                            None => {
+                                eprintln!("Invalid user data");
+                                exit(2);
+                            }
+                        }
+                    }
+                    None => None,
+                };
+
+                let tx = match drk.mint_token(&amount, rcpt, token_id, spend_hook, user_data).await
+                {
                     Ok(tx) => tx,
                     Err(e) => {
                         eprintln!("Failed to create token mint transaction: {e:?}");

+ 6 - 1
bin/drk/src/transfer.rs

@@ -28,7 +28,8 @@ use darkfi_money_contract::{
     MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_FEE_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
 };
 use darkfi_sdk::{
-    crypto::{contract_id::MONEY_CONTRACT_ID, Keypair, PublicKey},
+    crypto::{contract_id::MONEY_CONTRACT_ID, FuncId, Keypair, PublicKey},
+    pasta::pallas,
     tx::ContractCall,
 };
 use darkfi_serial::AsyncEncodable;
@@ -42,6 +43,8 @@ impl Drk {
         amount: &str,
         token_id: TokenId,
         recipient: PublicKey,
+        spend_hook: Option<FuncId>,
+        user_data: Option<pallas::Base>,
     ) -> Result<Transaction> {
         // First get all unspent OwnCoins to see what our balance is
         let owncoins = self.get_token_coins(&token_id).await?;
@@ -112,6 +115,8 @@ impl Drk {
             token_id,
             owncoins,
             tree.clone(),
+            spend_hook,
+            user_data,
             mint_zkbin,
             mint_pk,
             burn_zkbin,

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

@@ -66,7 +66,7 @@ of the guide can be added for future regressions.
 | 21 | DAO import                | dao import                                       | Pass               |
 | 22 | DAO list                  | dao list                                         | Pass               |
 | 23 | DAO mint                  | dao mint {DAO}                                   | Pass               |
-| 24 | DAO balance               | dao balance {DAO}                                | Failure: needs #19 |
+| 24 | DAO balance               | dao balance {DAO}                                | Pass               |
 | 25 | DAO propose               | dao propose {DAO} {ADDR} {AMOUNT} {TOKEN}        | Failure: needs #19 |
 | 26 | DAO proposals retrieval   | dao proposals {DAO}                              | Failure: needs #25 |
 | 27 | DAO proposal retrieval    | dao proposal {DAO} {PROPOSAL_ID}                 | Failure: needs #25 |

+ 8 - 2
src/contract/money/src/client/transfer_v1/mod.rs

@@ -72,6 +72,10 @@ pub fn select_coins(coins: Vec<OwnCoin>, min_value: u64) -> Result<(Vec<OwnCoin>
 /// * `token_id`: Token ID that we want to send to the recipient
 /// * `coins`: Set of `OwnCoin` we're given to use in this builder
 /// * `tree`: Merkle tree of coins used to create inclusion proofs
+/// * `output_spend_hook: Optional contract spend hook to use in
+///    the output, not applicable to the change
+/// * `output_user_data: Optional user data to use in the output,
+///    not applicable to the change
 /// * `mint_zkbin`: `Mint_V1` zkas circuit ZkBinary
 /// * `mint_pk`: Proving key for the `Mint_V1` zk circuit
 /// * `burn_zkbin`: `Burn_V1` zkas circuit ZkBinary
@@ -90,6 +94,8 @@ pub fn make_transfer_call(
     token_id: TokenId,
     coins: Vec<OwnCoin>,
     tree: MerkleTree,
+    output_spend_hook: Option<FuncId>,
+    output_user_data: Option<pallas::Base>,
     mint_zkbin: ZkBinary,
     mint_pk: ProvingKey,
     burn_zkbin: ZkBinary,
@@ -135,8 +141,8 @@ pub fn make_transfer_call(
         public_key: recipient,
         value,
         token_id,
-        spend_hook: FuncId::none(),
-        user_data: pallas::Base::ZERO,
+        spend_hook: output_spend_hook.unwrap_or(FuncId::none()),
+        user_data: output_user_data.unwrap_or(pallas::Base::ZERO),
         blind: Blind::random(&mut OsRng),
     });
 

+ 2 - 0
src/contract/money/tests/delayed_tx.rs

@@ -92,6 +92,8 @@ fn delayed_tx() -> Result<()> {
             alice_coins[0].note.token_id,
             alice_coins.to_owned(),
             money_merkle_tree.clone(),
+            None,
+            None,
             mint_zkbin.clone(),
             mint_pk.clone(),
             burn_zkbin.clone(),

+ 2 - 0
src/contract/test-harness/src/money_transfer.rs

@@ -60,6 +60,8 @@ impl TestHarness {
             token_id,
             owncoins.to_owned(),
             wallet.money_merkle_tree.clone(),
+            None,
+            None,
             mint_zkbin.clone(),
             mint_pk.clone(),
             burn_zkbin.clone(),

+ 22 - 4
src/sdk/src/crypto/func_ref.rs

@@ -15,12 +15,16 @@
  * 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 std::str::FromStr;
+
 #[cfg(feature = "async")]
 use darkfi_serial::async_trait;
 use darkfi_serial::{SerialDecodable, SerialEncodable};
 use pasta_curves::pallas;
 
 use super::{pasta_prelude::*, poseidon_hash, ContractId};
+use crate::{fp_from_bs58, fp_to_bs58, ty_from_fp, ContractError};
 
 pub type FunctionCode = u8;
 
@@ -49,10 +53,24 @@ impl FuncId {
     pub fn inner(&self) -> pallas::Base {
         self.0
     }
-}
 
-impl From<pallas::Base> for FuncId {
-    fn from(func_id: pallas::Base) -> Self {
-        Self(func_id)
+    /// Create a `FuncId` object from given bytes, erroring if the
+    /// input bytes are noncanonical.
+    pub fn from_bytes(x: [u8; 32]) -> Result<Self, ContractError> {
+        match pallas::Base::from_repr(x).into() {
+            Some(v) => Ok(Self(v)),
+            None => {
+                Err(ContractError::IoError("Failed to instantiate FuncId from bytes".to_string()))
+            }
+        }
+    }
+
+    /// Convert the `FuncId` type into 32 raw bytes
+    pub fn to_bytes(&self) -> [u8; 32] {
+        self.0.to_repr()
     }
 }
+
+fp_from_bs58!(FuncId);
+fp_to_bs58!(FuncId);
+ty_from_fp!(FuncId);