Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,3 @@ jobs:
run: npm test
env:
NODE_ENV: test

docker-validate:
name: Docker Compose validation
runs-on: ubuntu-latest
needs: test-and-migrate
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Build and run compose, wait for healthchecks
run: |
docker compose build
docker compose up --wait -d

- name: Tear down
if: always()
run: docker compose down --volumes --remove-orphans
41 changes: 0 additions & 41 deletions .github/workflows/contracts.yml

This file was deleted.

16 changes: 14 additions & 2 deletions contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,20 @@ Testing
# Run all tests
cargo test

# Run only decimals validation tests
cargo test test_create_vault -- decimals
#### Logs-enabled release profile

The crate defines a `release-with-logs` profile in `contracts/Cargo.toml` that inherits
from `release` and enables `debug-assertions`. This allows diagnostics to be compiled
only for simulator-focused, release-mode builds while keeping the production `release`
build log-free.

```bash
cd contracts/accountability_vault
cargo test --profile release-with-logs
cargo build --profile release-with-logs --target wasm32-unknown-unknown
```

### Migration: API change (cancel_vault vs withdraw)

Deployment
# Build
Expand Down
81 changes: 42 additions & 39 deletions contracts/accountability_vault/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub enum DataKey {
/// Address allowed to manage deployment-wide settings.
Admin,
/// The vault configuration and current state.
Vault(BytesN<32>),
Vault(String),
/// Per-milestone check-in timestamp (set when the milestone reaches the approval threshold).
CheckIn(u32),
/// Per-milestone list of addresses that have approved, used for M-of-N tracking.
Expand Down Expand Up @@ -161,10 +161,10 @@ pub enum Error {
StakedRemaining = 22,
/// Operation rejected because the vault is in `Disputed` state.
VaultDisputed = 23,
/// Milestone has already been released via claim_milestone
MilestoneAlreadyReleased = 26,
/// Some milestones already released, bulk claim not allowed
PartiallyReleased = 27,
/// A bulk claim is rejected because at least one milestone was already released.
PartiallyReleased = 26,
/// The requested milestone has already been released via `claim_milestone`.
MilestoneAlreadyReleased = 27,
}

#[contract]
Expand Down Expand Up @@ -320,6 +320,13 @@ impl AccountabilityVault {
Ok(())
}

#[cfg(debug_assertions)]
fn log_diagnostic(env: &Env, event: &str, actor: &Address, value: i128) {
env.logs().add(String::from_str(env, event));
env.logs().add(actor.clone());
env.logs().add(value);
}

/// Funds the vault by transferring `amount` of the staking token from the
/// creator into the contract, moving the vault from `Draft` to `Active`.
///
Expand Down Expand Up @@ -367,6 +374,8 @@ impl AccountabilityVault {
// Legacy event: preserved for backward-compatible listeners.
env.events()
.publish((Symbol::new(&env, "vault_staked"), from), vault.staked);
#[cfg(debug_assertions)]
Self::log_diagnostic(&env, "stake", &from, vault.staked);
Ok(())
}

Expand Down Expand Up @@ -445,7 +454,7 @@ impl AccountabilityVault {
/// human verifier sign-offs.
pub fn check_in(
env: Env,
vault_id: BytesN<32>,
vault_id: String,
caller: Address,
milestone_index: u32,
evidence_hash: BytesN<32>,
Expand Down Expand Up @@ -508,7 +517,7 @@ impl AccountabilityVault {
&DataKey::CheckIn(milestone_index),
&(env.ledger().timestamp(), evidence_hash.clone()),
);
env.storage().instance().set(&DataKey::Vault, &vault);
env.storage().instance().set(&DataKey::Vault(vault_id.clone()), &vault);
}

let source = if is_oracle {
Expand All @@ -519,11 +528,13 @@ impl AccountabilityVault {
env.events().publish(
(
Symbol::new(&env, "milestone_checked_in"),
caller,
caller.clone(),
source,
),
(milestone_index, evidence_hash),
);
#[cfg(debug_assertions)]
Self::log_diagnostic(&env, "check_in", &caller, milestone_index as i128);
Ok(())
}

Expand Down Expand Up @@ -590,7 +601,7 @@ impl AccountabilityVault {
/// Checks-Effects-Interactions: vault status is set to `Failed` and `staked`
/// is zeroed in storage BEFORE the external token transfer is executed,
/// ensuring the terminal state is committed even if the transfer call panics.
pub fn slash_on_miss(env: Env, vault_id: BytesN<32>) -> Result<(), Error> {
pub fn slash_on_miss(env: Env, vault_id: String) -> Result<(), Error> {
let mut vault: Vault = Self::load(&env, &vault_id)?;

// Check Disputed before NotActive so callers get the specific error code.
Expand All @@ -614,7 +625,7 @@ impl AccountabilityVault {
let token_addr = vault.token.clone();
vault.status = VaultStatus::Failed;
vault.staked = 0;
env.storage().instance().set(&DataKey::Vault(vault_id), &vault);
env.storage().instance().set(&DataKey::Vault(vault_id.clone()), &vault);

token::Client::new(&env, &token_addr).transfer(
&env.current_contract_address(),
Expand All @@ -625,10 +636,12 @@ impl AccountabilityVault {
env.events().publish(
(
Symbol::new(&env, "vault_slashed"),
failure_destination,
failure_destination.clone(),
),
slashed,
);
#[cfg(debug_assertions)]
Self::log_diagnostic(&env, "slash_on_miss", &failure_destination, slashed);
Ok(())
}

Expand All @@ -639,7 +652,7 @@ impl AccountabilityVault {
/// Checks-Effects-Interactions: vault status is set to `Completed` and
/// `staked` is zeroed in storage BEFORE the external token transfer,
/// ensuring the terminal state is committed even if the transfer call panics.
pub fn claim(env: Env, vault_id: BytesN<32>, caller: Address) -> Result<(), Error> {
pub fn claim(env: Env, vault_id: String, caller: Address) -> Result<(), Error> {
caller.require_auth();
let key = DataKey::Vault(vault_id);
let mut vault: Vault = env
Expand Down Expand Up @@ -670,20 +683,22 @@ impl AccountabilityVault {

let released = vault.staked;
vault.staked = 0;
vault.status = VaultStatus::Completed;
env.storage().persistent().set(&key, &vault);
token::Client::new(&env, &vault.token).transfer(
env.storage().instance().set(&DataKey::Vault(vault_id.clone()), &vault);

token::Client::new(&env, &token_addr).transfer(
&env.current_contract_address(),
&vault.success_destination,
&released,
);
env.events().publish(
(
Symbol::new(&env, "vault_completed"),
vault.success_destination,
success_destination.clone(),
),
released,
);
#[cfg(debug_assertions)]
Self::log_diagnostic(&env, "claim", &success_destination, released);
Ok(())
}

Expand All @@ -695,20 +710,14 @@ impl AccountabilityVault {
///
/// When the last milestone is claimed, the vault automatically transitions
/// to `Completed`.
pub fn claim_milestone(
env: Env,
vault_id: BytesN<32>,
caller: Address,
index: u32,
) -> Result<(), Error> {
pub fn claim_milestone(env: Env, vault_id: String, caller: Address, index: u32) -> Result<(), Error> {
caller.require_auth();
let mut vault: Vault = Self::load(&env, &vault_id)?;

if vault.status != VaultStatus::Active {
return Err(Error::NotActive);
}
let is_authorized = caller == vault.creator || vault.verifiers.iter().any(|v| v == caller);
if !is_authorized {
if caller != vault.creator && !vault.verifiers.iter().any(|v| v == caller) {
return Err(Error::Unauthorized);
}
if index >= vault.milestones.len() {
Expand Down Expand Up @@ -760,7 +769,7 @@ impl AccountabilityVault {
);
}

env.storage().instance().set(&DataKey::Vault(vault_id), &vault);
env.storage().instance().set(&DataKey::Vault(vault_id.clone()), &vault);
Ok(())
}

Expand Down Expand Up @@ -820,7 +829,7 @@ impl AccountabilityVault {
let token_addr = vault.token.clone();
vault.staked = 0;
vault.status = VaultStatus::Cancelled;
env.storage().instance().set(&DataKey::Vault, &vault);
env.storage().instance().set(&DataKey::Vault(vault_id.clone()), &vault);

token::Client::new(&env, &token_addr).transfer(
&env.current_contract_address(),
Expand All @@ -829,9 +838,11 @@ impl AccountabilityVault {
);

env.events().publish(
(Symbol::new(&env, "vault_withdrawn"), creator),
(Symbol::new(&env, "vault_withdrawn"), creator.clone()),
refunded,
);
#[cfg(debug_assertions)]
Self::log_diagnostic(&env, "withdraw", &creator, refunded);
Ok(())
}

Expand Down Expand Up @@ -904,19 +915,15 @@ impl AccountabilityVault {
///
/// Only the `guardian` address set at vault creation may call this function.
/// Use to halt settlement during disputes or detected incidents.
pub fn emergency_pause(
env: Env,
vault_id: BytesN<32>,
guardian: Address,
) -> Result<(), Error> {
pub fn emergency_pause(env: Env, vault_id: String, guardian: Address) -> Result<(), Error> {
guardian.require_auth();
let mut vault: Vault = Self::load(&env, &vault_id)?;

if guardian != vault.guardian {
return Err(Error::Unauthorized);
}
vault.paused = true;
env.storage().instance().set(&DataKey::Vault(vault_id), &vault);
env.storage().instance().set(&DataKey::Vault(vault_id.clone()), &vault);
env.events()
.publish((Symbol::new(&env, "vault_paused"), guardian), true);
Ok(())
Expand All @@ -925,11 +932,7 @@ impl AccountabilityVault {
/// Unpauses the vault, re-enabling `slash_on_miss`, `claim`, and `withdraw`.
///
/// Only the `guardian` address set at vault creation may call this function.
pub fn emergency_unpause(
env: Env,
vault_id: BytesN<32>,
guardian: Address,
) -> Result<(), Error> {
pub fn emergency_unpause(env: Env, vault_id: String, guardian: Address) -> Result<(), Error> {
guardian.require_auth();
let mut vault: Vault = Self::load(&env, &vault_id)?;

Expand Down Expand Up @@ -972,7 +975,7 @@ impl AccountabilityVault {
/// Sweeps any residual token balance held by the contract to the vault creator
/// after a terminal settlement. Only the creator may call this, and only once
/// `staked` has been zeroed by `claim`, `slash_on_miss`, or `withdraw`.
pub fn reclaim_after_settlement(env: Env, vault_id: BytesN<32>, token_address: Address) -> Result<(), Error> {
pub fn reclaim_after_settlement(env: Env, vault_id: String, token_address: Address) -> Result<(), Error> {
let vault: Vault = Self::load(&env, &vault_id)?;
vault.creator.require_auth();

Expand Down
Loading
Loading