This guide provides best practices for writing efficient smart contracts on the EpicChain blockchain using the EpicChain Rust Smart Contract Framework.
Storage operations are expensive on blockchains. Optimize your contract by:
- Batch Storage Updates: Group multiple storage operations when possible
- Use Appropriate Storage Types: Choose the smallest possible data type for your values
- Consider Cache Layers: Use in-memory caching for frequently accessed values
Example of inefficient vs. efficient storage:
// Inefficient - Multiple storage operations
pub fn transfer(from: Address, to: Address, amount: u128) -> bool {
let storage = Storage::new();
let from_balance = storage.get::<_, u128>(&from_key).unwrap_or(0);
storage.put(&from_key, &(from_balance - amount)); // First storage operation
let to_balance = storage.get::<_, u128>(&to_key).unwrap_or(0);
storage.put(&to_key, &(to_balance + amount)); // Second storage operation
true
}
// Efficient - Batched operations
pub fn transfer(from: Address, to: Address, amount: u128) -> bool {
let storage = Storage::new();
// Read all data first
let from_balance = storage.get::<_, u128>(&from_key).unwrap_or(0);
let to_balance = storage.get::<_, u128>(&to_key).unwrap_or(0);
// Perform computations
let new_from_balance = from_balance - amount;
let new_to_balance = to_balance + amount;
// Write all data at once
storage.put(&from_key, &new_from_balance);
storage.put(&to_key, &new_to_balance);
true
}Design efficient storage keys to minimize conflicts and improve organization:
// Good practice - Use prefixes for different data types
const BALANCE_PREFIX: &[u8] = b"balance:";
const ALLOWANCE_PREFIX: &[u8] = b"allowance:";
const METADATA_PREFIX: &[u8] = b"metadata:";
// Create keys with prefixes
let balance_key = [BALANCE_PREFIX, account.as_bytes()].concat();Mark read-only methods appropriately to save gas:
- Document Read-Only Methods: Use naming conventions (
get_,query_, etc.) or@safeannotations - Follow Conventions: Use standard method names from NEP specifications
/// Returns the token balance for the specified account
///
/// This method does not modify state and is marked as safe.
/// @safe
pub fn balanceOf(account: Address) -> u128 {
// Read-only implementation
}Keep on-chain computations minimal:
- Precompute Values: Calculate values off-chain when possible
- Simplify Algorithms: Use efficient algorithms with minimal steps
- Use Fixed-Point Math: Instead of floating point when possible
Be mindful of memory allocation:
- Prefer Stack Allocation: Use fixed-size arrays when possible
- Minimize Vector Growth: Pre-allocate vectors with known capacity
- Clean Up Unused Data: Free memory when no longer needed
// Less efficient - Growable vector with potential reallocations
let mut data = Vec::new();
for i in 0..count {
data.push(i);
}
// More efficient - Pre-allocated vector
let mut data = Vec::with_capacity(count);
for i in 0..count {
data.push(i);
}Design clear method interfaces:
- Descriptive Parameter Names: Use meaningful names that appear in the manifest
- Consistent Return Types: Use consistent return types for similar operations
- Well-Documented Interfaces: Add comprehensive documentation comments
/// Transfers tokens from one account to another
///
/// # Parameters
/// * `from` - The account to transfer tokens from
/// * `to` - The account to transfer tokens to
/// * `amount` - The amount of tokens to transfer
/// * `data` - Optional data to include with the transfer
///
/// # Returns
/// * `bool` - True if the transfer was successful, false otherwise
pub fn transfer(from: Address, to: Address, amount: u128, data: ByteString) -> bool {
// Implementation
}Always validate inputs:
pub fn transfer(from: Address, to: Address, amount: u128) -> bool {
// Verify sender
assert!(Runtime::check_witness(&from), "No authorization");
// Check amount
if amount == 0 {
return true;
}
// Check recipient is not null address
if to == Address::from([0u8; 20]) {
return false;
}
// Further implementation
}Guard against integer overflows:
// Unsafe - Potential overflow
let new_balance = balance + amount;
// Safe - Checks for overflow
let new_balance = balance.checked_add(amount).unwrap_or_else(|| {
panic!("Transfer would overflow recipient balance");
});Write comprehensive tests for all contract functions:
#[test]
fn test_transfer() {
// Set up test environment
let owner = Address::from([1u8; 20]);
let recipient = Address::from([2u8; 20]);
// Mock Runtime.CheckWitness to return true for owner
// Test the transfer functionality
// Assert the expected state changes
}Implement standards fully to enable proper manifest generation:
- NEP-17 for Fungible Tokens: Implement all required methods with correct signatures
- NEP-11 for Non-Fungible Tokens: Follow the standard interface precisely
Document methods comprehensively for better manifest generation:
/// Returns the symbol of the token
///
/// This method returns the token's symbol as a ByteString.
/// The symbol is a short string like "XPR" that represents the token.
///
/// # Returns
/// A ByteString containing the token's symbol
pub fn symbol() -> ByteString {
"TOKEN".into()
}Monitor and optimize gas usage:
- Test EpicPulse Consumption: Measure gas usage for critical operations
- Profile Hot Paths: Identify and optimize frequently executed code
- Benchmark Alternatives: Compare different implementation approaches
Efficient EpicChain smart contracts combine:
- Optimized Storage: Minimize and batch storage operations
- EpicPulse Efficiency: Reduce computation and mark read-only methods
- Clear Interfaces: Design intuitive, well-documented APIs
- Comprehensive Testing: Verify correctness and efficiency
- Good Documentation: Enable accurate manifest generation
By following these practices, your contracts will be more efficient, secure, and easier to maintain.
The framework includes examples that demonstrate efficient contract patterns:
cd examples/04-nep17-token
cat src/lib.rs # See efficient NEP-17 implementation
make # Build optimized contractcd examples/05-nep11-nft
cat src/lib.rs # See efficient NFT implementation
make test # Run performance testscd examples/09-simple-dex
cat src/lib.rs # See efficient DEX implementation
make # Build with optimizationsThe framework includes build optimizations in all Makefiles:
# Optimized build flags for production
RUST_FLAGS = "-Ctarget-feature=+multivalue \
-Cllvm-args=--combiner-store-merging=false \
-Clink-arg=--initial-memory=262144 \
-Clink-arg=-zstack-size=131072"These optimizations:
- Reduce WASM size - Smaller contracts use less gas
- Optimize memory usage - Efficient stack and heap management
- Enable WASM features - Use advanced WASM capabilities
- Getting Started Guide - Framework setup and basics
- Contract Attributes - Metadata and permissions
- Safe Methods - Read-only method patterns
- Testing Guide - Performance testing strategies