Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Escrow

Two-party escrow with an arbiter and a deadline. Prefer the enum-based contracts/escrow_v2.cam; contracts/escrow.cam is the same machine with u8 state codes.

State machine

Created  --fund()-->          Funded
Funded   --release()-->       Released   (pay seller)
Funded   --refund()-->        Refunded   (pay buyer)
Funded   --claimTimeout()-->  Refunded   (after deadline)
Funded   --dispute()-->       Disputed
Disputed --resolve(pct)-->    Resolved   (split by %)

Enum version (excerpt)

type Amount = U256
type Timestamp = u64

pure fn is_party(sender: address, buyer: address, seller: address) -> bool {
    sender == buyer || sender == seller
}

entity EscrowV2 {

    enum State {
        Created,
        Funded,
        Released,
        Refunded,
        Disputed,
        Resolved
    }

    routes {
        constructor(buyer: address, seller: address, arbiter: address,
                    amount: Amount, deadline: Timestamp) => []

        fund()
            where m_state == State::Created : throw 10 &&
                  msg::sender == m_buyer : throw 11 => []

        release()
            where m_state == State::Funded : throw 10 &&
                  msg::sender == m_buyer : throw 11 => [
                ~> m_seller
            ]

        refund()
            where m_state == State::Funded : throw 10 &&
                  msg::sender == m_seller : throw 12 => [
                ~> m_buyer
            ]

        claimTimeout()
            where m_state == State::Funded : throw 10 &&
                  msg::sender == m_buyer : throw 11 &&
                  msg::timestamp > m_deadline : throw 16 => [
                ~> m_buyer
            ]

        dispute()
            where m_state == State::Funded : throw 10 &&
                  is_party(msg::sender, m_buyer, m_seller) : throw 14 => []

        resolve(to_buyer_pct: u8)
            where m_state == State::Disputed : throw 10 &&
                  msg::sender == m_arbiter : throw 13 => [
                let buyer_amount = m_amount * to_buyer_pct as Amount / 100;
                let seller_amount = m_amount - buyer_amount;
                if buyer_amount + seller_amount == m_amount => [
                    ~> m_buyer
                    ~> m_seller
                ]
            ]

        getStatus() -> (u8, Amount, Timestamp) => [
            let state_code = match m_state {
                State::Created => 0,
                State::Funded => 1,
                State::Released => 2,
                State::Refunded => 3,
                State::Disputed => 4,
                State::Resolved => 5
            };
            return(state_code, m_amount, m_deadline)
        ]
    }

    m_state: State {
        in constructor(_, _, _, _, _) => State::Created
        in fund() => State::Funded
        in release() => State::Released
        in refund() => State::Refunded
        in claimTimeout() => State::Refunded
        in dispute() => State::Disputed
        in resolve(_) => State::Resolved
    }

    m_resolution: Option<String> {
        in constructor(_, _, _, _, _) => none
        in resolve(_) => some("Resolved by arbiter")
    }
}

Buyer / seller / arbiter / amount / deadline members are set once in constructor (full file in the repo). Plain ~> dest transfers value; the arbiter’s resolve splits by percentage with a conservation check.

Why enums

escrow.cam (u8)escrow_v2.cam (enum)
State typemagic numbersState::Funded
Queriesreturn raw u8match to a code if needed
Extra state—Option<String> resolution note

Same guards and payouts; the enum version is what you want for new code. Governor and Timelock use the same enum + member-transform pattern for proposal / operation lifecycles.

Tests in the workspace

FileRole
contracts/escrow_v2.test.camUnit tests for the enum escrow
contracts/escrow.test.camSame machine with u8 state codes
contracts/escrow_two_vaults.invariant.camMulti-entity invariant example

List them in project.yaml with target: evm to emit Foundry tests — see Counter.