The Rust Contracts

The content on this page assumes that you already have Rust and Cargo installed in your machine

First, clone the repo:

git clone git@github.com:aufacicenta/pulsemarkets.git prompt-wars

Contract files structure:

.
├── market-factory
│   ├── Cargo.lock
│   ├── Cargo.toml
│   ├── build.sh
│   └── src
│       ├── callbacks.rs
│       ├── consts.rs
│       ├── contract.rs
│       ├── lib.rs
│       ├── storage.rs
│       ├── tests.rs
│       └── views.rs
└── prompt-wars
    ├── Cargo.lock
    ├── Cargo.toml
    ├── README.md
    ├── build.sh
    └── src
        ├── callbacks.rs
        ├── consts.rs
        ├── contract.rs
        ├── flags.rs
        ├── ft_receiver.rs
        ├── lib.rs
        ├── math.rs
        ├── modifiers.rs
        ├── outcome_token.rs
        ├── storage.rs
        ├── tests.rs
        └── views.rs

You'll want to focus on the contract.rs files most of the time, but storage.rs  is your starting point, for example prompt-wars/src/storage.rs holds all the public structs that the game uses:

use near_sdk::{
    borsh::{self, BorshDeserialize, BorshSerialize},
    collections::{LookupMap, Vector},
    near_bindgen,
    serde::{Deserialize, Serialize},
    AccountId, BorshStorageKey,
};
use shared::OutcomeId;

pub type Timestamp = i64;
pub type WrappedBalance = u128;
pub type OutcomeTokenResult = f32;
pub type ResolutionResult = OutcomeId;

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Debug, PartialEq))]
#[serde(crate = "near_sdk::serde")]
pub struct MarketData {
    // The IPFS reference-image hash of the expected prompts
    pub image_uri: String,
    // Datetime nanos: the market is open
    pub starts_at: Timestamp,
    // Datetime nanos: the market is closed
    pub ends_at: Timestamp,
}

#[near_bindgen]
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Market {
    // Market metadata
    pub market: MarketData,
    // NEP141 token metadata
    pub collateral_token: CollateralToken,
    // MArket resolution metadata
    pub resolution: Resolution,
    // Market management account ids metadata
    pub management: Management,
    // Keeps track of Outcomes prices and balances
    pub outcome_tokens: LookupMap<AccountId, OutcomeToken>,
    // Keeps track of the outcome_ids that have bet on the market
    pub players: Vector<AccountId>,
    // Market fees metadata
    pub fees: Fees,
}

#[derive(Serialize, Deserialize)]
pub enum SetPriceOptions {
    Increase,
    Decrease,
}

#[derive(BorshSerialize, BorshDeserialize, Serialize)]
pub struct OutcomeToken {
    // the account id of the outcome_token
    pub outcome_id: OutcomeId,
    // the outcome value, in this case, the prompt submitted to the competition
    pub prompt: String,
    // the outcome value, in this case, the prompt submitted to the competition
    pub output_img_uri: Option<String>,
    // store the result from the image comparison: percentage_diff or pixel_difference
    pub result: Option<OutcomeTokenResult>,
    // total supply of this outcome_token
    pub total_supply: WrappedBalance,
}

#[derive(BorshSerialize, BorshDeserialize, Deserialize, Serialize, Clone)]
pub struct CollateralToken {
    pub id: AccountId,
    pub balance: WrappedBalance,
    pub decimals: u8,
    pub fee_balance: WrappedBalance,
}

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)]
pub struct Resolution {
    // Time to free up the market
    pub window: Timestamp,
    // Time after the market ends and before the resolution window starts
    pub reveal_window: Timestamp,
    // When the market is resolved, set only by fn resolve
    pub resolved_at: Option<Timestamp>,
    // When the market is resolved, set only by fn resolve
    pub result: Option<ResolutionResult>,
}

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)]
pub struct Management {
    // Gets sent fees when claiming window is open
    pub dao_account_id: AccountId,
    // Gets back the storage deposit upon self-destruction
    pub market_creator_account_id: AccountId,
    // Set at initialization, the market may be destroyed by the creator to claim the storage deposit
    pub self_destruct_window: Timestamp,
    // Set at initialization, determines when to close bets
    pub buy_sell_threshold: f32,
}

#[derive(BorshSerialize, BorshDeserialize, Deserialize, Serialize, Clone)]
pub struct Fees {
    // Price to charge when creating an outcome token
    pub price: WrappedBalance,
    // Decimal fee to charge upon a bet
    pub fee_ratio: WrappedBalance,
    // When fees got sent to the DAO
    pub claimed_at: Option<Timestamp>,
}

#[derive(BorshStorageKey, BorshSerialize)]
pub enum StorageKeys {
    OutcomeTokens,
    StakingFees,
    MarketCreatorFees,
}

#[derive(Serialize, Deserialize)]
pub struct CreateOutcomeTokenArgs {
    // the outcome value, in this case, the prompt submitted to the competition
    pub prompt: String,
}

#[derive(Serialize, Deserialize)]
pub enum Payload {
    CreateOutcomeTokenArgs(CreateOutcomeTokenArgs),
}

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize, Clone)]
pub struct Ix {
    pub address: [u8; 32],
}

Note that these structs, have their corresponding Typescript definitions:

app/src/providers/near/contracts/prompt-wars/prompt-wars.types.ts
type Timestamp = number;
type WrappedBalance = number;
export type AccountId = string;
export type OutcomeId = AccountId;
export type OutcomeTokenResult = number;

export type Prompt = {
  value: string;
  negative_prompt?: string;
};

export type ResolutionResult = AccountId;

export type MarketData = {
  image_uri: string;
  starts_at: Timestamp;
  ends_at: Timestamp;
};

export type Fees = {
  price: WrappedBalance;
  fee_ratio: WrappedBalance;
  claiming_window?: Timestamp;
};

export type Management = {
  dao_account_id: AccountId;
  self_destruct_window: Timestamp;
  buy_sell_threshold: number;
  market_creator_account_id?: AccountId;
};

export type CollateralToken = {
  id: string;
  balance: number;
  decimals: number;
  fee_balance: WrappedBalance;
};

export type Resolution = {
  window: Timestamp;
  reveal_window: Timestamp;
  resolved_at?: Timestamp;
  result?: string;
};

export type OutcomeToken = {
  outcome_id: OutcomeId;
  prompt: string;
  total_supply: WrappedBalance;
  result?: OutcomeTokenResult;
  output_img_uri?: string;
};

export type PromptWarsMarketContractValues = {
  market: MarketData;
  resolution: Resolution;
  fees: Fees;
  management: Management;
  collateralToken: CollateralToken;
  outcomeIds: Array<OutcomeId>;
  isResolved: boolean;
  isOpen: boolean;
  isOver: boolean;
  isRevealWindowExpired: boolean;
  isResolutionWindowExpired: boolean;
  isExpiredUnresolved: boolean;

  status: PromptWarsMarketContractStatus;
};

export enum PromptWarsMarketContractStatus {
  LOADING = "Loading",
  OPEN = "Open",
  REVEALING = "Revealing",
  RESOLVING = "Resolving",
  RESOLVED = "Resolved",
  UNRESOLVED = "Unresolved",
  CLOSED = "Closed",
}

export type GetOutcomeTokenArgs = {
  outcome_id: OutcomeId;
};

export type PromptWarsMarketContractMethods = {
  get_market_data: () => Promise<MarketData>;
  get_resolution_data: () => Promise<Resolution>;
  get_fee_data: () => Promise<Fees>;
  get_management_data: () => Promise<Management>;
  get_collateral_token_metadata: () => Promise<CollateralToken>;
  get_outcome_token: (args: GetOutcomeTokenArgs) => Promise<OutcomeToken>;
  get_outcome_ids: () => Promise<Array<AccountId>>;
  get_block_timestamp: () => Promise<Timestamp>;
  resolved_at: () => Promise<Timestamp>;
  balance_of: (outcome_id: OutcomeId) => Promise<WrappedBalance>;
  get_amount_mintable: (amount: WrappedBalance) => Promise<Array<WrappedBalance>>;
  get_amount_payable_unresolved: () => Promise<Array<WrappedBalance>>;
  get_amount_payable_resolved: () => Promise<Array<WrappedBalance>>;
  get_precision_decimals: () => Promise<WrappedBalance>;
  // flags
  is_resolved: () => Promise<boolean>;
  is_open: () => Promise<boolean>;
  is_over: () => Promise<boolean>;
  is_reveal_window_expired: () => Promise<boolean>;
  is_resolution_window_expired: () => Promise<boolean>;
  is_self_destruct_window_expired: () => Promise<boolean>;
  is_expired_unresolved: () => Promise<boolean>;
  // mutators
  sell: (args: { name: string; args: string }, gas?: number, amount?: string | null) => Promise<boolean>;
  reveal: (
    args: { outcome_id: OutcomeId; result: OutcomeTokenResult },
    gas?: number,
    amount?: string | null,
  ) => Promise<boolean>;
  resolve: (args: { name: string; args: string }, gas?: number, amount?: string | null) => Promise<boolean>;
};

Testing Rust contracts

Both prompt-wars and market-factory have a tests.rs file. If you make changes to any of these, you may check for errors by running:

cargo test -- --nocapture 

This command should result in 100% passing tests:

test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests promptwars

Last updated