Smart Contract
The Términa Escrow smart contract is written in Rust using the Odra framework, providing type-safe, gas-efficient escrow operations on the Casper blockchain.
Architecture
The contract is designed as a single, self-contained module that manages the entire escrow lifecycle:
#[odra::module]
pub struct Escrow {
// Invoice data
invoice: Var<Invoice>,
// Escrow state
state: Var<EscrowState>,
balance: Var<U512>,
// Dispute info
dispute_reason: Var<Option<String>>,
}Data Structures
#[derive(OdraType)]
pub struct Invoice {
pub id: String,
pub description: String,
pub amount: U512,
pub issuer_address: Address,
pub payer_address: Address,
pub arbiter_address: Option<Address>,
pub created_at: u64,
pub due_date: Option<u64>,
}
#[derive(OdraType)]
pub enum EscrowState {
Draft,
Accepted,
Funded,
Released,
Cancelled,
Disputed,
}Entry Points
The contract exposes the following callable methods:
init
Initializes a new escrow contract with invoice details.
#[odra(init)]
pub fn init(&mut self, config: EscrowConfig) {
// Only callable once during deployment
// Sets up invoice, initial state = Draft
}accept
Payer accepts the escrow terms.
pub fn accept(&mut self) {
// Caller must be payer
// State must be Draft
// Transitions to Accepted
}fund
Payer deposits funds into the escrow.
#[odra(payable)]
pub fn fund(&mut self) {
// Caller must be payer
// State must be Accepted
// Amount must be >= invoice.amount
// Transitions to Funded
}release
Payer releases funds to the issuer.
pub fn release(&mut self) {
// Caller must be payer
// State must be Funded
// Transfers balance to issuer
// Transitions to Released
}cancel
Cancel the escrow and return any funds.
pub fn cancel(&mut self) {
// Callable by issuer (in Draft) or both (in Accepted)
// If Funded, only via dispute resolution
// Returns funds to payer if any
// Transitions to Cancelled
}dispute
Raise a dispute when funded.
pub fn dispute(&mut self, reason: String) {
// Caller must be issuer or payer
// State must be Funded
// Stores dispute reason
// Transitions to Disputed
}resolve_dispute
Arbiter resolves the dispute.
pub fn resolve_dispute(&mut self, release_to_issuer: bool) {
// Caller must be arbiter
// State must be Disputed
// If true: releases to issuer
// If false: refunds to payer
}View Methods
pub fn get_state(&self) -> EscrowState;
pub fn get_invoice(&self) -> Invoice;
pub fn get_balance(&self) -> U512;
pub fn get_dispute_reason(&self) -> Option<String>;Events
The contract emits events for all state changes, enabling off-chain indexing and notifications:
| Event | Data | Emitted When |
|---|---|---|
EscrowCreated | invoice_id, issuer, payer, amount | Contract deployed |
EscrowAccepted | invoice_id, payer | Payer accepts |
FundsDeposited | invoice_id, amount | Payer funds escrow |
FundsReleased | invoice_id, recipient, amount | Funds released to issuer |
EscrowCancelled | invoice_id, cancelled_by | Escrow cancelled |
DisputeRaised | invoice_id, raised_by, reason | Dispute initiated |
DisputeResolved | invoice_id, resolved_by, outcome | Arbiter resolves |
Gas Costs
Estimated gas costs on Casper testnet:
- Deploy: ~5-10 CSPR
- Accept: ~1 CSPR
- Fund: ~1 CSPR + transfer amount
- Release: ~1 CSPR
- Cancel/Dispute: ~1 CSPR
Security Considerations
- Access Control: Each method validates the caller against stored addresses
- State Guards: Actions are only allowed in valid states
- Reentrancy: State updated before external calls
- Integer Overflow: Using Odra's safe math operations