Skip to content

Commit

Permalink
Merge pull request #12 from informalsystems/gabriela/snapshot-tests
Browse files Browse the repository at this point in the history
Add snapshot tests for CTF contracts
  • Loading branch information
bugarela authored Apr 4, 2024
2 parents c91878c + e758e8b commit 527b3c6
Show file tree
Hide file tree
Showing 15 changed files with 1,872 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "tests/fixtures/cosmwasm-ctf"]
path = tests/fixtures/cosmwasm-ctf
url = git@github.com:bugarela/cosmwasm-ctf.git
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ rustc_plugin = "0.8.0-nightly-2024-01-06"
env_logger = "0.10"
clap = {version = "4.4", features = ["derive"]}
serde = {version = "1", features = ["derive"]}
rustc_utils = {version = "0.8.0-nightly-2024-01-06", features = ["test"]}

[dev-dependencies]
anyhow = {version = "1", features = ["backtrace"]}
insta = {version = "1.22.0", features = ["redactions"]}
8 changes: 7 additions & 1 deletion default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ with (import <nixpkgs> { });
let
shell = mkShell {
name = "rust-env";
buildInputs = [ pkgs.rustup pkgs.rustc pkgs.rust-analyzer pkgs.rustic-rs ];
buildInputs = [
pkgs.rustup
pkgs.rustc
pkgs.rust-analyzer
pkgs.rustic-rs
pkgs.cargo-insta
];
shellHook = ''
rustup toolchain add nightly-2024-01-06
rustup override set nightly-2024-01-06
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/cosmwasm-ctf
Submodule cosmwasm-ctf added at 2dfdb8
120 changes: 120 additions & 0 deletions tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use std::{env, fs, path::Path, process::Command, sync::Once};

use anyhow::{ensure, Context, Result};

static SETUP: Once = Once::new();

fn run(dir: &str, f: impl FnOnce(&mut Command)) -> Result<String> {
let root = env::temp_dir().join("cosmwasm-to-quint");

let heredir = Path::new(".").canonicalize()?;

SETUP.call_once(|| {
let mut cmd = Command::new("cargo");
cmd.args(["install", "--path", ".", "--debug", "--locked", "--root"]);
cmd.arg(&root);
cmd.current_dir(&heredir);
let status = cmd.status().unwrap();
if !status.success() {
panic!("installing cosmwasm-to-quint failed")
}
});

let mut cmd = Command::new("cargo");
cmd.arg("cosmwasm-to-quint");

let path = format!(
"{}:{}",
root.join("bin").display(),
env::var("PATH").unwrap_or_else(|_| "".into())
);
cmd.env("PATH", path);

let ws = heredir.join("tests").join("fixtures").join(dir);
cmd.current_dir(&ws);

f(&mut cmd);

let _ = fs::remove_dir_all(ws.join("target"));

let output = cmd.output().context("Process failed AA")?;
ensure!(
output.status.success(),
"Process exited with non-zero exit code. Stderr:\n{}",
String::from_utf8(output.stderr)?
);

Ok(String::from_utf8(output.stdout)?)
}

// TODO: why do these tests need to be run sequentially?

#[test]
fn ctf01() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-01", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}

#[test]
fn ctf02() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-02", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}

#[test]
fn ctf03() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-03", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}

#[test]
fn ctf04() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-04", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}

#[test]
fn ctf05() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-05", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}

#[test]
fn ctf06() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-06", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}

#[test]
fn ctf07() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-07", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}

#[test]
fn ctf08() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-08", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}

#[test]
fn ctf09() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-09", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}

#[test]
fn ctf10() -> Result<()> {
let output = run("cosmwasm-ctf/ctf-10", |_cmd| {})?;
insta::assert_snapshot!(output);
Ok(())
}
140 changes: 140 additions & 0 deletions tests/snapshots/integration_tests__ctf01.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
source: tests/integration_tests.rs
expression: output
---
module oaksecurity_cosmwasm_ctf_01 {

import basicSpells.* from "../lib/basicSpells"
import cw_types.* from "../lib/cw_types"
import messaging.* from "../lib/messaging"
import bank from "../lib/bank"


var contract_state: ContractState
var return: Result
var bank: bank::Bank
var time: int


pure val CONTRACT_ADDRESS = "<contract>"


pure val ADDRESSES = Set("s1", "s2", "s3", CONTRACT_ADDRESS)
pure val DENOMS = Set("d1", "uawesome")
pure val MAX_AMOUNT = 200


type InstantiateMsg = { count: int }
type Lockup = { id: int, owner: Addr, amount: int, release_timestamp: int }
type ExecuteMsg =
| ExecuteMsg_Deposit
| ExecuteMsg_Withdraw({ ids: List[int] })
pure def instantiate(state: ContractState, _env: Env, _info: MessageInfo, _msg: InstantiateMsg): (Result[Response, ContractError], ContractState) = (Ok(Response_new), state)
pure def execute(state: ContractState, env: Env, info: MessageInfo, msg: ExecuteMsg): (Result[Response, ContractError], ContractState) = match msg {
| ExecuteMsg_Deposit(__r) => deposit(state, env, info)
| ExecuteMsg_Withdraw(__r) => withdraw(state, env, info, __r.ids)
}
pure def deposit(state: ContractState, env: Env, info: MessageInfo): (Result[Response, ContractError], ContractState) = (Ok(Response_new), state)

action deposit_action = {
// TODO: Change next line according to fund expectations
pure val max_funds = MAX_AMOUNT
nondet message: ExecuteMsg = ExecuteMsg_Deposit.oneOf()
execute_message(message, max_funds)
}
pure def withdraw(state: ContractState, env: Env, info: MessageInfo, ids: List[int]): (Result[Response, ContractError], ContractState) = (Ok(Response_new), state)

action withdraw_action = {
// TODO: Change next line according to fund expectations
pure val max_funds = MAX_AMOUNT
val possibilities = 0.to(MAX_AMOUNT).map(i => Some(i)).union(Set(None))
nondet v1 = possibilities.oneOf()
nondet v2 = possibilities.oneOf()
nondet v3 = possibilities.oneOf()
pure val message_ids: List[int] =
[v1, v2, v3].foldl([], (acc, v) => match v {
| Some(i) => acc.append(i)
| None => acc
})

pure val message: ExecuteMsg = ExecuteMsg_Withdraw({ ids: message_ids })
execute_message(message, max_funds)
}
pure val DENOM = "uawesome"
pure val MINIMUM_DEPOSIT_AMOUNT = 10000
pure val LOCK_PERIOD = 60 * 60 * 24

type ContractState = {
last_id: int,
lockups: int -> Lockup
}

pure val init_contract_state = {
last_id: 0,
lockups: Map()
}

action execute_step = all {
any {
deposit_action,
withdraw_action
},
advance_time,
}


pure def reply(state: ContractState, _env: Env, _reply: Reply): (Result, ContractState) = (Ok(Response_new), state)



pure val init_bank_state = ADDRESSES.mapBy(_ => DENOMS.mapBy(_ => MAX_AMOUNT))

val env_val = { block: { time: time } }

action init = all {
contract_state' = init_contract_state,
bank' = init_bank_state,
return' = Err("No previous request"),
time' = 0,
}


action execute_message(message, max_funds) = {
nondet sender = ADDRESSES.oneOf()
nondet denom = DENOMS.oneOf()
nondet amount = 0.to(max_funds).oneOf()
val funds = [{ denom: denom, amount: amount }]
val info = { sender: sender, funds: funds }

val r = execute(contract_state, env_val, info, message)
all {
bank.get(sender).get(denom) >= amount,
bank' = bank.setBy(sender, balances => balances.setBy(denom, balance => balance - amount))
.setBy(CONTRACT_ADDRESS, balances => balances.setBy(denom, balance => balance + amount)),
return' = r._1,
contract_state' = r._2,
}
}

action advance_time = time' = time + 1

action step = {
val message_getting = get_message(return)
val new_return = message_getting._1
val opt_message = message_getting._2
match opt_message {
| Some(submsg) => {
val current_state = { bank: bank, return: new_return, contract_state: contract_state }
val new_state = process_message(current_state, env_val, CONTRACT_ADDRESS, submsg, reply)
all {
bank' = new_state.bank,
return' = new_state.return,
contract_state' = new_state.contract_state,
advance_time,
}
}
| None => execute_step
}
}

}
Loading

0 comments on commit 527b3c6

Please sign in to comment.