/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 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 .
*/
use lazy_static::lazy_static;
use rand::rngs::OsRng;
use darkfi::{
tx::{ContractCallLeaf, Transaction, TransactionBuilder},
Error, Result,
};
use darkfi_deployooor_contract::{
client::{deploy_v1::DeployCallBuilder, lock_v1::LockCallBuilder},
DeployFunction,
};
use darkfi_sdk::{
crypto::{ContractId, Keypair, DEPLOYOOOR_CONTRACT_ID},
ContractCall,
};
use darkfi_serial::{deserialize_async, serialize_async, AsyncEncodable};
use rusqlite::types::Value;
use crate::{convert_named_params, error::WalletDbResult, Drk};
// Wallet SQL table constant names. These have to represent the `wallet.sql`
// SQL schema. Table names are prefixed with the contract ID to avoid collisions.
lazy_static! {
pub static ref DEPLOY_AUTH_TABLE: String =
format!("{}_deploy_auth", DEPLOYOOOR_CONTRACT_ID.to_string());
}
// DEPLOY_AUTH_TABLE
pub const DEPLOY_AUTH_COL_ID: &str = "id";
pub const DEPLOY_AUTH_COL_DEPLOY_AUTHORITY: &str = "deploy_authority";
pub const DEPLOY_AUTH_COL_IS_FROZEN: &str = "is_frozen";
pub const DEPLOY_AUTH_COL_FREEZE_HEIGHT: &str = "freeze_height";
impl Drk {
/// Initialize wallet with tables for the Deployooor contract.
pub fn initialize_deployooor(&self) -> WalletDbResult<()> {
// Initialize Deployooor wallet schema
let wallet_schema = include_str!("../deploy.sql");
self.wallet.exec_batch_sql(wallet_schema)?;
Ok(())
}
/// Generate a new deploy authority keypair and place it into the wallet
pub async fn deploy_auth_keygen(&self) -> WalletDbResult<()> {
eprintln!("Generating a new keypair");
let keypair = Keypair::random(&mut OsRng);
let freeze_height: Option = None;
let query = format!(
"INSERT INTO {} ({}, {}, {}) VALUES (?1, ?2, ?3);",
*DEPLOY_AUTH_TABLE,
DEPLOY_AUTH_COL_DEPLOY_AUTHORITY,
DEPLOY_AUTH_COL_IS_FROZEN,
DEPLOY_AUTH_COL_FREEZE_HEIGHT,
);
self.wallet.exec_sql(
&query,
rusqlite::params![serialize_async(&keypair).await, 0, freeze_height],
)?;
eprintln!("Created new contract deploy authority");
println!("Contract ID: {}", ContractId::derive_public(keypair.public));
Ok(())
}
/// List contract deploy authorities from the wallet
pub async fn list_deploy_auth(&self) -> Result)>> {
let rows = match self.wallet.query_multiple(&DEPLOY_AUTH_TABLE, &[], &[]) {
Ok(r) => r,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[list_deploy_auth] Deploy auth retrieval failed: {e:?}",
)))
}
};
let mut ret = Vec::with_capacity(rows.len());
for row in rows {
let Value::Integer(idx) = row[0] else {
return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse index"))
};
let Value::Blob(ref auth_bytes) = row[1] else {
return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse keypair bytes"))
};
let deploy_auth: Keypair = deserialize_async(auth_bytes).await?;
let Value::Integer(frozen) = row[2] else {
return Err(Error::ParseFailed("[list_deploy_auth] Failed to parse \"is_frozen\""))
};
let freeze_height = match row[3] {
Value::Integer(freeze_height) => {
let Ok(freeze_height) = u32::try_from(freeze_height) else {
return Err(Error::ParseFailed(
"[list_deploy_auth] Freeze height parsing failed",
))
};
Some(freeze_height)
}
Value::Null => None,
_ => {
return Err(Error::ParseFailed(
"[list_deploy_auth] Freeze height parsing failed",
))
}
};
ret.push((
idx,
ContractId::derive_public(deploy_auth.public),
frozen != 0,
freeze_height,
))
}
Ok(ret)
}
/// Retrieve a deploy authority keypair given an index
async fn get_deploy_auth(&self, idx: u64) -> Result {
// Find the deploy authority keypair
let row = match self.wallet.query_single(
&DEPLOY_AUTH_TABLE,
&[DEPLOY_AUTH_COL_DEPLOY_AUTHORITY],
convert_named_params! {(DEPLOY_AUTH_COL_ID, idx)},
) {
Ok(v) => v,
Err(e) => {
return Err(Error::DatabaseError(format!(
"[deploy_contract] Failed to retrieve deploy authority keypair: {e:?}"
)))
}
};
let Value::Blob(ref keypair_bytes) = row[0] else {
return Err(Error::ParseFailed("[deploy_contract] Failed to parse keypair bytes"))
};
let keypair: Keypair = deserialize_async(keypair_bytes).await?;
Ok(keypair)
}
/// Create a feeless contract deployment transaction.
pub async fn deploy_contract(
&self,
deploy_auth: u64,
wasm_bincode: Vec,
deploy_ix: Vec,
) -> Result {
// Fetch the keypair
let deploy_keypair = self.get_deploy_auth(deploy_auth).await?;
// Create the contract call
let deploy_call = DeployCallBuilder { deploy_keypair, wasm_bincode, deploy_ix };
let deploy_debris = deploy_call.build()?;
// Encode the call
let mut data = vec![DeployFunction::DeployV1 as u8];
deploy_debris.params.encode_async(&mut data).await?;
let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
let mut tx_builder =
TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
let mut tx = tx_builder.build()?;
let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
tx.signatures = vec![sigs];
Ok(tx)
}
/// Create a feeless contract redeployment lock transaction.
pub async fn lock_contract(&self, deploy_auth: u64) -> Result {
// Fetch the keypair
let deploy_keypair = self.get_deploy_auth(deploy_auth).await?;
// Create the contract call
let lock_call = LockCallBuilder { deploy_keypair };
let lock_debris = lock_call.build()?;
// Encode the call
let mut data = vec![DeployFunction::LockV1 as u8];
lock_debris.params.encode_async(&mut data).await?;
let call = ContractCall { contract_id: *DEPLOYOOOR_CONTRACT_ID, data };
let mut tx_builder =
TransactionBuilder::new(ContractCallLeaf { call, proofs: vec![] }, vec![])?;
let mut tx = tx_builder.build()?;
let sigs = tx.create_sigs(&[deploy_keypair.secret])?;
tx.signatures = vec![sigs];
Ok(tx)
}
}