Phase 1 - Test Writing (8 parallel agents): - synord: Added 133 unit tests for node, config, services - synor-rpc: Expanded to 207 tests for API endpoints - synor-bridge: Added 144 tests for transfer lifecycle - synor-zk: Added 279 tests for circuits, proofs, state - synor-mining: Added 147 tests for kHeavyHash, miner - synor-types: Added 146 tests for hash, amount, address - cross-crate: Created 75 integration tests - byzantine: Created 40+ fault scenario tests Key additions: - tests/cross_crate_integration.rs (new) - apps/synord/tests/byzantine_fault_tests.rs (new) - crates/synor-storage/src/cf.rs (new) - src/lib.rs for workspace integration tests Fixes during testing: - synor-compute: Added Default impl for GpuVariant - synor-bridge: Fixed replay protection in process_lock_event - synor-storage: Added cf module and database exports All 1,357 tests pass with 0 failures.
985 lines
27 KiB
Rust
985 lines
27 KiB
Rust
//! Bridge Vault
|
|
//!
|
|
//! Manages locked assets for cross-chain transfers.
|
|
//! Assets are locked in vaults on the source chain and
|
|
//! wrapped tokens are minted on the destination chain.
|
|
|
|
use crate::{AssetId, BridgeAddress, BridgeError, BridgeResult, ChainType};
|
|
use borsh::{BorshDeserialize, BorshSerialize};
|
|
use serde::{Deserialize, Serialize};
|
|
use sha2::{Digest, Sha256};
|
|
use std::collections::HashMap;
|
|
use std::fmt;
|
|
|
|
/// Unique vault identifier
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
|
pub struct VaultId(pub String);
|
|
|
|
impl VaultId {
|
|
/// Create a new vault ID
|
|
pub fn new(id: impl Into<String>) -> Self {
|
|
Self(id.into())
|
|
}
|
|
|
|
/// Generate vault ID from asset
|
|
pub fn from_asset(asset: &AssetId, chain: &ChainType) -> Self {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(chain.to_string().as_bytes());
|
|
hasher.update(asset.identifier.as_bytes());
|
|
Self(hex::encode(&hasher.finalize()[..16]))
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for VaultId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
/// Locked asset record
|
|
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
|
pub struct LockedAsset {
|
|
/// Asset being locked
|
|
pub asset: AssetId,
|
|
/// Amount locked
|
|
pub amount: u128,
|
|
/// Who locked the asset
|
|
pub owner: BridgeAddress,
|
|
/// Recipient on destination chain
|
|
pub recipient: BridgeAddress,
|
|
/// Lock timestamp (Unix seconds)
|
|
pub locked_at: u64,
|
|
/// Lock expiry (for timelock, 0 = no expiry)
|
|
pub expires_at: u64,
|
|
/// Transaction hash on source chain
|
|
pub lock_tx_hash: Option<Vec<u8>>,
|
|
/// Whether the asset has been released
|
|
pub released: bool,
|
|
}
|
|
|
|
impl LockedAsset {
|
|
/// Create a new locked asset record
|
|
pub fn new(
|
|
asset: AssetId,
|
|
amount: u128,
|
|
owner: BridgeAddress,
|
|
recipient: BridgeAddress,
|
|
locked_at: u64,
|
|
) -> Self {
|
|
Self {
|
|
asset,
|
|
amount,
|
|
owner,
|
|
recipient,
|
|
locked_at,
|
|
expires_at: 0,
|
|
lock_tx_hash: None,
|
|
released: false,
|
|
}
|
|
}
|
|
|
|
/// Set expiry time
|
|
pub fn with_expiry(mut self, expires_at: u64) -> Self {
|
|
self.expires_at = expires_at;
|
|
self
|
|
}
|
|
|
|
/// Set lock transaction hash
|
|
pub fn with_tx_hash(mut self, tx_hash: Vec<u8>) -> Self {
|
|
self.lock_tx_hash = Some(tx_hash);
|
|
self
|
|
}
|
|
|
|
/// Check if the lock has expired
|
|
pub fn is_expired(&self, current_time: u64) -> bool {
|
|
self.expires_at > 0 && current_time >= self.expires_at
|
|
}
|
|
|
|
/// Mark as released
|
|
pub fn release(&mut self) {
|
|
self.released = true;
|
|
}
|
|
}
|
|
|
|
/// Vault state
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum VaultState {
|
|
/// Vault is active and accepting deposits
|
|
Active,
|
|
/// Vault is paused (no new deposits)
|
|
Paused,
|
|
/// Vault is deprecated (migration needed)
|
|
Deprecated,
|
|
}
|
|
|
|
/// Asset vault for a specific chain
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Vault {
|
|
/// Vault identifier
|
|
pub id: VaultId,
|
|
/// Chain this vault is on
|
|
pub chain: ChainType,
|
|
/// Asset managed by this vault
|
|
pub asset: AssetId,
|
|
/// Current vault state
|
|
pub state: VaultState,
|
|
/// Total locked amount
|
|
pub total_locked: u128,
|
|
/// Individual locked assets by lock ID
|
|
locked_assets: HashMap<String, LockedAsset>,
|
|
/// Vault address (contract address for EVM)
|
|
pub vault_address: Option<BridgeAddress>,
|
|
/// Admin addresses
|
|
pub admins: Vec<BridgeAddress>,
|
|
/// Daily limit (0 = unlimited)
|
|
pub daily_limit: u128,
|
|
/// Today's usage
|
|
daily_usage: u128,
|
|
/// Last reset timestamp
|
|
last_reset: u64,
|
|
}
|
|
|
|
impl Vault {
|
|
/// Create a new vault
|
|
pub fn new(id: VaultId, chain: ChainType, asset: AssetId) -> Self {
|
|
Self {
|
|
id,
|
|
chain,
|
|
asset,
|
|
state: VaultState::Active,
|
|
total_locked: 0,
|
|
locked_assets: HashMap::new(),
|
|
vault_address: None,
|
|
admins: Vec::new(),
|
|
daily_limit: 0,
|
|
daily_usage: 0,
|
|
last_reset: 0,
|
|
}
|
|
}
|
|
|
|
/// Set vault address
|
|
pub fn with_address(mut self, address: BridgeAddress) -> Self {
|
|
self.vault_address = Some(address);
|
|
self
|
|
}
|
|
|
|
/// Set daily limit
|
|
pub fn with_daily_limit(mut self, limit: u128) -> Self {
|
|
self.daily_limit = limit;
|
|
self
|
|
}
|
|
|
|
/// Add admin
|
|
pub fn add_admin(&mut self, admin: BridgeAddress) {
|
|
if !self.admins.contains(&admin) {
|
|
self.admins.push(admin);
|
|
}
|
|
}
|
|
|
|
/// Lock assets in the vault
|
|
pub fn lock(
|
|
&mut self,
|
|
lock_id: impl Into<String>,
|
|
amount: u128,
|
|
owner: BridgeAddress,
|
|
recipient: BridgeAddress,
|
|
current_time: u64,
|
|
) -> BridgeResult<()> {
|
|
// Check vault state
|
|
if self.state != VaultState::Active {
|
|
return Err(BridgeError::BridgePaused);
|
|
}
|
|
|
|
// Check daily limit
|
|
self.check_daily_limit(amount, current_time)?;
|
|
|
|
let lock_id = lock_id.into();
|
|
if self.locked_assets.contains_key(&lock_id) {
|
|
return Err(BridgeError::TransferAlreadyExists(lock_id));
|
|
}
|
|
|
|
let locked = LockedAsset::new(
|
|
self.asset.clone(),
|
|
amount,
|
|
owner,
|
|
recipient,
|
|
current_time,
|
|
);
|
|
|
|
self.locked_assets.insert(lock_id, locked);
|
|
self.total_locked += amount;
|
|
self.daily_usage += amount;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Unlock assets from the vault
|
|
pub fn unlock(&mut self, lock_id: &str) -> BridgeResult<LockedAsset> {
|
|
let locked = self
|
|
.locked_assets
|
|
.get_mut(lock_id)
|
|
.ok_or_else(|| BridgeError::TransferNotFound(lock_id.to_string()))?;
|
|
|
|
if locked.released {
|
|
return Err(BridgeError::TransferAlreadyCompleted(lock_id.to_string()));
|
|
}
|
|
|
|
locked.release();
|
|
self.total_locked = self.total_locked.saturating_sub(locked.amount);
|
|
|
|
Ok(locked.clone())
|
|
}
|
|
|
|
/// Get locked asset
|
|
pub fn get_locked(&self, lock_id: &str) -> Option<&LockedAsset> {
|
|
self.locked_assets.get(lock_id)
|
|
}
|
|
|
|
/// Check and update daily limit
|
|
fn check_daily_limit(&mut self, amount: u128, current_time: u64) -> BridgeResult<()> {
|
|
if self.daily_limit == 0 {
|
|
return Ok(());
|
|
}
|
|
|
|
// Reset daily usage if new day
|
|
let day = current_time / 86400;
|
|
let last_day = self.last_reset / 86400;
|
|
if day > last_day {
|
|
self.daily_usage = 0;
|
|
self.last_reset = current_time;
|
|
}
|
|
|
|
// Check limit
|
|
if self.daily_usage + amount > self.daily_limit {
|
|
return Err(BridgeError::RateLimitExceeded);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Pause the vault
|
|
pub fn pause(&mut self) {
|
|
self.state = VaultState::Paused;
|
|
}
|
|
|
|
/// Resume the vault
|
|
pub fn resume(&mut self) {
|
|
self.state = VaultState::Active;
|
|
}
|
|
|
|
/// Deprecate the vault
|
|
pub fn deprecate(&mut self) {
|
|
self.state = VaultState::Deprecated;
|
|
}
|
|
|
|
/// Get all locked assets
|
|
pub fn all_locked(&self) -> impl Iterator<Item = (&String, &LockedAsset)> {
|
|
self.locked_assets.iter()
|
|
}
|
|
|
|
/// Get active (unreleased) locked assets
|
|
pub fn active_locked(&self) -> impl Iterator<Item = (&String, &LockedAsset)> {
|
|
self.locked_assets.iter().filter(|(_, l)| !l.released)
|
|
}
|
|
|
|
/// Get expired locked assets
|
|
pub fn expired_locked(&self, current_time: u64) -> impl Iterator<Item = (&String, &LockedAsset)> {
|
|
self.locked_assets
|
|
.iter()
|
|
.filter(move |(_, l)| !l.released && l.is_expired(current_time))
|
|
}
|
|
}
|
|
|
|
/// Vault manager for multiple vaults
|
|
pub struct VaultManager {
|
|
/// Vaults by ID
|
|
vaults: HashMap<VaultId, Vault>,
|
|
/// Vault lookup by (chain, asset)
|
|
by_chain_asset: HashMap<(ChainType, String), VaultId>,
|
|
}
|
|
|
|
impl VaultManager {
|
|
/// Create a new vault manager
|
|
pub fn new() -> Self {
|
|
Self {
|
|
vaults: HashMap::new(),
|
|
by_chain_asset: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Create and register a new vault
|
|
pub fn create_vault(&mut self, chain: ChainType, asset: AssetId) -> VaultId {
|
|
let vault_id = VaultId::from_asset(&asset, &chain);
|
|
|
|
let vault = Vault::new(vault_id.clone(), chain.clone(), asset.clone());
|
|
|
|
self.by_chain_asset
|
|
.insert((chain, asset.identifier.clone()), vault_id.clone());
|
|
self.vaults.insert(vault_id.clone(), vault);
|
|
|
|
vault_id
|
|
}
|
|
|
|
/// Get vault by ID
|
|
pub fn get_vault(&self, vault_id: &VaultId) -> Option<&Vault> {
|
|
self.vaults.get(vault_id)
|
|
}
|
|
|
|
/// Get mutable vault by ID
|
|
pub fn get_vault_mut(&mut self, vault_id: &VaultId) -> Option<&mut Vault> {
|
|
self.vaults.get_mut(vault_id)
|
|
}
|
|
|
|
/// Find vault by chain and asset
|
|
pub fn find_vault(&self, chain: &ChainType, asset: &AssetId) -> Option<&Vault> {
|
|
self.by_chain_asset
|
|
.get(&(chain.clone(), asset.identifier.clone()))
|
|
.and_then(|id| self.vaults.get(id))
|
|
}
|
|
|
|
/// Find mutable vault by chain and asset
|
|
pub fn find_vault_mut(&mut self, chain: &ChainType, asset: &AssetId) -> Option<&mut Vault> {
|
|
let vault_id = self
|
|
.by_chain_asset
|
|
.get(&(chain.clone(), asset.identifier.clone()))?
|
|
.clone();
|
|
self.vaults.get_mut(&vault_id)
|
|
}
|
|
|
|
/// Get or create vault for asset
|
|
pub fn get_or_create_vault(&mut self, chain: ChainType, asset: AssetId) -> &mut Vault {
|
|
let key = (chain.clone(), asset.identifier.clone());
|
|
if !self.by_chain_asset.contains_key(&key) {
|
|
self.create_vault(chain.clone(), asset.clone());
|
|
}
|
|
let vault_id = self.by_chain_asset.get(&key).unwrap().clone();
|
|
self.vaults.get_mut(&vault_id).unwrap()
|
|
}
|
|
|
|
/// Get total locked value across all vaults
|
|
pub fn total_locked(&self) -> u128 {
|
|
self.vaults.values().map(|v| v.total_locked).sum()
|
|
}
|
|
|
|
/// Get total locked value for an asset
|
|
pub fn total_locked_for_asset(&self, asset: &AssetId) -> u128 {
|
|
self.vaults
|
|
.values()
|
|
.filter(|v| v.asset.identifier == asset.identifier)
|
|
.map(|v| v.total_locked)
|
|
.sum()
|
|
}
|
|
|
|
/// List all vault IDs
|
|
pub fn vault_ids(&self) -> Vec<VaultId> {
|
|
self.vaults.keys().cloned().collect()
|
|
}
|
|
}
|
|
|
|
impl Default for VaultManager {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
// ==================== Helper Functions ====================
|
|
|
|
fn test_owner() -> BridgeAddress {
|
|
BridgeAddress::from_eth([0xaa; 20])
|
|
}
|
|
|
|
fn test_recipient() -> BridgeAddress {
|
|
BridgeAddress::from_synor([0xbb; 32])
|
|
}
|
|
|
|
fn test_owner_alt() -> BridgeAddress {
|
|
BridgeAddress::from_eth([0xcc; 20])
|
|
}
|
|
|
|
fn test_erc20_asset() -> AssetId {
|
|
AssetId::erc20("0x1234567890123456789012345678901234567890", "USDC", 6)
|
|
}
|
|
|
|
// ==================== VaultId Tests ====================
|
|
|
|
#[test]
|
|
fn test_vault_id_new() {
|
|
let id = VaultId::new("my-vault");
|
|
assert_eq!(id.0, "my-vault");
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_id_display() {
|
|
let id = VaultId::new("vault-123");
|
|
assert_eq!(format!("{}", id), "vault-123");
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_id_from_asset_deterministic() {
|
|
let asset = AssetId::eth();
|
|
let id1 = VaultId::from_asset(&asset, &ChainType::Ethereum);
|
|
let id2 = VaultId::from_asset(&asset, &ChainType::Ethereum);
|
|
|
|
assert_eq!(id1, id2);
|
|
assert!(!id1.0.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_id_from_asset_different_chains() {
|
|
let asset = AssetId::eth();
|
|
let id1 = VaultId::from_asset(&asset, &ChainType::Ethereum);
|
|
let id2 = VaultId::from_asset(&asset, &ChainType::EthereumSepolia);
|
|
|
|
assert_ne!(id1, id2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_id_from_asset_different_assets() {
|
|
let eth = AssetId::eth();
|
|
let usdc = test_erc20_asset();
|
|
|
|
let id1 = VaultId::from_asset(ð, &ChainType::Ethereum);
|
|
let id2 = VaultId::from_asset(&usdc, &ChainType::Ethereum);
|
|
|
|
assert_ne!(id1, id2);
|
|
}
|
|
|
|
// ==================== LockedAsset Tests ====================
|
|
|
|
#[test]
|
|
fn test_locked_asset_new() {
|
|
let locked = LockedAsset::new(
|
|
AssetId::eth(),
|
|
1000,
|
|
test_owner(),
|
|
test_recipient(),
|
|
1700000000,
|
|
);
|
|
|
|
assert_eq!(locked.amount, 1000);
|
|
assert_eq!(locked.locked_at, 1700000000);
|
|
assert_eq!(locked.expires_at, 0);
|
|
assert!(locked.lock_tx_hash.is_none());
|
|
assert!(!locked.released);
|
|
}
|
|
|
|
#[test]
|
|
fn test_locked_asset_with_expiry() {
|
|
let locked = LockedAsset::new(
|
|
AssetId::eth(),
|
|
1000,
|
|
test_owner(),
|
|
test_recipient(),
|
|
1700000000,
|
|
)
|
|
.with_expiry(1700003600);
|
|
|
|
assert_eq!(locked.expires_at, 1700003600);
|
|
}
|
|
|
|
#[test]
|
|
fn test_locked_asset_with_tx_hash() {
|
|
let tx_hash = vec![0x11; 32];
|
|
let locked = LockedAsset::new(
|
|
AssetId::eth(),
|
|
1000,
|
|
test_owner(),
|
|
test_recipient(),
|
|
1700000000,
|
|
)
|
|
.with_tx_hash(tx_hash.clone());
|
|
|
|
assert_eq!(locked.lock_tx_hash, Some(tx_hash));
|
|
}
|
|
|
|
#[test]
|
|
fn test_locked_asset_is_expired_no_expiry() {
|
|
let locked = LockedAsset::new(
|
|
AssetId::eth(),
|
|
1000,
|
|
test_owner(),
|
|
test_recipient(),
|
|
1700000000,
|
|
);
|
|
|
|
assert!(!locked.is_expired(1800000000));
|
|
}
|
|
|
|
#[test]
|
|
fn test_locked_asset_expiry() {
|
|
let locked = LockedAsset::new(
|
|
AssetId::eth(),
|
|
1000,
|
|
test_owner(),
|
|
test_recipient(),
|
|
1000,
|
|
)
|
|
.with_expiry(2000);
|
|
|
|
assert!(!locked.is_expired(1500));
|
|
assert!(locked.is_expired(2000));
|
|
assert!(locked.is_expired(3000));
|
|
}
|
|
|
|
#[test]
|
|
fn test_locked_asset_release() {
|
|
let mut locked = LockedAsset::new(
|
|
AssetId::eth(),
|
|
1000,
|
|
test_owner(),
|
|
test_recipient(),
|
|
1700000000,
|
|
);
|
|
|
|
assert!(!locked.released);
|
|
locked.release();
|
|
assert!(locked.released);
|
|
}
|
|
|
|
// ==================== VaultState Tests ====================
|
|
|
|
#[test]
|
|
fn test_vault_state_equality() {
|
|
assert_eq!(VaultState::Active, VaultState::Active);
|
|
assert_eq!(VaultState::Paused, VaultState::Paused);
|
|
assert_eq!(VaultState::Deprecated, VaultState::Deprecated);
|
|
assert_ne!(VaultState::Active, VaultState::Paused);
|
|
}
|
|
|
|
// ==================== Vault Tests ====================
|
|
|
|
#[test]
|
|
fn test_vault_new() {
|
|
let vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
assert_eq!(vault.id.0, "test-vault");
|
|
assert_eq!(vault.chain, ChainType::Ethereum);
|
|
assert_eq!(vault.state, VaultState::Active);
|
|
assert_eq!(vault.total_locked, 0);
|
|
assert_eq!(vault.daily_limit, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_with_address() {
|
|
let address = test_owner();
|
|
let vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
)
|
|
.with_address(address.clone());
|
|
|
|
assert_eq!(vault.vault_address, Some(address));
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_with_daily_limit() {
|
|
let vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
)
|
|
.with_daily_limit(1000000);
|
|
|
|
assert_eq!(vault.daily_limit, 1000000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_add_admin() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
let admin = test_owner();
|
|
vault.add_admin(admin.clone());
|
|
|
|
assert_eq!(vault.admins.len(), 1);
|
|
assert_eq!(vault.admins[0], admin);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_add_admin_duplicate() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
let admin = test_owner();
|
|
vault.add_admin(admin.clone());
|
|
vault.add_admin(admin.clone());
|
|
|
|
assert_eq!(vault.admins.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_lock_unlock() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
let current_time = 1700000000;
|
|
|
|
vault
|
|
.lock("lock1", 1000, test_owner(), test_recipient(), current_time)
|
|
.unwrap();
|
|
|
|
assert_eq!(vault.total_locked, 1000);
|
|
assert!(vault.get_locked("lock1").is_some());
|
|
|
|
let released = vault.unlock("lock1").unwrap();
|
|
assert_eq!(released.amount, 1000);
|
|
assert!(released.released);
|
|
assert_eq!(vault.total_locked, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_lock_multiple() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
let current_time = 1700000000;
|
|
vault.lock("lock-1", 1000, test_owner(), test_recipient(), current_time).unwrap();
|
|
vault.lock("lock-2", 2000, test_owner(), test_recipient(), current_time).unwrap();
|
|
vault.lock("lock-3", 500, test_owner_alt(), test_recipient(), current_time).unwrap();
|
|
|
|
assert_eq!(vault.total_locked, 3500);
|
|
}
|
|
|
|
#[test]
|
|
fn test_duplicate_lock() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
vault
|
|
.lock("lock1", 1000, test_owner(), test_recipient(), 0)
|
|
.unwrap();
|
|
|
|
let result = vault.lock("lock1", 500, test_owner(), test_recipient(), 0);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_unlock_nonexistent() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
let result = vault.unlock("nonexistent");
|
|
assert!(matches!(result, Err(BridgeError::TransferNotFound(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_unlock_already_released() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap();
|
|
vault.unlock("lock-1").unwrap();
|
|
|
|
let result = vault.unlock("lock-1");
|
|
assert!(matches!(result, Err(BridgeError::TransferAlreadyCompleted(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_pause() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
vault.pause();
|
|
|
|
let result = vault.lock("lock1", 1000, test_owner(), test_recipient(), 0);
|
|
assert!(matches!(result, Err(BridgeError::BridgePaused)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_resume() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
vault.pause();
|
|
vault.resume();
|
|
|
|
assert_eq!(vault.state, VaultState::Active);
|
|
vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_deprecate() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
vault.deprecate();
|
|
assert_eq!(vault.state, VaultState::Deprecated);
|
|
|
|
let result = vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0);
|
|
assert!(matches!(result, Err(BridgeError::BridgePaused)));
|
|
}
|
|
|
|
#[test]
|
|
fn test_daily_limit() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
)
|
|
.with_daily_limit(1000);
|
|
|
|
let current_time = 86400 * 100;
|
|
|
|
vault
|
|
.lock("lock1", 500, test_owner(), test_recipient(), current_time)
|
|
.unwrap();
|
|
|
|
let result = vault.lock("lock2", 600, test_owner(), test_recipient(), current_time);
|
|
assert!(matches!(result, Err(BridgeError::RateLimitExceeded)));
|
|
|
|
let next_day = current_time + 86400;
|
|
vault
|
|
.lock("lock2", 600, test_owner(), test_recipient(), next_day)
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_no_daily_limit() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
let current_time = 0;
|
|
vault.lock("lock-1", 1000000000, test_owner(), test_recipient(), current_time).unwrap();
|
|
vault.lock("lock-2", 1000000000, test_owner(), test_recipient(), current_time).unwrap();
|
|
|
|
assert_eq!(vault.total_locked, 2000000000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_get_locked() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap();
|
|
|
|
assert!(vault.get_locked("lock-1").is_some());
|
|
assert!(vault.get_locked("nonexistent").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_all_locked() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap();
|
|
vault.lock("lock-2", 2000, test_owner(), test_recipient(), 0).unwrap();
|
|
|
|
let all: Vec<_> = vault.all_locked().collect();
|
|
assert_eq!(all.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_active_locked() {
|
|
let mut vault = Vault::new(
|
|
VaultId::new("test-vault"),
|
|
ChainType::Ethereum,
|
|
AssetId::eth(),
|
|
);
|
|
|
|
vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap();
|
|
vault.lock("lock-2", 2000, test_owner(), test_recipient(), 0).unwrap();
|
|
vault.unlock("lock-1").unwrap();
|
|
|
|
let active: Vec<_> = vault.active_locked().collect();
|
|
assert_eq!(active.len(), 1);
|
|
}
|
|
|
|
// ==================== VaultManager Tests ====================
|
|
|
|
#[test]
|
|
fn test_vault_manager_new() {
|
|
let manager = VaultManager::new();
|
|
assert_eq!(manager.total_locked(), 0);
|
|
assert!(manager.vault_ids().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager_default() {
|
|
let manager = VaultManager::default();
|
|
assert_eq!(manager.total_locked(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager() {
|
|
let mut manager = VaultManager::new();
|
|
|
|
let eth = AssetId::eth();
|
|
let vault_id = manager.create_vault(ChainType::Ethereum, eth.clone());
|
|
|
|
assert!(manager.get_vault(&vault_id).is_some());
|
|
assert!(manager.find_vault(&ChainType::Ethereum, ð).is_some());
|
|
|
|
let vault = manager.get_or_create_vault(ChainType::Ethereum, eth.clone());
|
|
vault.lock("lock1", 100, test_owner(), test_recipient(), 0).unwrap();
|
|
|
|
assert_eq!(manager.total_locked(), 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager_create_multiple() {
|
|
let mut manager = VaultManager::new();
|
|
|
|
manager.create_vault(ChainType::Ethereum, AssetId::eth());
|
|
manager.create_vault(ChainType::Ethereum, test_erc20_asset());
|
|
manager.create_vault(ChainType::EthereumSepolia, AssetId::eth());
|
|
|
|
assert_eq!(manager.vault_ids().len(), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager_get_vault_mut() {
|
|
let mut manager = VaultManager::new();
|
|
let vault_id = manager.create_vault(ChainType::Ethereum, AssetId::eth());
|
|
|
|
{
|
|
let vault = manager.get_vault_mut(&vault_id).unwrap();
|
|
vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap();
|
|
}
|
|
|
|
let vault = manager.get_vault(&vault_id).unwrap();
|
|
assert_eq!(vault.total_locked, 1000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager_find_vault_not_found() {
|
|
let manager = VaultManager::new();
|
|
let vault = manager.find_vault(&ChainType::Ethereum, &AssetId::eth());
|
|
assert!(vault.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager_find_vault_mut() {
|
|
let mut manager = VaultManager::new();
|
|
let eth = AssetId::eth();
|
|
manager.create_vault(ChainType::Ethereum, eth.clone());
|
|
|
|
let vault = manager.find_vault_mut(&ChainType::Ethereum, ð).unwrap();
|
|
vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap();
|
|
|
|
assert_eq!(manager.total_locked(), 1000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager_get_or_create_new() {
|
|
let mut manager = VaultManager::new();
|
|
let eth = AssetId::eth();
|
|
|
|
let vault = manager.get_or_create_vault(ChainType::Ethereum, eth.clone());
|
|
vault.lock("lock-1", 1000, test_owner(), test_recipient(), 0).unwrap();
|
|
|
|
assert_eq!(manager.vault_ids().len(), 1);
|
|
assert_eq!(manager.total_locked(), 1000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager_total_locked() {
|
|
let mut manager = VaultManager::new();
|
|
|
|
let eth = AssetId::eth();
|
|
let usdc = test_erc20_asset();
|
|
|
|
let eth_vault_id = manager.create_vault(ChainType::Ethereum, eth);
|
|
let usdc_vault_id = manager.create_vault(ChainType::Ethereum, usdc);
|
|
|
|
manager
|
|
.get_vault_mut(ð_vault_id)
|
|
.unwrap()
|
|
.lock("lock-1", 1000, test_owner(), test_recipient(), 0)
|
|
.unwrap();
|
|
|
|
manager
|
|
.get_vault_mut(&usdc_vault_id)
|
|
.unwrap()
|
|
.lock("lock-2", 2000, test_owner(), test_recipient(), 0)
|
|
.unwrap();
|
|
|
|
assert_eq!(manager.total_locked(), 3000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager_total_locked_for_asset() {
|
|
let mut manager = VaultManager::new();
|
|
|
|
let eth = AssetId::eth();
|
|
let usdc = test_erc20_asset();
|
|
|
|
let eth_vault_id = manager.create_vault(ChainType::Ethereum, eth.clone());
|
|
let usdc_vault_id = manager.create_vault(ChainType::Ethereum, usdc.clone());
|
|
|
|
manager
|
|
.get_vault_mut(ð_vault_id)
|
|
.unwrap()
|
|
.lock("lock-1", 1000, test_owner(), test_recipient(), 0)
|
|
.unwrap();
|
|
|
|
manager
|
|
.get_vault_mut(&usdc_vault_id)
|
|
.unwrap()
|
|
.lock("lock-2", 2000, test_owner(), test_recipient(), 0)
|
|
.unwrap();
|
|
|
|
assert_eq!(manager.total_locked_for_asset(ð), 1000);
|
|
assert_eq!(manager.total_locked_for_asset(&usdc), 2000);
|
|
}
|
|
|
|
#[test]
|
|
fn test_vault_manager_vault_ids() {
|
|
let mut manager = VaultManager::new();
|
|
|
|
let id1 = manager.create_vault(ChainType::Ethereum, AssetId::eth());
|
|
let id2 = manager.create_vault(ChainType::Ethereum, test_erc20_asset());
|
|
|
|
let ids = manager.vault_ids();
|
|
assert_eq!(ids.len(), 2);
|
|
assert!(ids.contains(&id1));
|
|
assert!(ids.contains(&id2));
|
|
}
|
|
}
|