forked from SO4-Markets/contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontract_upgrade.rs
More file actions
215 lines (183 loc) · 8.3 KB
/
Copy pathcontract_upgrade.rs
File metadata and controls
215 lines (183 loc) · 8.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
//! Integration tests for issue #267: contract upgrade — deploy v2 and verify existing state.
//!
//! Scenarios:
//! 1. Non-admin upgrade attempt reverts with auth error
//! 2. Admin upgrade succeeds (auth gate open)
//! 3. After upgrade, pending order metadata in persistent storage is still readable
//!
//! Note: Soroban's test environment cannot execute a true WASM swap (there is no
//! second compiled binary to load), so the "state preservation" test stores an order,
//! calls `upgrade` (which will panic at WASM lookup in the mock env), and verifies
//! the persistent storage layer was not touched before the WASM lookup step.
//! The test is therefore marked `#[ignore]` for CI and serves as a specification
//! contract for the upgrade path.
#![cfg(test)]
use data_store::{DataStore, DataStoreClient as DsClient};
use gmx_keys::{market_index_token_key, market_long_token_key, market_short_token_key, roles};
use gmx_math::FLOAT_PRECISION;
use gmx_types::{CreateOrderParams, OrderType, TokenPrice};
use market_token::{MarketToken, MarketTokenClient as MtClient};
use oracle::{Oracle, OracleClient as OClient};
use order_handler::{OrderHandler, OrderHandlerClient as OHClient};
use order_vault::{OrderVault, OrderVaultClient as OVClient};
use role_store::{RoleStore, RoleStoreClient as RsClient};
use soroban_sdk::{
testutils::Address as _, token::StellarAssetClient, Address, BytesN, Env, Vec,
};
const ONE_TOKEN: i128 = 10_000_000;
const ONE_USD: i128 = FLOAT_PRECISION;
struct World {
env: Env,
admin: Address,
keeper: Address,
user: Address,
rs: Address,
ds: Address,
oracle: Address,
ord_vault: Address,
ord_handler: Address,
market_tk: Address,
long_tk: Address,
index_tk: Address,
}
fn setup() -> World {
let env = Env::default();
env.mock_all_auths();
env.cost_estimate().budget().reset_unlimited();
let admin = Address::generate(&env);
let keeper = Address::generate(&env);
let user = Address::generate(&env);
let rs = env.register(RoleStore, ());
let rs_c = RsClient::new(&env, &rs);
rs_c.initialize(&admin);
rs_c.grant_role(&admin, &admin, &roles::controller(&env));
rs_c.grant_role(&admin, &keeper, &roles::order_keeper(&env));
let ds = env.register(DataStore, ());
DsClient::new(&env, &ds).initialize(&admin, &rs);
let oracle_addr = env.register(Oracle, ());
let passphrase = soroban_sdk::Bytes::from_slice(&env, b"Test SDF Network ; September 2015");
OClient::new(&env, &oracle_addr).initialize(&admin, &rs, &ds, &passphrase);
let ord_vault = env.register(OrderVault, ());
OVClient::new(&env, &ord_vault).initialize(&admin, &rs);
let market_tk = env.register(MarketToken, ());
MtClient::new(&env, &market_tk).initialize(
&admin,
&rs,
&7u32,
&soroban_sdk::String::from_str(&env, "ETH/USD Market"),
&soroban_sdk::String::from_str(&env, "GM-ETH"),
);
let long_tk = env.register_stellar_asset_contract_v2(admin.clone()).address();
let index_tk = Address::generate(&env);
let ord_handler = env.register(OrderHandler, ());
OHClient::new(&env, &ord_handler).initialize(&admin, &rs, &ds, &oracle_addr, &ord_vault);
rs_c.grant_role(&admin, &ord_handler, &roles::controller(&env));
let ds_c = DsClient::new(&env, &ds);
ds_c.set_address(&admin, &market_index_token_key(&env, &market_tk), &index_tk);
ds_c.set_address(&admin, &market_long_token_key(&env, &market_tk), &long_tk);
ds_c.set_address(&admin, &market_short_token_key(&env, &market_tk), &long_tk);
StellarAssetClient::new(&env, &long_tk).mint(&user, &(10_000 * ONE_TOKEN));
World {
env,
admin,
keeper,
user,
rs,
ds,
oracle: oracle_addr,
ord_vault,
ord_handler,
market_tk,
long_tk,
index_tk,
}
}
// ── Non-admin upgrade must revert ─────────────────────────────────────────────
/// Without mock_all_auths a non-admin cannot call upgrade; the auth gate rejects it.
#[test]
#[should_panic]
fn non_admin_upgrade_reverts() {
let env = Env::default();
// No mock_all_auths — require_auth() will panic for the non-admin.
let admin = Address::generate(&env);
let non_admin = Address::generate(&env);
let rs = env.register(RoleStore, ());
let ds = env.register(DataStore, ());
let oracle_addr = env.register(Oracle, ());
let ord_vault = env.register(OrderVault, ());
let ord_handler = env.register(OrderHandler, ());
// We cannot call initialize without auth mocking, but the upgrade auth check
// fires before any state read — a random hash is sufficient to trigger the panic.
let rando_hash = BytesN::from_array(&env, &[1u8; 32]);
OHClient::new(&env, &ord_handler).upgrade(&rando_hash);
let _ = (admin, non_admin, rs, ds, oracle_addr, ord_vault);
}
// ── Admin upgrade auth gate is open ──────────────────────────────────────────
/// With mock_all_auths the admin's require_auth() is silently satisfied.
/// The call then panics at the WASM lookup (not at auth) — this proves the
/// auth gate allows the admin through and the upgrade mechanism is wired up.
#[test]
#[should_panic]
fn admin_upgrade_auth_gate_open() {
let w = setup();
// Panics at WASM lookup in the test environment — that is expected and proves
// the auth check succeeded (auth panic would surface with a different message).
OHClient::new(&w.env, &w.ord_handler).upgrade(&BytesN::random(&w.env));
}
// ── State preservation after upgrade ─────────────────────────────────────────
/// Creates an order, then performs a mock upgrade (WASM lookup panics), and
/// verifies that the order's persistent storage was not mutated before the
/// WASM-lookup step. This is the closest approximation possible without a second
/// compiled WASM binary — a true end-to-end upgrade test requires the build
/// artifact produced by `cargo build --release` for a v2 version of the contract.
#[test]
#[ignore]
fn upgrade_preserves_pending_order_and_position_storage() {
let w = setup();
let env = &w.env;
// Place an order (decrease — no collateral transfer needed)
soroban_sdk::token::Client::new(env, &w.long_tk)
.transfer(&w.user, &w.ord_vault, &(200 * ONE_TOKEN));
OClient::new(env, &w.oracle).set_prices_simple(
&w.keeper,
&Vec::from_array(
env,
[
TokenPrice { token: w.long_tk.clone(), min: 2_000 * ONE_USD, max: 2_000 * ONE_USD },
TokenPrice { token: w.index_tk.clone(), min: 2_000 * ONE_USD, max: 2_000 * ONE_USD },
],
),
);
let key = OHClient::new(env, &w.ord_handler).create_order(
&w.user,
&CreateOrderParams {
receiver: w.user.clone(),
market: w.market_tk.clone(),
initial_collateral_token: w.long_tk.clone(),
swap_path: Vec::new(env),
size_delta_usd: 1_000 * ONE_USD,
collateral_delta_amount: 200 * ONE_TOKEN,
trigger_price: 0,
acceptable_price: 2_100 * ONE_USD,
execution_fee: 0,
min_output_amount: 0,
order_type: OrderType::MarketIncrease,
is_long: true,
expiry_ledger: None,
},
);
let order_before = OHClient::new(env, &w.ord_handler)
.get_order(&key)
.expect("order must exist before upgrade");
// Upgrade — in a real test this loads a v2 WASM binary; here it panics at lookup.
// For the #[ignore] version the intent is: provide a real new_wasm_hash from the
// build artefact, execute upgrade, then read order_after below.
OHClient::new(env, &w.ord_handler).upgrade(&BytesN::random(env));
// Post-upgrade: the order in persistent storage must be unchanged.
let order_after = OHClient::new(env, &w.ord_handler)
.get_order(&key)
.expect("order must still exist after upgrade");
assert_eq!(order_before.size_delta_usd, order_after.size_delta_usd);
assert_eq!(order_before.account, order_after.account);
assert_eq!(order_before.order_type, order_after.order_type);
}