A complete blockchain implementation featuring: - synord: Full node with GHOSTDAG consensus - explorer-web: Modern React blockchain explorer with 3D DAG visualization - CLI wallet and tools - Smart contract SDK and example contracts (DEX, NFT, token) - WASM crypto library for browser/mobile
183 lines
5.5 KiB
Rust
183 lines
5.5 KiB
Rust
//! Contract Testing Framework for Synor Smart Contracts.
|
|
//!
|
|
//! This crate provides a comprehensive testing framework for Synor smart contracts,
|
|
//! including test environments, mock storage, test accounts, and assertion utilities.
|
|
//!
|
|
//! # Quick Start
|
|
//!
|
|
//! ```rust,ignore
|
|
//! use synor_contract_test::prelude::*;
|
|
//!
|
|
//! #[tokio::test]
|
|
//! async fn test_token_contract() {
|
|
//! // Create test environment with accounts
|
|
//! let mut env = TestEnvironment::builder()
|
|
//! .with_account(TestAccount::with_balance(1000))
|
|
//! .with_account(TestAccount::with_balance(500))
|
|
//! .build();
|
|
//!
|
|
//! // Deploy contract
|
|
//! let bytecode = include_bytes!("../contracts/token.wasm");
|
|
//! let contract = env.deploy_contract(bytecode, &init_params).unwrap();
|
|
//!
|
|
//! // Call contract methods
|
|
//! let result = env.call_contract(contract, "transfer", &args).unwrap();
|
|
//!
|
|
//! // Assert results
|
|
//! assert_success!(result);
|
|
//! assert_event_emitted!(result, "Transfer");
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! # Features
|
|
//!
|
|
//! - **TestEnvironment**: Wraps VmEngine with test utilities
|
|
//! - **MockStorage**: Isolated storage for testing
|
|
//! - **TestAccount**: Pre-funded accounts with keypairs
|
|
//! - **Assertions**: Macros for testing results, storage, and events
|
|
//!
|
|
//! # Architecture
|
|
//!
|
|
//! ```text
|
|
//! +------------------+
|
|
//! | TestEnvironment |
|
|
//! +------------------+
|
|
//! |
|
|
//! v
|
|
//! +------------------+ +------------------+
|
|
//! | VmEngine |<--->| MockStorage |
|
|
//! +------------------+ +------------------+
|
|
//! |
|
|
//! v
|
|
//! +------------------+
|
|
//! | TestAccount[] |
|
|
//! +------------------+
|
|
//! ```
|
|
|
|
#![allow(dead_code)]
|
|
|
|
pub mod assertions;
|
|
mod environment;
|
|
mod mock_storage;
|
|
mod test_account;
|
|
|
|
// Re-export core types
|
|
pub use environment::{TestEnvironment, TestEnvironmentBuilder};
|
|
pub use mock_storage::MockStorage;
|
|
pub use test_account::{TestAccount, TestKeypair};
|
|
|
|
// Re-export assertion utilities
|
|
pub use assertions::{
|
|
event_selector, to_return_bytes, to_storage_bytes, to_topic_bytes, AssertionChain,
|
|
AssertionError, ReturnEncodable, StorageEncodable, TopicEncodable,
|
|
};
|
|
|
|
// Re-export VM types for convenience
|
|
pub use synor_vm;
|
|
pub use synor_vm::{
|
|
CallParams, ContractId, ContractLog, DeployParams, ExecutionResult, StorageChange, StorageKey,
|
|
StorageValue, VmEngine, VmError,
|
|
};
|
|
|
|
/// Errors that can occur during contract testing.
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum TestError {
|
|
/// VM error during execution.
|
|
#[error("VM error: {0}")]
|
|
VmError(#[from] VmError),
|
|
|
|
/// Contract deployment failed.
|
|
#[error("Deployment failed: {0}")]
|
|
DeploymentFailed(String),
|
|
|
|
/// Contract call failed.
|
|
#[error("Call failed: {0}")]
|
|
CallFailed(String),
|
|
|
|
/// Account not found.
|
|
#[error("Account not found: {0}")]
|
|
AccountNotFound(String),
|
|
|
|
/// Insufficient balance.
|
|
#[error("Insufficient balance: have {have}, need {need}")]
|
|
InsufficientBalance { have: u64, need: u64 },
|
|
|
|
/// Invalid bytecode.
|
|
#[error("Invalid bytecode: {0}")]
|
|
InvalidBytecode(String),
|
|
|
|
/// Serialization error.
|
|
#[error("Serialization error: {0}")]
|
|
SerializationError(String),
|
|
|
|
/// Invalid state.
|
|
#[error("Invalid state: {0}")]
|
|
InvalidState(String),
|
|
|
|
/// Configuration error.
|
|
#[error("Configuration error: {0}")]
|
|
ConfigError(String),
|
|
}
|
|
|
|
/// Result type for contract test operations.
|
|
pub type TestResult<T> = Result<T, TestError>;
|
|
|
|
/// Prelude module for convenient imports.
|
|
pub mod prelude {
|
|
pub use crate::assertions::{
|
|
event_selector, to_return_bytes, to_storage_bytes, to_topic_bytes, AssertionChain,
|
|
ReturnEncodable, StorageEncodable, TopicEncodable,
|
|
};
|
|
pub use crate::{
|
|
assert_event_emitted, assert_gas_used, assert_no_events, assert_return_data, assert_revert,
|
|
assert_storage_empty, assert_storage_eq, assert_storage_none, assert_success,
|
|
};
|
|
pub use crate::{
|
|
CallParams, ContractId, DeployParams, ExecutionResult, MockStorage, StorageKey,
|
|
StorageValue, TestAccount, TestEnvironment, TestError, TestKeypair, TestResult, VmError,
|
|
};
|
|
pub use synor_types::{Address, Amount, Hash256, Network};
|
|
}
|
|
|
|
/// Utility to encode method call arguments.
|
|
pub fn encode_call_args(method: &str, args: &[u8]) -> Vec<u8> {
|
|
let selector = synor_sdk::method_selector(method);
|
|
let mut data = Vec::with_capacity(4 + args.len());
|
|
data.extend_from_slice(&selector);
|
|
data.extend_from_slice(args);
|
|
data
|
|
}
|
|
|
|
/// Utility to decode return data.
|
|
pub fn decode_return_data<T: borsh::BorshDeserialize>(data: &[u8]) -> TestResult<T> {
|
|
borsh::from_slice(data).map_err(|e| TestError::SerializationError(e.to_string()))
|
|
}
|
|
|
|
/// Utility to encode arguments using borsh.
|
|
pub fn encode_args<T: borsh::BorshSerialize>(args: &T) -> TestResult<Vec<u8>> {
|
|
borsh::to_vec(args).map_err(|e| TestError::SerializationError(e.to_string()))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_encode_call_args() {
|
|
let args = encode_call_args("transfer", &[1, 2, 3]);
|
|
assert!(args.len() >= 4);
|
|
|
|
// Method selector should be first 4 bytes
|
|
let selector = synor_sdk::method_selector("transfer");
|
|
assert_eq!(&args[..4], &selector);
|
|
assert_eq!(&args[4..], &[1, 2, 3]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_encode_decode_roundtrip() {
|
|
let original: u64 = 12345;
|
|
let encoded = encode_args(&original).unwrap();
|
|
let decoded: u64 = decode_return_data(&encoded).unwrap();
|
|
assert_eq!(original, decoded);
|
|
}
|
|
}
|