contract/consensus: laid foundation

This commit is contained in:
aggstam
2023-03-02 19:18:25 +02:00
parent 1361ef7183
commit 4d294d1947
17 changed files with 771 additions and 1 deletions

19
Cargo.lock generated
View File

@@ -1230,6 +1230,25 @@ dependencies = [
"wasmer-middlewares",
]
[[package]]
name = "darkfi-consensus-contract"
version = "0.4.1"
dependencies = [
"async-std",
"chacha20poly1305",
"darkfi",
"darkfi-money-contract",
"darkfi-sdk",
"darkfi-serial",
"getrandom",
"halo2_proofs",
"log",
"rand",
"simplelog",
"sled",
"thiserror",
]
[[package]]
name = "darkfi-dao-contract"
version = "0.4.1"

View File

@@ -48,6 +48,7 @@ members = [
"src/contract/money",
"src/contract/dao",
"src/contract/consensus",
"example/dchat",
]

View File

@@ -22,7 +22,7 @@ use async_std::sync::{Arc, RwLock};
use darkfi_sdk::{
crypto::{
constants::MERKLE_DEPTH,
contract_id::{DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
contract_id::{CONSENSUS_CONTRACT_ID, DAO_CONTRACT_ID, MONEY_CONTRACT_ID},
schnorr::{SchnorrPublic, SchnorrSecret},
MerkleNode, PublicKey, SecretKey,
},
@@ -150,6 +150,7 @@ impl ValidatorState {
// in the money contract.
let money_contract_deploy_payload = serialize(&faucet_pubkeys);
let dao_contract_deploy_payload = vec![];
let consensus_contract_deploy_payload = vec![];
let native_contracts = vec![
(
@@ -164,6 +165,12 @@ impl ValidatorState {
include_bytes!("../contract/dao/dao_contract.wasm").to_vec(),
dao_contract_deploy_payload,
),
(
"Consensus Contract",
*CONSENSUS_CONTRACT_ID,
include_bytes!("../contract/consensus/consensus_contract.wasm").to_vec(),
consensus_contract_deploy_payload,
),
];
info!(target: "consensus::validator", "Deploying native wasm contracts");

View File

@@ -8,3 +8,7 @@ This directory contains native WASM contracts on DarkFi.
## DAO
* https://darkrenaissance.github.io/darkfi/development/darkfi_dao_contract/index.html
## Consensus
* TODO: add link here

2
src/contract/consensus/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
consensus_contract.wasm
proof/*.zk.bin

View File

@@ -0,0 +1,49 @@
[package]
name = "darkfi-consensus-contract"
version = "0.4.1"
authors = ["Dyne.org foundation <foundation@dyne.org>"]
license = "AGPL-3.0-only"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
darkfi-sdk = { path = "../../sdk" }
darkfi-serial = { path = "../../serial", features = ["derive", "crypto"] }
darkfi-money-contract = { path = "../money", features = ["no-entrypoint"] }
thiserror = "1.0.38"
# The following dependencies are used for the client API and
# probably shouldn't be in WASM
chacha20poly1305 = { version = "0.10.1", optional = true }
darkfi = { path = "../../../", features = ["zk", "rpc", "blockchain"], optional = true }
halo2_proofs = { version = "0.2.0", optional = true }
log = { version = "0.4.17", optional = true }
rand = { version = "0.8.5", optional = true }
# These are used just for the integration tests
[dev-dependencies]
async-std = {version = "1.12.0", features = ["attributes"]}
darkfi = {path = "../../../", features = ["tx", "blockchain"]}
darkfi-money-contract = { path = "../money", features = ["client", "no-entrypoint"] }
simplelog = "0.12.0"
sled = "0.34.7"
# We need to disable random using "custom" which makes the crate a noop
# so the wasm32-unknown-unknown target is enabled.
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.8", features = ["custom"] }
[features]
default = []
no-entrypoint = []
client = [
"darkfi",
"darkfi-money-contract/client",
"darkfi-money-contract/no-entrypoint",
"rand",
"chacha20poly1305",
"log",
"halo2_proofs",
]

View File

@@ -0,0 +1,41 @@
.POSIX:
# Cargo binary
CARGO = cargo
# zkas compiler binary
ZKAS = ../../../zkas
# zkas circuits
PROOFS_SRC = $(shell find proof -type f -name '*.zk')
PROOFS_BIN = $(PROOFS_SRC:=.bin)
# wasm source files
WASM_SRC = \
$(shell find src -type f) \
$(shell find ../../sdk -type f) \
$(shell find ../../serial -type f)
# wasm contract binary
WASM_BIN = consensus_contract.wasm
all: $(WASM_BIN)
$(WASM_BIN): $(WASM_SRC) $(PROOFS_BIN)
$(CARGO) build --release --package darkfi-consensus-contract --target wasm32-unknown-unknown
cp -f ../../../target/wasm32-unknown-unknown/release/darkfi_consensus_contract.wasm $@
$(PROOFS_BIN): $(ZKAS) $(PROOFS_SRC)
$(ZKAS) $(basename $@) -o $@
test-stake-unstake: all
$(CARGO) test --release --features=no-entrypoint,client \
--package darkfi-consensus-contract \
--test stake_unstake
test: test-stake-unstake
clean:
rm -f $(PROOFS_BIN) $(WASM_BIN)
.PHONY: all test-stake-unstake test clean

View File

@@ -0,0 +1,29 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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/>.
*/
//! This module implements the client-side API for this contract's interaction.
//! What we basically do here is implement an API that creates the necessary
//! structures and is able to export them to create a DarkFi transaction
//! object that can be broadcasted to the network.
//!
//! Note that this API does not involve any wallet interaction, but only takes
//! the necessary objects provided by the caller. This is intentional, so we
//! are able to abstract away any wallet interfaces to client implementations.
/// `Consensus::StakeV1` API
pub mod stake_v1;

View File

@@ -0,0 +1,19 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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/>.
*/
//! This API is crufty. Please rework it into something nice to read and nice to use.

View File

@@ -0,0 +1,119 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 darkfi_sdk::{
crypto::ContractId,
db::{db_init, db_lookup, set_return_data, SMART_CONTRACT_ZKAS_DB_NAME},
error::{ContractError, ContractResult},
msg, ContractCall,
};
use darkfi_serial::deserialize;
use crate::{model::ConsensusStakeUpdateV1, ConsensusFunction};
/// `Consensus::Stake` functions
mod stake_v1;
use stake_v1::{
consensus_stake_get_metadata_v1, consensus_stake_process_instruction_v1,
consensus_stake_process_update_v1,
};
darkfi_sdk::define_contract!(
init: init_contract,
exec: process_instruction,
apply: process_update,
metadata: get_metadata
);
/// This entrypoint function runs when the contract is (re)deployed and initialized.
/// We use this function to initialize all the necessary databases and prepare them
/// with initial data if necessary. This is also the place where we bundle the zkas
/// circuits that are to be used with functions provided by the contract.
fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
// The zkas circuit can simply be embedded in the wasm and set up by
// the initialization. Note that the tree should then be called "zkas".
// The lookups can be done by `contract_id+_zkas+namespace`.
// TODO: For the zkas tree, external host checks should be done to ensure
// that the bincode is actually valid and not arbitrary.
let _zkas_db = match db_lookup(cid, SMART_CONTRACT_ZKAS_DB_NAME) {
Ok(v) => v,
Err(_) => db_init(cid, SMART_CONTRACT_ZKAS_DB_NAME)?,
};
// TODO: implement
Ok(())
}
/// This function is used by the wasm VM's host to fetch the necessary metadata
/// for verifying signatures and zk proofs. The payload given here are all the
/// contract calls in the transaction.
fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
if call_idx >= calls.len() as u32 {
msg!("Error: call_idx >= calls.len()");
return Err(ContractError::Internal)
}
match ConsensusFunction::try_from(calls[call_idx as usize].data[0])? {
ConsensusFunction::StakeV1 => {
// We pass everything into the correct function, and it will return
// the metadata for us, which we can then copy into the host with
// the `set_return_data` function. On the host, this metadata will
// be used to do external verification (zk proofs, and signatures).
let metadata = consensus_stake_get_metadata_v1(cid, call_idx, calls)?;
Ok(set_return_data(&metadata)?)
}
}
}
/// This function verifies a state transition and produces a state update
/// if everything is successful. This step should happen **after** the host
/// has successfully verified the metadata from `get_metadata()`.
fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
let (call_idx, calls): (u32, Vec<ContractCall>) = deserialize(ix)?;
if call_idx >= calls.len() as u32 {
msg!("Error: call_idx >= calls.len()");
return Err(ContractError::Internal)
}
match ConsensusFunction::try_from(calls[call_idx as usize].data[0])? {
ConsensusFunction::StakeV1 => {
// Again, we pass everything into the correct function.
// If it executes successfully, we'll get a state update
// which we can copy into the host using `set_return_data`.
// This update can then be written with `process_update()`
// if everything is in order.
let update_data = consensus_stake_process_instruction_v1(cid, call_idx, calls)?;
Ok(set_return_data(&update_data)?)
}
}
}
/// This function attempts to write a given state update provided the previous steps
/// of the contract call execution all were successful. It's the last in line, and
/// assumes that the transaction/call was successful. The payload given to the function
/// is the update data retrieved from `process_instruction()`.
fn process_update(cid: ContractId, update_data: &[u8]) -> ContractResult {
match ConsensusFunction::try_from(update_data[0])? {
ConsensusFunction::StakeV1 => {
let update: ConsensusStakeUpdateV1 = deserialize(&update_data[1..])?;
Ok(consensus_stake_process_update_v1(cid, update)?)
}
}
}

View File

@@ -0,0 +1,81 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 darkfi_sdk::{
crypto::{ContractId, PublicKey},
error::{ContractError, ContractResult},
pasta::pallas,
ContractCall,
};
use darkfi_serial::{deserialize, Encodable, WriteExt};
use crate::{
model::{ConsensusStakeParamsV1, ConsensusStakeUpdateV1},
ConsensusFunction,
};
/// `get_metadata` function for `Consensus::StakeV1`
pub(crate) fn consensus_stake_get_metadata_v1(
_cid: ContractId,
call_idx: u32,
calls: Vec<ContractCall>,
) -> Result<Vec<u8>, ContractError> {
let self_ = &calls[call_idx as usize];
let _params: ConsensusStakeParamsV1 = deserialize(&self_.data[1..])?;
// Public inputs for the ZK proofs we have to verify
let zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
// Public keys for the transaction signatures we have to verify
let signature_pubkeys: Vec<PublicKey> = vec![];
// TODO: implement
// Serialize everything gathered and return it
let mut metadata = vec![];
zk_public_inputs.encode(&mut metadata)?;
signature_pubkeys.encode(&mut metadata)?;
Ok(metadata)
}
/// `process_instruction` function for `Consensus::StakeV1`
pub(crate) fn consensus_stake_process_instruction_v1(
_cid: ContractId,
_call_idx: u32,
_calls: Vec<ContractCall>,
) -> Result<Vec<u8>, ContractError> {
// TODO: implement
// Create a state update.
let update = ConsensusStakeUpdateV1 {};
let mut update_data = vec![];
update_data.write_u8(ConsensusFunction::StakeV1 as u8)?;
update.encode(&mut update_data)?;
Ok(update_data)
}
/// `process_update` function for `Consensus::StakeV1`
pub(crate) fn consensus_stake_process_update_v1(
_cid: ContractId,
_update: ConsensusStakeUpdateV1,
) -> ContractResult {
// TODO: implement
Ok(())
}

View File

@@ -0,0 +1,33 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 darkfi_sdk::error::ContractError;
#[derive(Debug, Clone, thiserror::Error)]
pub enum ConsensusError {
#[error("Error")]
SomeError,
}
impl From<ConsensusError> for ContractError {
fn from(e: ConsensusError) -> Self {
match e {
ConsensusError::SomeError => Self::Custom(1),
}
}
}

View File

@@ -0,0 +1,60 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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/>.
*/
//! Smart contract implementing staking, unstaking and evolving
//! of consensus tokens.
//! Smart contract implementing money transfers, atomic swaps, token
//! minting and freezing, and staking/unstaking of consensus tokens.
use darkfi_sdk::error::ContractError;
/// Functions available in the contract
#[repr(u8)]
pub enum ConsensusFunction {
StakeV1 = 0x00,
//EvolveV1 = 0x01,
//UnstakeV1 = 0x02,
}
impl TryFrom<u8> for ConsensusFunction {
type Error = ContractError;
fn try_from(b: u8) -> core::result::Result<Self, Self::Error> {
match b {
0x00 => Ok(Self::StakeV1),
//0x01 => Ok(Self::EvolveV1),
//0x02 => Ok(Self::UnstakeV1),
_ => Err(ContractError::InvalidFunction),
}
}
}
/// Internal contract errors
pub mod error;
/// Call parameters definitions
pub mod model;
#[cfg(not(feature = "no-entrypoint"))]
/// WASM entrypoint functions
pub mod entrypoint;
#[cfg(feature = "client")]
/// Client API for interaction with this smart contract
pub mod client;

View File

@@ -0,0 +1,31 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 darkfi_serial::{SerialDecodable, SerialEncodable};
/// Parameters for `Consensus::Stake`
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
pub struct ConsensusStakeParamsV1 {
// TODO: implement
}
/// State update for `Consensus::Stake`
#[derive(Clone, Debug, SerialEncodable, SerialDecodable)]
pub struct ConsensusStakeUpdateV1 {
// TODO: implement
}

View File

@@ -0,0 +1,193 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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 std::collections::HashMap;
use darkfi::{
consensus::{
ValidatorState, ValidatorStatePtr, TESTNET_BOOTSTRAP_TIMESTAMP, TESTNET_GENESIS_HASH_BYTES,
TESTNET_GENESIS_TIMESTAMP, TESTNET_INITIAL_DISTRIBUTION,
},
tx::Transaction,
wallet::{WalletDb, WalletPtr},
zk::{empty_witnesses, halo2::Field, ProvingKey, ZkCircuit},
zkas::ZkBinary,
Result,
};
use darkfi_money_contract::client::OwnCoin;
use darkfi_sdk::{
crypto::{Keypair, MerkleTree, PublicKey, DARK_TOKEN_ID, MONEY_CONTRACT_ID},
db::SMART_CONTRACT_ZKAS_DB_NAME,
pasta::pallas,
ContractCall,
};
use darkfi_serial::{serialize, Encodable};
use log::warn;
use rand::rngs::OsRng;
use darkfi_money_contract::{
client::transfer_v1::TransferCallBuilder, model::MoneyTransferParamsV1, MoneyFunction,
MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1, MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1,
};
pub fn init_logger() {
let mut cfg = simplelog::ConfigBuilder::new();
cfg.add_filter_ignore("sled".to_string());
cfg.add_filter_ignore("blockchain::contractstore".to_string());
// We check this error so we can execute same file tests in parallel,
// otherwise second one fails to init logger here.
if let Err(_) = simplelog::TermLogger::init(
//simplelog::LevelFilter::Info,
simplelog::LevelFilter::Debug,
//simplelog::LevelFilter::Trace,
cfg.build(),
simplelog::TerminalMode::Mixed,
simplelog::ColorChoice::Auto,
) {
warn!(target: "money_harness", "Logger already initialized");
}
}
pub struct Wallet {
pub keypair: Keypair,
pub state: ValidatorStatePtr,
pub merkle_tree: MerkleTree,
pub wallet: WalletPtr,
pub coins: Vec<OwnCoin>,
pub spent_coins: Vec<OwnCoin>,
}
impl Wallet {
async fn new(keypair: Keypair, faucet_pubkeys: &[PublicKey]) -> Result<Self> {
let wallet = WalletDb::new("sqlite::memory:", "foo").await?;
let sled_db = sled::Config::new().temporary(true).open()?;
let state = ValidatorState::new(
&sled_db,
*TESTNET_BOOTSTRAP_TIMESTAMP,
*TESTNET_GENESIS_TIMESTAMP,
*TESTNET_GENESIS_HASH_BYTES,
*TESTNET_INITIAL_DISTRIBUTION,
wallet.clone(),
faucet_pubkeys.to_vec(),
false,
false,
)
.await?;
let merkle_tree = MerkleTree::new(100);
let coins = vec![];
let spent_coins = vec![];
Ok(Self { keypair, state, merkle_tree, wallet, coins, spent_coins })
}
}
pub struct ConsensusTestHarness {
pub faucet: Wallet,
pub alice: Wallet,
pub bob: Wallet,
pub charlie: Wallet,
pub proving_keys: HashMap<&'static str, (ProvingKey, ZkBinary)>,
}
impl ConsensusTestHarness {
pub async fn new() -> Result<Self> {
let faucet_kp = Keypair::random(&mut OsRng);
let faucet_pubkeys = vec![faucet_kp.public];
let faucet = Wallet::new(faucet_kp, &faucet_pubkeys).await?;
let alice_kp = Keypair::random(&mut OsRng);
let alice = Wallet::new(alice_kp, &faucet_pubkeys).await?;
let bob_kp = Keypair::random(&mut OsRng);
let bob = Wallet::new(bob_kp, &faucet_pubkeys).await?;
let charlie_kp = Keypair::random(&mut OsRng);
let charlie = Wallet::new(charlie_kp, &faucet_pubkeys).await?;
// Get the zkas circuits and build proving keys
let mut proving_keys = HashMap::new();
let alice_sled = alice.state.read().await.blockchain.sled_db.clone();
let db_handle = alice.state.read().await.blockchain.contracts.lookup(
&alice_sled,
&MONEY_CONTRACT_ID,
SMART_CONTRACT_ZKAS_DB_NAME,
)?;
macro_rules! mkpk {
($ns:expr) => {
let zkbin = db_handle.get(&serialize(&$ns))?.unwrap();
let zkbin = ZkBinary::decode(&zkbin)?;
let witnesses = empty_witnesses(&zkbin);
let circuit = ZkCircuit::new(witnesses, zkbin.clone());
let pk = ProvingKey::build(13, &circuit);
proving_keys.insert($ns, (pk, zkbin));
};
}
mkpk!(MONEY_CONTRACT_ZKAS_MINT_NS_V1);
mkpk!(MONEY_CONTRACT_ZKAS_BURN_NS_V1);
mkpk!(MONEY_CONTRACT_ZKAS_TOKEN_MINT_NS_V1);
mkpk!(MONEY_CONTRACT_ZKAS_TOKEN_FRZ_NS_V1);
Ok(Self { faucet, alice, bob, charlie, proving_keys })
}
pub fn airdrop_native(
&self,
value: u64,
recipient: PublicKey,
) -> Result<(Transaction, MoneyTransferParamsV1)> {
let (mint_pk, mint_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_MINT_NS_V1).unwrap();
let (burn_pk, burn_zkbin) = self.proving_keys.get(&MONEY_CONTRACT_ZKAS_BURN_NS_V1).unwrap();
let builder = TransferCallBuilder {
keypair: self.faucet.keypair,
recipient,
value,
token_id: *DARK_TOKEN_ID,
rcpt_spend_hook: pallas::Base::zero(),
rcpt_user_data: pallas::Base::zero(),
rcpt_user_data_blind: pallas::Base::random(&mut OsRng),
change_spend_hook: pallas::Base::zero(),
change_user_data: pallas::Base::zero(),
change_user_data_blind: pallas::Base::random(&mut OsRng),
coins: vec![],
tree: self.faucet.merkle_tree.clone(),
mint_zkbin: mint_zkbin.clone(),
mint_pk: mint_pk.clone(),
burn_zkbin: burn_zkbin.clone(),
burn_pk: burn_pk.clone(),
clear_input: true,
};
let debris = builder.build()?;
let mut data = vec![MoneyFunction::TransferV1 as u8];
debris.params.encode(&mut data)?;
let calls = vec![ContractCall { contract_id: *MONEY_CONTRACT_ID, data }];
let proofs = vec![debris.proofs];
let mut tx = Transaction { calls, proofs, signatures: vec![] };
let sigs = tx.create_sigs(&mut OsRng, &debris.signature_secrets)?;
tx.signatures = vec![sigs];
Ok((tx, debris.params))
}
}

View File

@@ -0,0 +1,78 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 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/>.
*/
//! Integration test of consensus staking and unstaking for Alice.
//!
//! We first airdrop Alica native tokes, and then she can stake and unstake
//! them a couple of times.
//!
//! With this test, we want to confirm the consensus contract state
//! transitions work for a single party and are able to be verified.
//!
//! TODO: Malicious cases
use darkfi::Result;
use darkfi_sdk::crypto::{merkle_prelude::*, poseidon_hash, Coin, MerkleNode, Nullifier};
use log::info;
use darkfi_money_contract::client::{MoneyNote, OwnCoin};
mod harness;
use harness::{init_logger, ConsensusTestHarness};
#[async_std::test]
async fn consensus_contract_stake_unstake() -> Result<()> {
init_logger();
const ALICE_AIRDROP: u64 = 1000;
// Initialize harness
let mut th = ConsensusTestHarness::new().await?;
info!(target: "money", "[Faucet] ===================================================");
info!(target: "money", "[Faucet] Building Money::Transfer params for Alice's airdrop");
info!(target: "money", "[Faucet] ===================================================");
let (airdrop_tx, airdrop_params) = th.airdrop_native(ALICE_AIRDROP, th.alice.keypair.public)?;
info!(target: "money", "[Faucet] ==========================");
info!(target: "money", "[Faucet] Executing Alice airdrop tx");
info!(target: "money", "[Faucet] ==========================");
th.faucet.state.read().await.verify_transactions(&[airdrop_tx.clone()], true).await?;
th.faucet.merkle_tree.append(&MerkleNode::from(airdrop_params.outputs[0].coin.inner()));
info!(target: "money", "[Alice] ==========================");
info!(target: "money", "[Alice] Executing Alice airdrop tx");
info!(target: "money", "[Alice] ==========================");
th.alice.state.read().await.verify_transactions(&[airdrop_tx.clone()], true).await?;
th.alice.merkle_tree.append(&MerkleNode::from(airdrop_params.outputs[0].coin.inner()));
assert!(th.faucet.merkle_tree.root(0).unwrap() == th.alice.merkle_tree.root(0).unwrap());
// Gather new owncoins
let mut owncoins = vec![];
let leaf_position = th.alice.merkle_tree.witness().unwrap();
let note: MoneyNote = airdrop_params.outputs[0].note.decrypt(&th.alice.keypair.secret)?;
owncoins.push(OwnCoin {
coin: Coin::from(airdrop_params.outputs[0].coin),
note: note.clone(),
secret: th.alice.keypair.secret,
nullifier: Nullifier::from(poseidon_hash([th.alice.keypair.secret.inner(), note.serial])),
leaf_position,
});
// Thanks for reading
Ok(())
}

View File

@@ -36,6 +36,10 @@ lazy_static! {
/// Contract ID for the native DAO contract
pub static ref DAO_CONTRACT_ID: ContractId =
ContractId::from(poseidon_hash([pallas::Base::zero(), pallas::Base::from(1)]));
/// Contract ID for the native Consensus contract
pub static ref CONSENSUS_CONTRACT_ID: ContractId =
ContractId::from(poseidon_hash([pallas::Base::zero(), pallas::Base::from(2)]));
}
/// ContractId represents an on-chain identifier for a certain smart contract.