Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contracts/satoshi-bridge/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "satoshi-bridge"
version = "0.7.5"
version = "0.8.0"
edition.workspace = true
publish.workspace = true
repository.workspace = true
Expand Down
60 changes: 42 additions & 18 deletions contracts/satoshi-bridge/src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,44 @@ pub struct OutstandingInfo {
#[derive(Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
pub struct Account {
pub account_id: AccountId,
pub btc_pending_sign_ids: HashSet<String>,
Comment thread
karim-en marked this conversation as resolved.
pub btc_pending_verify_list: HashSet<String>,
}

/// Old Account format (v0.7.5 and earlier) with single pending sign id.
#[near(serializers = [borsh])]
#[derive(Clone)]
pub struct AccountV0 {
pub account_id: AccountId,
pub btc_pending_sign_id: Option<String>,
pub btc_pending_verify_list: HashSet<String>,
}

impl From<AccountV0> for Account {
fn from(v: AccountV0) -> Self {
let mut btc_pending_sign_ids = HashSet::new();
if let Some(id) = v.btc_pending_sign_id {
btc_pending_sign_ids.insert(id);
}
Self {
account_id: v.account_id,
btc_pending_sign_ids,
btc_pending_verify_list: v.btc_pending_verify_list,
}
}
}

#[near(serializers = [borsh])]
pub enum VAccount {
V0(AccountV0),
Current(Account),
}

impl From<VAccount> for Account {
fn from(v: VAccount) -> Self {
match v {
VAccount::V0(c) => c.into(),
VAccount::Current(c) => c,
}
}
Expand All @@ -36,23 +61,22 @@ impl From<VAccount> for Account {
impl From<&VAccount> for Account {
fn from(v: &VAccount) -> Self {
match v {
VAccount::V0(c) => c.clone().into(),
VAccount::Current(c) => c.clone(),
}
}
}

impl<'a> From<&'a mut VAccount> for &'a mut Account {
fn from(v: &'a mut VAccount) -> Self {
match v {
VAccount::Current(c) => c,
// Lazy migrate V0 -> Current on first mutable access
if let VAccount::V0(old) = v {
let migrated: Account = old.clone().into();
*v = VAccount::Current(migrated);
}
}
}

impl<'a> From<&'a VAccount> for &'a Account {
fn from(v: &'a VAccount) -> Self {
match v {
VAccount::Current(c) => c,
_ => unreachable!(),
}
}
}
Expand All @@ -67,29 +91,29 @@ impl Account {
pub fn new(account_id: &AccountId) -> Self {
Self {
account_id: account_id.clone(),
btc_pending_sign_id: None,
btc_pending_sign_ids: HashSet::new(),
btc_pending_verify_list: HashSet::new(),
}
}

pub fn pending_sign_count(&self) -> u32 {
u32::try_from(self.btc_pending_sign_ids.len()).unwrap_or(u32::MAX)
}
}

impl Contract {
pub fn check_account_exists(&self, account_id: &AccountId) -> bool {
self.data().accounts.contains_key(account_id)
}

pub fn internal_get_account(&self, account_id: &AccountId) -> Option<&Account> {
self.data().accounts.get(account_id).map(Into::into)
pub fn internal_get_account(&self, account_id: &AccountId) -> Option<Account> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why we return cloned version now?

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.

I just decided to implement a lazy account migration. If we request mut, we upgrade the account to the new version. If it’s view, we convert it locally. Because of this approach, we can’t return a reference, since a conversion happens.

As an alternative, we could implement a separate migration function and migrate accounts in batches. But I kind of like the lazy approach more.

Comment thread
karim-en marked this conversation as resolved.
Outdated
self.data().accounts.get(account_id).map(Account::from)
}

pub fn internal_unwrap_account(&self, account_id: &AccountId) -> &Account {
self.data()
.accounts
.get(account_id)
.map(|o| o.into())
.unwrap_or_else(|| {
env::panic_str(&format!("ERR_ACCOUNT_NOT_REGISTERED: {}", account_id))
})
pub fn internal_unwrap_account(&self, account_id: &AccountId) -> Account {
Comment thread
karim-en marked this conversation as resolved.
Outdated
self.internal_get_account(account_id).unwrap_or_else(|| {
env::panic_str(&format!("ERR_ACCOUNT_NOT_REGISTERED: {}", account_id))
})
}

pub fn internal_unwrap_mut_account(&mut self, account_id: &AccountId) -> &mut Account {
Expand Down
21 changes: 11 additions & 10 deletions contracts/satoshi-bridge/src/api/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ impl Contract {
let account_id = env::predecessor_account_id();
require!(
self.internal_unwrap_account(&account_id)
.btc_pending_sign_id
.is_none(),
.btc_pending_sign_ids
.is_empty(),
Comment thread
frolvanya marked this conversation as resolved.
Outdated
"Previous btc tx has not been signed"
);

Expand Down Expand Up @@ -243,8 +243,8 @@ impl Contract {
.clone();
require!(
self.internal_unwrap_account(&user_account_id)
.btc_pending_sign_id
.is_none(),
.btc_pending_sign_ids
.is_empty(),
"Assisted user previous btc tx has not been signed"
);

Expand Down Expand Up @@ -331,8 +331,8 @@ impl Contract {
let account_id = env::predecessor_account_id();
require!(
self.internal_unwrap_account(&account_id)
.btc_pending_sign_id
.is_none(),
.btc_pending_sign_ids
.is_empty(),
"Previous btc tx has not been signed"
);
self.active_utxo_management_rbf_chain_specific(
Expand Down Expand Up @@ -364,8 +364,8 @@ impl Contract {
.clone();
require!(
self.internal_unwrap_account(&user_account_id)
.btc_pending_sign_id
.is_none(),
.btc_pending_sign_ids
.is_empty(),
"Assisted user previous btc tx has not been signed"
);
self.cancel_active_utxo_management_chain_specific(
Expand Down Expand Up @@ -434,7 +434,7 @@ impl Contract {
) {
let account = self.internal_unwrap_account(&account_id);
require!(
account.btc_pending_sign_id.is_none(),
account.btc_pending_sign_ids.is_empty(),
"Previous btc tx has not been signed"
);

Expand Down Expand Up @@ -480,7 +480,8 @@ impl Contract {
"pending info already exist"
);
self.internal_unwrap_mut_account(&account_id)
.btc_pending_sign_id = Some(btc_pending_id.clone());
.btc_pending_sign_ids
.insert(btc_pending_id.clone());
Event::UtxoRemoved { utxo_storage_keys }.emit();
Event::GenerateBtcPendingInfo {
account_id: &account_id,
Expand Down
12 changes: 6 additions & 6 deletions contracts/satoshi-bridge/src/api/chain_signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ impl Contract {
btc_pending_info.assert_pending_sign();
if let Some(original_tx_id) = btc_pending_info.get_original_tx_id() {
if !self.check_btc_pending_info_exists(original_tx_id) {
let clear_account_btc_pending_sign_id = self
.internal_unwrap_mut_account(&btc_pending_info.account_id.clone())
.btc_pending_sign_id
.take()
== Some(btc_pending_sign_id.clone());
require!(clear_account_btc_pending_sign_id, "Internal error");
require!(
self.internal_unwrap_mut_account(&btc_pending_info.account_id.clone())
.btc_pending_sign_ids
.remove(&btc_pending_sign_id),
"Internal error"
);
self.internal_remove_btc_pending_info(&btc_pending_sign_id);
return PromiseOrValue::Value(true);
}
Expand Down
34 changes: 34 additions & 0 deletions contracts/satoshi-bridge/src/api/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,32 @@ impl Contract {
}
}

#[payable]
#[access_control_any(roles(Role::DAO))]
pub fn extend_multi_txs_white_list(&mut self, account_ids: Vec<AccountId>) {
assert_one_yocto();
for account_id in account_ids {
let is_success = self
.data_mut()
.multi_txs_white_list
.insert(account_id.clone());
require!(
is_success,
format!("Already exist account_id: {}", account_id)
);
}
}

#[payable]
#[access_control_any(roles(Role::DAO))]
pub fn remove_multi_txs_white_list(&mut self, account_ids: Vec<AccountId>) {
assert_one_yocto();
for account_id in account_ids {
let is_success = self.data_mut().multi_txs_white_list.remove(&account_id);
require!(is_success, format!("Invalid account_id: {}", account_id));
}
}

#[payable]
#[access_control_any(roles(Role::DAO))]
pub fn extend_post_action_msg_templates(
Expand Down Expand Up @@ -449,6 +475,14 @@ impl Contract {
self.internal_mut_config().max_btc_tx_pending_sec = max_btc_tx_pending_sec;
}

#[payable]
#[access_control_any(roles(Role::DAO))]
pub fn set_max_pending_sign_txs(&mut self, max_pending_sign_txs: u32) {
assert_one_yocto();
require!(max_pending_sign_txs >= 1, "Invalid max_pending_sign_txs");
self.internal_mut_config().max_pending_sign_txs = max_pending_sign_txs;
}

#[payable]
#[access_control_any(roles(Role::DAO))]
pub fn set_unhealthy_utxo_amount(&mut self, unhealthy_utxo_amount: U64) {
Expand Down
15 changes: 10 additions & 5 deletions contracts/satoshi-bridge/src/api/token_receiver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,15 @@ impl Contract {
max_gas_fee: Option<U128>,
) {
let (utxo_storage_keys, vutxos) = self.generate_vutxos(&mut psbt);
let max_pending = if self.data().multi_txs_white_list.contains(&sender_id) {
self.internal_config().max_pending_sign_txs
} else {
1
};
let account = self.internal_unwrap_or_create_mut_account(&sender_id);
require!(
self.internal_unwrap_or_create_mut_account(&sender_id)
.btc_pending_sign_id
.is_none(),
"Previous btc tx has not been signed"
account.pending_sign_count() < max_pending,
"Too many pending sign transactions"
);

let withdraw_change_address_script_pubkey =
Expand Down Expand Up @@ -129,7 +133,8 @@ impl Contract {
"pending info already exist"
);
self.internal_unwrap_mut_account(&sender_id)
.btc_pending_sign_id = Some(btc_pending_id.clone());
.btc_pending_sign_ids
.insert(btc_pending_id.clone());
Event::UtxoRemoved { utxo_storage_keys }.emit();
Event::GenerateBtcPendingInfo {
account_id: &sender_id,
Expand Down
10 changes: 8 additions & 2 deletions contracts/satoshi-bridge/src/api/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct Metadata {
pub relayer_white_list: Vec<AccountId>,
pub extra_msg_relayer_white_list: Vec<AccountId>,
pub post_action_receiver_id_white_list: Vec<AccountId>,
pub multi_txs_white_list: Vec<AccountId>,
#[serde(with = "u128_dec_format")]
pub acc_collected_protocol_fee: u128,
#[serde(with = "u128_dec_format")]
Expand Down Expand Up @@ -57,6 +58,11 @@ impl Contract {
.iter()
.cloned()
.collect(),
multi_txs_white_list: root_state
.multi_txs_white_list
.iter()
.cloned()
.collect(),
acc_collected_protocol_fee: root_state.acc_collected_protocol_fee,
cur_available_protocol_fee: root_state.cur_available_protocol_fee,
acc_claimed_protocol_fee: root_state.acc_claimed_protocol_fee,
Expand All @@ -73,7 +79,7 @@ impl Contract {
}

pub fn get_account(&self, account_id: AccountId) -> Option<Account> {
self.internal_get_account(&account_id).cloned()
self.internal_get_account(&account_id)
}

pub fn list_accounts(
Expand All @@ -82,7 +88,7 @@ impl Contract {
) -> HashMap<AccountId, Option<Account>> {
account_ids
.into_iter()
.map(|key| (key.clone(), self.internal_get_account(&key).cloned()))
.map(|key| (key.clone(), self.internal_get_account(&key)))
.collect()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ macro_rules! define_rbf_method {
);

self.internal_unwrap_mut_account(&account_id)
.btc_pending_sign_id = Some(btc_pending_id.clone());
.btc_pending_sign_ids
.insert(btc_pending_id.clone());

Event::GenerateBtcPendingInfo {
account_id: &account_id,
Expand Down
2 changes: 1 addition & 1 deletion contracts/satoshi-bridge/src/btc_pending_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ impl Contract {

pub fn generate_btc_pending_sign_id(payload_preimages: &[Vec<u8>]) -> String {
let hash_bytes = env::sha256_array(
&payload_preimages
payload_preimages
.iter()
.flatten()
.copied()
Expand Down
7 changes: 4 additions & 3 deletions contracts/satoshi-bridge/src/chain_signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,10 @@ impl Contract {

let is_original_tx = btc_pending_info.get_original_tx_id().is_none();
let account = self.internal_unwrap_mut_account(&account_id);
let clear_account_btc_pending_sign_id =
account.btc_pending_sign_id.take() == Some(btc_pending_sign_id.clone());
require!(clear_account_btc_pending_sign_id, "Internal error");
require!(
account.btc_pending_sign_ids.remove(&btc_pending_sign_id),
"Internal error"
Comment thread
karim-en marked this conversation as resolved.
);
if is_original_tx {
account
.btc_pending_verify_list
Expand Down
7 changes: 7 additions & 0 deletions contracts/satoshi-bridge/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ use crate::{

pub const MAX_RATIO: u32 = 10000;

fn default_max_pending_sign_txs() -> u32 {
1
}

#[near(serializers = [borsh, json])]
#[derive(Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug))]
Expand Down Expand Up @@ -108,6 +112,9 @@ pub struct Config {
pub max_btc_tx_pending_sec: u32,
// UTXOs less than or equal to this amount are allowed to be merged through active management.
pub unhealthy_utxo_amount: u64,
// The maximum number of pending-sign transactions allowed per whitelisted account.
#[serde(default = "default_max_pending_sign_txs")]
pub max_pending_sign_txs: u32,
#[cfg(feature = "zcash")]
pub expiry_height_gap: u32,
}
Expand Down
Loading
Loading