From 47445cfa4f27ba0eb3e8fe20208898222bc03c4f Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Tue, 3 Sep 2024 15:23:10 +0530 Subject: [PATCH 1/2] Property test on CPMM buy and sell --- contract/Cargo.lock | 305 ++++++++++++++++++++++++++++++++++++++++ contract/Cargo.toml | 1 + contract/src/api.rs | 2 +- contract/src/execute.rs | 53 +++---- contract/src/tests.rs | 59 +++++++- 5 files changed, 394 insertions(+), 26 deletions(-) diff --git a/contract/Cargo.lock b/contract/Cargo.lock index acb69b7..47b277f 100644 --- a/contract/Cargo.lock +++ b/contract/Cargo.lock @@ -19,6 +19,12 @@ version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "base16ct" version = "0.2.0" @@ -43,6 +49,27 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + [[package]] name = "block-buffer" version = "0.9.0" @@ -367,6 +394,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + [[package]] name = "ff" version = "0.13.0" @@ -377,6 +420,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "forward_ref" version = "1.0.0" @@ -484,6 +533,12 @@ dependencies = [ "signature", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "levana-predict" version = "0.1.0" @@ -493,6 +548,7 @@ dependencies = [ "cw-multi-test", "cw-storage-plus", "cw2", + "proptest", "schemars", "semver", "serde", @@ -507,6 +563,28 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + [[package]] name = "once_cell" version = "1.19.0" @@ -529,6 +607,15 @@ dependencies = [ "spki", ] +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.78" @@ -538,6 +625,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.12.3" @@ -561,6 +668,12 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.35" @@ -570,6 +683,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -585,6 +719,21 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + [[package]] name = "rfc6979" version = "0.4.0" @@ -595,12 +744,37 @@ dependencies = [ "subtle", ] +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustversion" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "ryu" version = "1.0.16" @@ -799,6 +973,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "thiserror" version = "1.0.58" @@ -825,6 +1012,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -837,12 +1030,124 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/contract/Cargo.toml b/contract/Cargo.toml index 1e2fdc6..37f9f86 100644 --- a/contract/Cargo.toml +++ b/contract/Cargo.toml @@ -32,3 +32,4 @@ semver = "1.0.23" [dev-dependencies] cosmwasm-schema = "1.5.0" cw-multi-test = "0.20.0" +proptest = "1.5.0" diff --git a/contract/src/api.rs b/contract/src/api.rs index 035be1b..da35890 100644 --- a/contract/src/api.rs +++ b/contract/src/api.rs @@ -46,7 +46,7 @@ pub enum ExecuteMsg { pub struct AddMarketParams { pub title: String, pub description: String, - /// Wallet address + /// Wallet address. Sets the winner. pub arbitrator: String, pub outcomes: Vec, /// Denom of collateral for this market. diff --git a/contract/src/execute.rs b/contract/src/execute.rs index 200599b..0c1df68 100644 --- a/contract/src/execute.rs +++ b/contract/src/execute.rs @@ -43,6 +43,34 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> R } } +pub(crate) fn to_stored_outcome(outcomes: Vec) -> (Vec, Collateral) { + let mut total = Collateral::zero(); + let outcomes = outcomes + .into_iter() + .enumerate() + .map( + |( + idx, + OutcomeDef { + label, + initial_amount, + }, + )| { + total += initial_amount; + let id = OutcomeId(u8::try_from(idx + 1).unwrap()); + let pool_tokens = Token(Decimal256::from_ratio(initial_amount.0, 1u8)); + StoredOutcome { + id, + label, + pool_tokens, + total_tokens: pool_tokens, + } + }, + ) + .collect(); + (outcomes, total) +} + fn add_market( deps: DepsMut, env: Env, @@ -79,30 +107,7 @@ fn add_market( .map_or_else(MarketId::one, MarketId::next); LAST_MARKET_ID.save(deps.storage, &id)?; let arbitrator = deps.api.addr_validate(&arbitrator)?; - let mut total = Collateral::zero(); - let outcomes = outcomes - .into_iter() - .enumerate() - .map( - |( - idx, - OutcomeDef { - label, - initial_amount, - }, - )| { - total += initial_amount; - let id = OutcomeId(u8::try_from(idx + 1).unwrap()); - let pool_tokens = Token(Decimal256::from_ratio(initial_amount.0, 1u8)); - StoredOutcome { - id, - label, - pool_tokens, - total_tokens: pool_tokens, - } - }, - ) - .collect(); + let (outcomes, total) = to_stored_outcome(outcomes); if total != funds { return Err(Error::IncorrectFundsPerOutcome { provided: funds, diff --git a/contract/src/tests.rs b/contract/src/tests.rs index 642388a..0d63474 100644 --- a/contract/src/tests.rs +++ b/contract/src/tests.rs @@ -1,7 +1,8 @@ use cosmwasm_std::Addr; use cw_multi_test::{error::AnyResult, App, AppResponse, ContractWrapper, Executor}; +use proptest::prelude::*; -use crate::prelude::*; +use crate::{execute::to_stored_outcome, prelude::*}; struct Predict { app: App, @@ -153,3 +154,59 @@ fn sanity() { .unwrap(); assert_eq!(Uint128::from(1000u16), amount_after); } + +proptest! { +#[test] +fn test_cpmm_buy_sell(pool_one in 1..1000u32, pool_two in 1..1000u32, buy in 2..50u32) { + let pool_one_collateral = Collateral(pool_one.into()); + let pool_two_collateral = Collateral(pool_two.into()); + + let buy = pool_one_collateral * Decimal256::from_ratio(1u32, buy); + let buy = buy.unwrap(); + + let pool_one = OutcomeDef { + label: "Yes".to_owned(), + initial_amount: pool_one_collateral, + }; + let pool_two = OutcomeDef { + label: "No".to_owned(), + initial_amount: pool_two_collateral, + }; + let outcomes = vec![pool_one, pool_two]; + let (outcomes, total) = to_stored_outcome(outcomes); + let mut original_variant = Decimal256::one(); + for outcome in &outcomes { + original_variant *= outcome.pool_tokens.0; + } + + let ts = Timestamp::from_nanos(1_000_000_202); + + let mut stored = StoredMarket { + id: MarketId::one(), + title: "ATOM_USDT".to_owned(), + description: "Some desc".to_owned(), + arbitrator: Addr::unchecked("arbitrator"), + outcomes, + denom: DENOM.to_owned(), + deposit_fee: "0.01".parse().unwrap(), + withdrawal_fee: "0.01".parse().unwrap(), + pool_size: total, + deposit_stop_date: ts.plus_days(2), + withdrawal_stop_date: ts.plus_days(1), + winner: None, + house: Addr::unchecked("house"), + }; + let yes_id = OutcomeId(1); + let yes_tokens = stored.buy(yes_id, buy).unwrap(); + let _funds = stored.sell(yes_id, yes_tokens).unwrap(); + + let mut new_variant = Decimal256::one(); + for outcome in &stored.outcomes { + new_variant *= outcome.pool_tokens.0; + } + + let diff = original_variant.abs_diff(new_variant); + + assert!(diff < Decimal256::from_ratio(1u32, 10u32)); +} +} From 952d9a791f3f2554a7795d9390147a9b767e1d5c Mon Sep 17 00:00:00 2001 From: Sibi Prabakaran Date: Tue, 3 Sep 2024 16:08:04 +0530 Subject: [PATCH 2/2] Add mid variant case too --- contract/src/tests.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/contract/src/tests.rs b/contract/src/tests.rs index 0d63474..70aef7f 100644 --- a/contract/src/tests.rs +++ b/contract/src/tests.rs @@ -198,6 +198,11 @@ fn test_cpmm_buy_sell(pool_one in 1..1000u32, pool_two in 1..1000u32, buy in 2.. }; let yes_id = OutcomeId(1); let yes_tokens = stored.buy(yes_id, buy).unwrap(); + let mut mid_variant = Decimal256::one(); + for outcome in &stored.outcomes { + mid_variant *= outcome.pool_tokens.0; + } + let _funds = stored.sell(yes_id, yes_tokens).unwrap(); let mut new_variant = Decimal256::one(); @@ -205,8 +210,10 @@ fn test_cpmm_buy_sell(pool_one in 1..1000u32, pool_two in 1..1000u32, buy in 2.. new_variant *= outcome.pool_tokens.0; } - let diff = original_variant.abs_diff(new_variant); + let diff1 = original_variant.abs_diff(new_variant); + let diff2 = original_variant.abs_diff(mid_variant); - assert!(diff < Decimal256::from_ratio(1u32, 10u32)); + assert!(diff1 < Decimal256::from_ratio(1u32, 10u32)); + assert!(diff2 < Decimal256::from_ratio(1u32, 10u32)); } }