From 693568da48918bad4e08ad39e34c5eceb48f2b78 Mon Sep 17 00:00:00 2001 From: Artifex <dev@codx.io> Date: Mon, 20 May 2024 01:39:43 +0200 Subject: [PATCH] transfer: add bridge function This commit introduces a new bridge functionality to the transfer contract's state. This function enables any sequencer to register as a bridge provider upon staking a balance into the bridge. The authorized sequencer can then facilitate up to the amount of their stake in bridge operations. Adopting an optimistic approach, the bridge will process transactions without immediately verifying L1 associated events. However, it will keep track of these events and wait for several blocks before considering a transaction finalized. If a fraud proof is presented, demonstrating that a bridge operation was carried out without a valid L1 event, the responsible sequencer stands to lose all rewards and staked amount. In contrast, the individual who exposed the fraudulent activity will be rewarded with the ill-gotten amount. --- contracts/transfer-types/Cargo.toml | 1 + contracts/transfer-types/src/lib.rs | 23 +++ contracts/transfer/Cargo.toml | 1 + contracts/transfer/src/state.rs | 237 +++++++++++++++++++++++++++- 4 files changed, 261 insertions(+), 1 deletion(-) diff --git a/contracts/transfer-types/Cargo.toml b/contracts/transfer-types/Cargo.toml index 2e1a6d8916..ac8e4615a8 100644 --- a/contracts/transfer-types/Cargo.toml +++ b/contracts/transfer-types/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] dusk-bls12_381 = { version = "0.13", default-features = false, features = ["rkyv-impl"] } +bls12_381-bls = { version = "0.2", default-features = false, features = ["rkyv-impl"] } dusk-jubjub = { version = "0.14", default-features = false, features = ["rkyv-impl"] } dusk-poseidon = { version = "0.33", default-features = false, features = ["rkyv-impl", "alloc"] } phoenix-core = { version = "0.26", default-features = false, features = ["rkyv-impl", "alloc"] } diff --git a/contracts/transfer-types/src/lib.rs b/contracts/transfer-types/src/lib.rs index 28e8e34e03..06186c6fb6 100644 --- a/contracts/transfer-types/src/lib.rs +++ b/contracts/transfer-types/src/lib.rs @@ -14,6 +14,7 @@ extern crate alloc; use alloc::vec::Vec; use dusk_bls12_381::BlsScalar; +use bls12_381_bls::PublicKey; use bytecheck::CheckBytes; use phoenix_core::{Note, StealthAddress}; @@ -90,3 +91,25 @@ pub struct Mint { /// A nonce to prevent replay. pub nonce: BlsScalar, } + +/// Locked staked amount for the bridge. +#[derive(Debug, Clone, Archive, Deserialize, Serialize)] +#[archive_attr(derive(CheckBytes))] +pub struct Bridge { + /// Address of the sequencer that performed the bridge operation. + pub sequencer: PublicKey, + /// Benefitiary of the bridge operation. + pub receiver: PublicKey, + /// Block number of the operation. + pub block: u64, + /// Fee paid to the sequencer. + pub fee: u64, + /// Value to be minted for the receiver. + pub value: u64, + /// Block number that contains the event. + pub event_block: u64, + /// Bridge event on L1. + pub event: Vec<u8>, + /// Proof of validity of the sequencer authorship. + pub proof: Vec<u8>, +} diff --git a/contracts/transfer/Cargo.toml b/contracts/transfer/Cargo.toml index 5c4b7d0ce9..2899e7f970 100644 --- a/contracts/transfer/Cargo.toml +++ b/contracts/transfer/Cargo.toml @@ -8,6 +8,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] dusk-bls12_381 = { version = "0.13", default-features = false, features = ["rkyv-impl"] } +bls12_381-bls = { version = "0.2", default-features = false, features = ["rkyv-impl"] } dusk-bytes = "0.1" dusk-jubjub = { version = "0.14", default-features = false, features = ["rkyv-impl"] } jubjub-schnorr = { version = "0.2", default-features = false, features = ["rkyv-impl"] } diff --git a/contracts/transfer/src/state.rs b/contracts/transfer/src/state.rs index 825a2759e8..ae7f0cdf82 100644 --- a/contracts/transfer/src/state.rs +++ b/contracts/transfer/src/state.rs @@ -12,6 +12,7 @@ use alloc::collections::btree_map::Entry; use alloc::collections::{BTreeMap, BTreeSet}; use alloc::vec::Vec; +use bls12_381_bls::PublicKey; use dusk_bls12_381::BlsScalar; use dusk_bytes::DeserializableSlice; use dusk_jubjub::JubJubAffine; @@ -22,7 +23,7 @@ use ringbuffer::{ConstGenericRingBuffer, RingBuffer}; use rusk_abi::{ ContractError, ContractId, PaymentInfo, PublicInput, STAKE_CONTRACT, }; -use transfer_contract_types::{Mint, Stct, Wfct, Wfctc}; +use transfer_contract_types::{Bridge, Mint, Stct, Wfct, Wfctc}; /// Arity of the transfer tree. pub const A: usize = 4; @@ -35,6 +36,11 @@ pub struct TransferState { nullifiers: BTreeSet<BlsScalar>, roots: ConstGenericRingBuffer<BlsScalar, MAX_ROOTS>, balances: BTreeMap<ContractId, u64>, + bridge_staked: BTreeMap<[u8; PublicKey::SIZE], u64>, + bridge: BTreeMap<[u8; PublicKey::SIZE], Vec<Bridge>>, + bridge_rewards: BTreeMap<[u8; PublicKey::SIZE], u64>, + bridge_blocks_delay: u64, + bridge_standard_locked_amount: u64, var_crossover: Option<Crossover>, var_crossover_addr: Option<StealthAddress>, } @@ -169,6 +175,235 @@ impl TransferState { true } + /// Adds the specified value to the staked amount for bridging, utilizing + /// the given sequencer public key. The proof will mirror that of a + /// "send to contract transparent", asserting the consumption of a + /// Phoenix Note value to augment the staked balance. + pub fn bridge_stake( + &mut self, + sequencer: PublicKey, + value: u64, + proof: Vec<u8>, + ) { + let mut pi = Vec::with_capacity(3); + let balance; + match self.bridge_staked.entry(sequencer) { + Entry::Vacant(ve) => { + balance = value; + ve.insert(value); + } + Entry::Occupied(mut oe) => { + let v = oe.get_mut(); + balance = value + *v; + *v += value; + } + } + + let vd = todo!(); + Self::assert_proof(vd, proof, pi) + .expect("Failed to verify the provided proof!"); + } + + pub fn bridge( + &mut self, + sequencer: PublicKey, + receiver: PublicKey, + fee: u64, + value: u64, + event: Vec<u8>, + proof: Vec<u8>, + ) { + let mut pi = Vec::with_capacity(3); + + if value == 0 { + panic!("The bridged value is invalid."); + } + + if value < fee { + panic!("The bridged value is not enough to pay the sequencer fee."); + } + + let balance = self + .bridge_staked + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + let mut rewards = fee + + self + .bridge_rewards + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + let bridged = self + .bridge + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + let bridged_adjusted = Vec::with_capacity(bridged.len() + 1); + let mut locked = self.bridge_standard_locked_amount; + + // Locked amount will expire after `self.bridge_blocks_delay` blocks. + // Such interval should be enough to assume a fraud proof is + // inexistent. + for bridge in bridged { + if bridge.block + self.bridge_blocks_delay + < rusk_abi::block_height() + { + // the bridge operation is still locked under the delay. + locked += bridge.value; + bridged_adjusted.push(bridge); + } else { + // the bridge operation outlived the delay; remove from vector & + // add fee reward + rewards += bridge.fee; + } + } + + let balance = balance.saturating_sub(locked); + if balance < value { + panic!("The sequencer does not have enough balance to perform this operation."); + } + + self.bridge.insert(sequencer.to_bytes(), bridged_adjusted); + self.bridge_rewards.insert(sequencer.to_bytes(), rewards); + self.push_note_current_height(todo!( + "receiver note with `value - fee`" + )); + + let vd = todo!(); + Self::assert_proof(vd, proof, pi) + .expect("Failed to verify the provided proof!"); + } + + pub fn bridge_withdraw_rewards( + &mut self, + sequencer: PublicKey, + proof: Vec<u8>, + ) { + let mut pi = Vec::with_capacity(3); + + let mut rewards = self + .bridge_rewards + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + + let bridged = self + .bridge + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + let bridged_adjusted = Vec::with_capacity(bridged.len() + 1); + + for bridge in bridged { + if bridge.block + self.bridge_blocks_delay + < rusk_abi::block_height() + { + bridged_adjusted.push(bridge); + } else { + rewards += bridge.fee; + } + } + + self.bridge_rewards.insert(sequencer.to_bytes(), 0); + self.bridge.insert(sequencer.to_bytes(), bridged_adjusted); + self.push_note_current_height(todo!("sequencer rewards note")); + + let vd = todo!(); + Self::assert_proof(vd, proof, pi) + .expect("Failed to verify the provided proof!"); + } + + pub fn bridge_withdraw_stake( + &mut self, + sequencer: PublicKey, + proof: Vec<u8>, + ) { + let mut pi = Vec::with_capacity(3); + + let mut rewards = self + .bridge_rewards + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + + let bridged = self + .bridge + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + + let bridged_adjusted = Vec::with_capacity(bridged.len() + 1); + + for bridge in bridged { + if bridge.block + self.bridge_blocks_delay + < rusk_abi::block_height() + { + bridged_adjusted.push(bridge); + } else { + rewards += bridge.fee; + } + } + + if !bridged_adjusted.is_empty() { + panic!("the sequencer is not allowed to withdraw stake white bridged operations are locked."); + } + + let balance = self + .bridge_staked + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + + self.bridge_rewards.insert(sequencer.to_bytes(), 0); + self.bridge_staked.insert(sequencer.to_bytes(), 0); + self.bridge.insert(sequencer.to_bytes(), bridged_adjusted); + self.push_note_current_height(todo!( + "sequencer balance + rewards note" + )); + + let vd = todo!(); + Self::assert_proof(vd, proof, pi) + .expect("Failed to verify the provided proof!"); + } + + /// This function accepts a proof that one bridge operation was performed + /// without a corresponding valid L1 event. + /// + /// The sequencer proven to have performed a fraud will lose all his rewards + /// and stake to the prover. + pub fn bridge_fraud( + &mut self, + sequencer: PublicKey, + prover: PublicKey, + operation: Bridge, + proof: Vec<u8>, + ) { + let mut pi = Vec::with_capacity(3); + + let rewards = self + .bridge_rewards + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + + let balance = self + .bridge_staked + .get(sequencer.to_bytes()) + .copied() + .unwrap_or_default(); + + self.push_note_current_height(todo!("prover balance + rewards note")); + + self.bridge_rewards.insert(sequencer.to_bytes(), 0); + self.bridge_staked.insert(sequencer.to_bytes(), 0); + self.bridge.insert(sequencer.to_bytes(), Vec::new()); + + let vd = todo!(); + Self::assert_proof(vd, proof, pi) + .expect("Failed to verify the provided proof!"); + } + /// Spends the inputs and creates the given UTXO, and executes the contract /// call if present. It performs all checks necessary to ensure the /// transaction is valid - hash matches, anchor has been a root of the