Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2,128 changes: 1,014 additions & 1,114 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ BRIDGE_MANIFEST := $(MAKEFILE_DIR)/contracts/satoshi-bridge/Cargo.toml

RFLAGS="-C link-arg=-s"

FEATURES = bitcoin zcash
FEATURES = bitcoin zcash dash

release: $(addprefix build-,$(FEATURES))
$(call build_release_wasm,nbtc,nbtc)
Expand Down
7 changes: 5 additions & 2 deletions contracts/satoshi-bridge/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ container_build_command = ["cargo", "near", "build", "non-reproducible-wasm", "-
[package.metadata.near.reproducible_build.variant.zcash]
container_build_command = ["cargo", "near", "build", "non-reproducible-wasm", "--locked", "--no-abi", "--no-default-features", "--features", "zcash"]

[package.metadata.near.reproducible_build.variant.dash]
container_build_command = ["cargo", "near", "build", "non-reproducible-wasm", "--locked", "--no-abi", "--no-default-features", "--features", "dash"]

[dependencies]
near-sdk.workspace = true
near-contract-standards.workspace = true
Expand All @@ -44,12 +47,11 @@ sapling-crypto = { version = "0.5.0", default-features = false, optional = true
zcash_protocol = { version = "0.6.1" }
core2 = { version = "0.3", optional = true }
zcash_address = { version = "0.9.0" }

[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.12", features = ["custom"] }

[dev-dependencies]
near-workspaces = { version = "0.20", features = ["unstable"] }
near-workspaces = { version = "0.22", features = ["unstable"] }
tokio = { version = "1.12.0", features = ["full"] }
rand = "0.8"
hex = "0.4"
Expand All @@ -59,3 +61,4 @@ bs58 = "0.5"
default = []
zcash = ["zcash_primitives", "zcash_transparent", "orchard", "core2", "sapling-crypto"]
bitcoin = []
dash = []
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use crate::{
env, near, require, serde_json, BTCPendingInfo, Contract, ContractExt, Gas, Promise,
PromiseOrValue, MAX_BOOL_RESULT,
};
use super::assert_verification_succeeded;
use crate::{env, near, BTCPendingInfo, Contract, ContractExt, Gas, Promise, PromiseOrValue};

pub const GAS_FOR_VERIFY_ACTIVE_UTXO_MANAGEMENT_CALL_BACK: Gas = Gas::from_tgas(50);

impl Contract {
#[allow(unused_variables)]
pub fn internal_verify_active_utxo_management(
&self,
tx_id: String,
Expand All @@ -16,15 +15,24 @@ impl Contract {
) -> Promise {
let config = self.internal_config();
let confirmations = self.get_confirmations(config, btc_pending_info.actual_received_amount);
self.verify_transaction_inclusion_promise(

#[cfg(not(feature = "dash"))]
let verify_promise = self.verify_transaction_inclusion_promise(
config.btc_light_client_account_id.clone(),
tx_id.clone(),
tx_block_blockhash,
tx_index,
merkle_proof,
confirmations,
)
.then(
);

#[cfg(feature = "dash")]
let verify_promise = {
let signed_tx_id = btc_pending_info.get_signed_tx_id();
self.verify_transaction_via_mpc(signed_tx_id, confirmations)
};

verify_promise.then(
Self::ext(env::current_account_id())
.with_static_gas(GAS_FOR_VERIFY_ACTIVE_UTXO_MANAGEMENT_CALL_BACK)
.verify_active_utxo_management_callback(tx_id),
Expand All @@ -39,11 +47,7 @@ impl Contract {
&mut self,
tx_id: String,
) -> PromiseOrValue<bool> {
let result_bytes = env::promise_result_checked(0, MAX_BOOL_RESULT)
.expect("Call verify_transaction_inclusion failed");
let is_valid = serde_json::from_slice::<bool>(&result_bytes)
.expect("verify_transaction_inclusion return not bool");
require!(is_valid, "verify_transaction_inclusion return false");
assert_verification_succeeded();
self.internal_unwrap_btc_pending_info(&tx_id)
.assert_pending_verify();
self.internal_unwrap_mut_btc_pending_info(&tx_id)
Expand Down
36 changes: 19 additions & 17 deletions contracts/satoshi-bridge/src/btc_light_client/deposit.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
use near_sdk::serde_json::Value;

use super::assert_verification_succeeded;
use crate::{
burn::GAS_FOR_BURN_CALL,
env, ext_nbtc,
mint::{GAS_FOR_MINT_CALL, GAS_FOR_MINT_CALL_BACK},
near, require, serde_json, AccountId, Contract, ContractExt, DepositMsg, Event, Gas, NearToken,
PendingUTXOInfo, PostAction, Promise, PromiseOrValue, SafeDepositMsg, MAX_BOOL_RESULT,
PendingUTXOInfo, PostAction, Promise, PromiseOrValue, SafeDepositMsg,
MAX_FT_TRANSFER_CALL_RESULT, U128,
};

pub const GAS_FOR_VERIFY_DEPOSIT_CALL_BACK: Gas = Gas::from_tgas(190);
pub const GAS_FOR_UNAVAILABLE_UTXO_CALL_BACK: Gas = Gas::from_tgas(20);

impl Contract {
#[allow(unused_variables)]
pub(crate) fn internal_verify_deposit(
&mut self,
deposit_amount: u128,
Expand All @@ -29,6 +31,8 @@ impl Contract {
} else {
self.get_extra_msg_confirmations(config, deposit_amount)
};

#[cfg(not(feature = "dash"))]
let promise = self.verify_transaction_inclusion_promise(
config.btc_light_client_account_id.clone(),
pending_utxo_info.tx_id.clone(),
Expand All @@ -38,6 +42,10 @@ impl Contract {
confirmations,
);

#[cfg(feature = "dash")]
let promise =
self.verify_transaction_via_mpc(pending_utxo_info.tx_id.clone(), confirmations);

if deposit_amount < config.min_deposit_amount {
promise.then(
Self::ext(env::current_account_id())
Expand Down Expand Up @@ -67,7 +75,7 @@ impl Contract {
}
}

#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_arguments, unused_variables)]
pub(crate) fn internal_safe_verify_deposit(
&mut self,
deposit_amount: u128,
Expand All @@ -80,6 +88,8 @@ impl Contract {
) -> Promise {
let config = self.internal_config();
let confirmations = self.get_confirmations(config, deposit_amount);

#[cfg(not(feature = "dash"))]
let promise = self.verify_transaction_inclusion_promise(
config.btc_light_client_account_id.clone(),
pending_utxo_info.tx_id.clone(),
Expand All @@ -89,6 +99,10 @@ impl Contract {
confirmations,
);

#[cfg(feature = "dash")]
let promise =
self.verify_transaction_via_mpc(pending_utxo_info.tx_id.clone(), confirmations);

if deposit_amount < config.min_deposit_amount {
promise.then(
Self::ext(env::current_account_id())
Expand Down Expand Up @@ -118,11 +132,7 @@ impl Contract {
recipient_id: AccountId,
pending_utxo_info: PendingUTXOInfo,
) -> PromiseOrValue<bool> {
let result_bytes = env::promise_result_checked(0, MAX_BOOL_RESULT)
.expect("Call verify_transaction_inclusion failed");
let is_valid = serde_json::from_slice::<bool>(&result_bytes)
.expect("verify_transaction_inclusion return not bool");
require!(is_valid, "verify_transaction_inclusion return false");
assert_verification_succeeded();
require!(
self.data_mut()
.verified_deposit_utxo
Expand Down Expand Up @@ -153,11 +163,7 @@ impl Contract {
pending_utxo_info: PendingUTXOInfo,
post_actions: Option<Vec<PostAction>>,
) -> PromiseOrValue<bool> {
let result_bytes = env::promise_result_checked(0, MAX_BOOL_RESULT)
.expect("Call verify_transaction_inclusion failed");
let is_valid = serde_json::from_slice::<bool>(&result_bytes)
.expect("verify_transaction_inclusion return not bool");
require!(is_valid, "verify_transaction_inclusion return false");
assert_verification_succeeded();
require!(
self.data_mut()
.verified_deposit_utxo
Expand All @@ -183,11 +189,7 @@ impl Contract {
msg: String,
pending_utxo_info: PendingUTXOInfo,
) -> PromiseOrValue<bool> {
let result_bytes = env::promise_result_checked(0, MAX_BOOL_RESULT)
.expect("Call verify_transaction_inclusion failed");
let is_valid = serde_json::from_slice::<bool>(&result_bytes)
.expect("verify_transaction_inclusion return not bool");
require!(is_valid, "verify_transaction_inclusion return false");
assert_verification_succeeded();
require!(
self.data_mut()
.verified_deposit_utxo
Expand Down
26 changes: 26 additions & 0 deletions contracts/satoshi-bridge/src/btc_light_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,37 @@ use near_sdk::serde::{
};
use std::{fmt, str::FromStr};

#[cfg(not(feature = "dash"))]
use crate::require;
use crate::{env, ext_contract, near, AccountId, Contract, Gas, Promise};
pub mod active_utxo_management;
pub mod deposit;
pub mod withdraw;

#[cfg(not(feature = "dash"))]
pub(crate) const MAX_VERIFY_RESULT: usize = crate::MAX_BOOL_RESULT;
#[cfg(feature = "dash")]
pub(crate) const MAX_VERIFY_RESULT: usize = crate::MAX_MPC_VERIFY_RESULT;

pub(crate) fn assert_verification_succeeded() {
let result_bytes =
env::promise_result_checked(0, MAX_VERIFY_RESULT).expect("Call verify_transaction failed");

#[cfg(not(feature = "dash"))]
{
let is_valid = crate::serde_json::from_slice::<bool>(&result_bytes)
.expect("verify_transaction_inclusion return not bool");
require!(is_valid, "verify_transaction_inclusion return false");
}

#[cfg(feature = "dash")]
{
let _response: crate::mpc_verifier::VerifyForeignTransactionResponse =
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to parse response properly, I left it as todo for now

crate::serde_json::from_slice(&result_bytes)
.expect("ERR_MPC_VERIFY: failed to deserialize response");
}
}

pub const GAS_FOR_VERIFY_TRANSACTION_INCLUSION: Gas = Gas::from_tgas(10);
pub const GAS_FOR_GET_LAST_BLOCK_HEIGHT: Gas = Gas::from_tgas(3);
#[near(serializers = [borsh])]
Expand Down
31 changes: 19 additions & 12 deletions contracts/satoshi-bridge/src/btc_light_client/withdraw.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use crate::{
env, near, require, serde_json, BTCPendingInfo, Contract, ContractExt, Gas, Promise,
PromiseOrValue, MAX_BOOL_RESULT,
};
use super::assert_verification_succeeded;
use crate::{env, near, BTCPendingInfo, Contract, ContractExt, Gas, Promise, PromiseOrValue};

pub const GAS_FOR_VERIFY_WITHDRAW_CALL_BACK: Gas = Gas::from_tgas(50);
pub const GAS_FOR_VERIFY_CANCEL_WITHDRAW_CALL_BACK: Gas = Gas::from_tgas(50);

impl Contract {
#[allow(unused_variables)]
pub fn internal_verify_withdraw(
&self,
tx_id: String,
Expand All @@ -17,15 +16,27 @@ impl Contract {
) -> Promise {
let config = self.internal_config();
let confirmations = self.get_confirmations(config, btc_pending_info.actual_received_amount);
self.verify_transaction_inclusion_promise(

#[cfg(not(feature = "dash"))]
let verify_promise = self.verify_transaction_inclusion_promise(
config.btc_light_client_account_id.clone(),
tx_id.clone(),
tx_block_blockhash,
tx_index,
merkle_proof,
confirmations,
)
.then(
);

// For DASH (legacy P2PKH), the on-chain txid differs from the unsigned pending ID
// because script_sig is included in the txid hash. Compute the actual signed txid
// from tx_bytes_with_sign for the MPC verification query.
#[cfg(feature = "dash")]
let verify_promise = {
let signed_tx_id = btc_pending_info.get_signed_tx_id();
self.verify_transaction_via_mpc(signed_tx_id, confirmations)
};

verify_promise.then(
Self::ext(env::current_account_id())
.with_static_gas(GAS_FOR_VERIFY_CANCEL_WITHDRAW_CALL_BACK)
.internal_verify_withdraw_callback(tx_id),
Expand All @@ -37,11 +48,7 @@ impl Contract {
impl Contract {
#[private]
pub fn internal_verify_withdraw_callback(&mut self, tx_id: String) -> PromiseOrValue<bool> {
let result_bytes = env::promise_result_checked(0, MAX_BOOL_RESULT)
.expect("Call verify_transaction_inclusion failed");
let is_valid = serde_json::from_slice::<bool>(&result_bytes)
.expect("verify_transaction_inclusion return not bool");
require!(is_valid, "verify_transaction_inclusion return false");
assert_verification_succeeded();
self.internal_unwrap_btc_pending_info(&tx_id)
.assert_pending_verify();
self.internal_unwrap_mut_btc_pending_info(&tx_id)
Expand Down
10 changes: 10 additions & 0 deletions contracts/satoshi-bridge/src/btc_pending_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,16 @@ impl BTCPendingInfo {
PsbtWrapper::deserialize(&self.psbt_hex)
}

pub fn get_signed_tx_id(&self) -> String {
let tx_bytes = self
.tx_bytes_with_sign
.as_ref()
.expect("Missing tx_bytes_with_sign");
let tx: bitcoin::Transaction =
bitcoin::consensus::deserialize(tx_bytes).expect("Deserialization tx_bytes failed");
tx.compute_txid().to_string()
}

pub fn get_transaction(&self, chain: &network::Chain) -> WrappedTransaction {
WrappedTransaction::decode(
self.tx_bytes_with_sign
Expand Down
Loading