Skip to content

Commit 311cce9

Browse files
authored
Merge pull request #731 from unlimitedengineer/test/bonding-curve-monotonicity-697
test: add integration tests for bonding curve ten-buy price monotonicity
2 parents e629b06 + b7d6cbe commit 311cce9

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
//! Integration test confirming the bonding curve price increases monotonically
2+
//! across ten sequential single-key buys, and decreases after a sell (Issue #697).
3+
//!
4+
//! Covers all four acceptance criteria:
5+
//! AC-1 Price strictly increases after each sequential buy.
6+
//! AC-2 Price strictly decreases after a sell.
7+
//! AC-3 Price at supply 0 equals the configured base price.
8+
//! AC-4 All ten price snapshots are monotonically increasing.
9+
10+
mod contract_test_env;
11+
12+
use contract_test_env::{
13+
register_creator_keys, register_test_creator, set_curve_slope, set_pricing_and_fees,
14+
test_env_with_auths,
15+
};
16+
use soroban_sdk::testutils::Address as _;
17+
18+
const BASE_PRICE: i128 = 5_000;
19+
const CURVE_SLOPE: i128 = 100;
20+
const NUM_BUYS: usize = 10;
21+
22+
/// AC-3: `get_buy_quote` at supply 0 returns a price equal to BASE_PRICE.
23+
/// AC-1 + AC-4: all ten buy-quote prices are strictly increasing.
24+
/// AC-2: one sell after ten buys yields a price strictly below the last buy price.
25+
#[test]
26+
fn test_ten_sequential_buys_price_strictly_increases_and_sell_decreases() {
27+
let env = test_env_with_auths();
28+
let (client, _) = register_creator_keys(&env);
29+
30+
set_pricing_and_fees(&env, &client, BASE_PRICE, 9000, 1000);
31+
set_curve_slope(&env, &client, CURVE_SLOPE);
32+
33+
let creator = register_test_creator(&env, &client, "alice");
34+
let holder = soroban_sdk::Address::generate(&env);
35+
36+
// AC-3: price at supply 0 must equal BASE_PRICE.
37+
let price_at_zero = client.get_buy_quote(&creator).price;
38+
assert_eq!(
39+
price_at_zero, BASE_PRICE,
40+
"price at supply 0 must equal the configured base price"
41+
);
42+
43+
// AC-1 + AC-4: record the quote price before each buy (price at supply i),
44+
// execute the buy, then assert each successive quote is strictly greater.
45+
// Buy 0 goes to `holder` so they hold a key for the sell assertion below.
46+
let mut buy_prices: Vec<i128> = Vec::new();
47+
48+
for i in 0..NUM_BUYS {
49+
let quote = client.get_buy_quote(&creator);
50+
51+
if let Some(&prev_price) = buy_prices.last() {
52+
assert!(
53+
quote.price > prev_price,
54+
"buy {} price ({}) must be strictly greater than buy {} price ({})",
55+
i + 1,
56+
quote.price,
57+
i,
58+
prev_price
59+
);
60+
}
61+
62+
buy_prices.push(quote.price);
63+
64+
let buyer = if i == 0 {
65+
holder.clone()
66+
} else {
67+
soroban_sdk::Address::generate(&env)
68+
};
69+
70+
client.buy_key(&creator, &buyer, &quote.total_amount, &None);
71+
}
72+
73+
assert_eq!(buy_prices.len(), NUM_BUYS);
74+
75+
// AC-4: full monotonicity check across the collected snapshots.
76+
for (index, window) in buy_prices.windows(2).enumerate() {
77+
assert!(
78+
window[1] > window[0],
79+
"price snapshot {} ({}) must be strictly greater than snapshot {} ({})",
80+
index + 2,
81+
window[1],
82+
index + 1,
83+
window[0]
84+
);
85+
}
86+
87+
// AC-2: price after one sell must be strictly less than the last buy price.
88+
let price_before_sell = client.get_buy_quote(&creator).price;
89+
client.sell_key(&creator, &holder, &None);
90+
let price_after_sell = client.get_buy_quote(&creator).price;
91+
92+
assert!(
93+
price_after_sell < price_before_sell,
94+
"price after sell ({}) must be strictly less than price before sell ({})",
95+
price_after_sell,
96+
price_before_sell
97+
);
98+
}
99+
100+
/// Regression guard: a flat (zero-slope) curve does not produce strictly
101+
/// increasing prices, confirming the main test above is actually discriminating.
102+
#[test]
103+
fn test_flat_curve_prices_do_not_strictly_increase_across_ten_buys() {
104+
let env = test_env_with_auths();
105+
let (client, _) = register_creator_keys(&env);
106+
107+
// No set_curve_slope call — defaults to 0 (flat).
108+
set_pricing_and_fees(&env, &client, BASE_PRICE, 9000, 1000);
109+
110+
let creator = register_test_creator(&env, &client, "bob");
111+
112+
let mut prices: Vec<i128> = Vec::new();
113+
114+
for _ in 0..NUM_BUYS {
115+
let quote = client.get_buy_quote(&creator);
116+
prices.push(quote.price);
117+
let buyer = soroban_sdk::Address::generate(&env);
118+
client.buy_key(&creator, &buyer, &quote.total_amount, &None);
119+
}
120+
121+
let strictly_increasing = prices.windows(2).all(|w| w[1] > w[0]);
122+
assert!(
123+
!strictly_increasing,
124+
"flat pricing must not produce strictly increasing prices across ten buys"
125+
);
126+
}
127+
128+
/// Validates the ten price snapshots against the bonding curve formula
129+
/// `price = base_price + slope * supply` for every step.
130+
#[test]
131+
fn test_ten_buy_prices_match_bonding_curve_formula() {
132+
let env = test_env_with_auths();
133+
let (client, _) = register_creator_keys(&env);
134+
135+
set_pricing_and_fees(&env, &client, BASE_PRICE, 9000, 1000);
136+
set_curve_slope(&env, &client, CURVE_SLOPE);
137+
138+
let creator = register_test_creator(&env, &client, "carol");
139+
140+
for i in 0..NUM_BUYS {
141+
let quote = client.get_buy_quote(&creator);
142+
143+
let expected = BASE_PRICE + CURVE_SLOPE * i as i128;
144+
assert_eq!(
145+
quote.price, expected,
146+
"price at supply {} must be {} (base_price + slope * supply)",
147+
i, expected
148+
);
149+
150+
let buyer = soroban_sdk::Address::generate(&env);
151+
client.buy_key(&creator, &buyer, &quote.total_amount, &None);
152+
}
153+
154+
assert_eq!(
155+
client.get_total_key_supply(&creator),
156+
NUM_BUYS as u32,
157+
"supply must equal ten after ten buys"
158+
);
159+
}

0 commit comments

Comments
 (0)