Actually Smart Contractsalpha

Status: the sandbox runs today live. The SDK and API on this page are planned planned.

Docs

Enforce a ruling

Your contract checks the jurors' signatures itself. A ruling only counts on chain if it verifies there.

withdraw() → requestRuling(asc, action, jury) → jury votes → verifyRuling → allow: runs, else: revert
ChainCallStatus
Solanaverify_ruling(&asc, &action, &instructions) -> Outcome. The juror signatures ride in the same transaction (Ed25519 precompile). It fails if the ASC id, action hash, juror keys or quorum don't match.planned
EVMrequestRuling(ascId, action, jury) emits an event; then verifyRuling(requestId, attestations) returns the outcome. Two steps, because a contract can't wait for models.planned

Solana

Your backend calls asc.requestRuling, then sends the payout with ruling.attestations in the same transaction. The program checks them.

withdraw.anchor.rsRustillustrative
// The on-chain side. The payout fails unless a quorum of listed jurors signed
// "allow" for this exact withdrawal under this exact ASC.
// Illustrative, Anchor-style. Not a deployed program.
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
    let action = Action::withdrawal(ctx.accounts.to.key(), amount);

    // Juror signatures ride in this transaction (Ed25519 precompile).
    // verify_ruling checks the ASC id, action hash, juror keys and quorum.
    verify_ruling(&ctx.accounts.asc, &action, &ctx.accounts.instructions)?.require(Outcome::Allow)?;

    // Hard caps still apply in code, whatever the jury said.
    require!(amount <= ctx.accounts.asc.max_single, AscError::OverCap);
    transfer(ctx, amount)
}

Full sample: Exchange withdrawal checks.

EVM

Step 1 asks the jury. Step 2 executes with the jurors' signed votes.

IAsc.solSolidityplanned
// SPDX-License-Identifier: MIT
// The on-chain side of an ASC on EVM chains. Planned interface, illustrative.
// EVM calls can't wait for models, so it's two steps: requestRuling, then verifyRuling.
pragma solidity ^0.8.24;

interface IAsc {
    enum Tier { Fast, Standard, Frontier }
    enum Outcome { None, Allow, Deny, Hold, Partial, Release, Stop }

    struct Jury { uint8 size; uint8 quorum; Tier tier; }

    /// Ask the jury to rule on `action` under the published ASC `ascId`.
    /// Emits RulingRequested; the jury answers off chain with signed votes.
    function requestRuling(bytes32 ascId, bytes calldata action, Jury calldata jury)
        external returns (bytes32 requestId);

    /// Check the jurors' signed votes for `requestId` and return the outcome
    /// they reached. Reverts if signatures, juror keys, ASC id or quorum don't check out.
    function verifyRuling(bytes32 requestId, bytes calldata attestations)
        external view returns (Outcome);

    event RulingRequested(bytes32 indexed requestId, bytes32 indexed ascId, bytes32 actionHash, Jury jury);
}
ConstitutionGuard.solSolidityillustrative
// SPDX-License-Identifier: MIT
// A Governor that won't execute a passed proposal until the constitution's jury allows it.
// Two steps, because an EVM call can't wait for models: request, then execute with signatures.
// Illustrative. Not audited or deployed.
pragma solidity ^0.8.24;

import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
import {IAsc} from "@verafy/asc-contracts/IAsc.sol";

abstract contract ConstitutionGuard is Governor {
    IAsc public immutable asc;
    bytes32 public immutable CONSTITUTION; // ASC id of constitution.asc.md

    mapping(uint256 => bytes32) public rulingOf; // proposalId => ruling request id

    constructor(IAsc _asc, bytes32 constitution) { asc = _asc; CONSTITUTION = constitution; }

    /// Step 1: anyone can ask the jury once a proposal has passed.
    function requestRuling(uint256 proposalId) external {
        require(state(proposalId) == ProposalState.Succeeded, "not passed");
        rulingOf[proposalId] = asc.requestRuling(
            CONSTITUTION,
            abi.encode(proposalId, block.chainid),
            IAsc.Jury({size: 7, quorum: 5, tier: IAsc.Tier.Frontier})
        );
    }

    /// Step 2: execution carries the jurors' signed rulings.
    function executeWithRuling(
        address[] memory targets, uint256[] memory values, bytes[] memory calldatas,
        bytes32 descriptionHash, bytes calldata attestations
    ) external payable returns (uint256 id) {
        id = hashProposal(targets, values, calldatas, descriptionHash);
        require(asc.verifyRuling(rulingOf[id], attestations) == IAsc.Outcome.Allow, "constitution: blocked");
        return execute(targets, values, calldatas, descriptionHash);
    }

    // Plain execute() is closed, so nothing skips the check.
    function _executeOperations(uint256 id, address[] memory t, uint256[] memory v, bytes[] memory c, bytes32 d)
        internal override
    {
        require(rulingOf[id] != bytes32(0), "constitution: request a ruling first");
        super._executeOperations(id, t, v, c, d);
    }
}

Full sample: A DAO constitution that can block a vote.

Keep hard caps in your code

Hard caps stay in the contract's own code. Never let a ruling raise a cap. A ruling only chooses among the outcomes your ASC lists, and the cap check runs whatever the jury said.