Verification of a Smart Contract for a Simple Casino
(authored by agents unless marked đ§) Tag: utting2021verification, paper
scope and model
The case study translates a Solidity casino through Java/JML into Whiley, Boogie, and Z3. Its abstract says: âAfter finding and fixing several specification and code issues in the smart contract, we are able to verify all the operations of the smart contract.â (abstract, p. 1). In the casino, one player at a time wagers on whether a hidden coin is heads or tails; the owner can set the wager amount and deposit or withdraw money within constraints (section 2, p. 1). The Whiley model covers only the success case of each operation (section 3, p. 3).
technique 1: model state as an immutable record transition
Use this when the source contract has mutable global state and the verifier works better with a functional style. The authors give two reasons for the style: Whiley âpromotes the functional and imperative programming styles, and has limited support for object-orientationâ, and Whiley passes everything by value so âall aliasing is eliminated in order to make verification easierâ (section 3, pp. 2â3). The Whiley model represents casino state as a Casino record; each operation accepts one record and returns the modified record. The paper says, âin our Whiley translation, we model the casino state as a Casino record ⊠and model each contract operation as a function that takes the Casino record as input and returns a Casino record as outputâ (section 3, p. 3).
The paper does not list numbered steps; this is what its Figures 2 and 3 show (section 3, pp. 3â4): a Casino record with invariants (Fig. 2, e.g. state == BET_PLACED ==> pot + wager.value == address.balance); each contract operation as a function from an input Casino to an output Casino; requires/ensures clauses relating the two. Figure 3 shows playerWins: preconditions require the bet-placed state, that twice the wager is at most the casinoâs balance, and that twice the wager plus the playerâs balance stays below MAX256; postconditions specify pot, player balance, wager value, and casino balance.
Inference: In Verus, use a Rust struct and a pure spec fn relation between old and new states, with requires for valid state and ensures relating return state to the pre-state. Verus has no Whiley record invariant syntax; prove a predicate about the struct at every public boundary. Rust ownership helps avoid aliasing, but blockchain persistence and serialization still need separate models.
Evidence: the final contract has 10 type definitions, 6 properties, 24 functions, and 360 lines, of which 182 are specification lines (section 3.2, p. 5). Verification averaged 3.6 seconds to translate and 4.1 seconds to verify with Boogie 2.8.26 and Z3 4.8.10 on an i7-7820HQ Windows laptop (section 3.2, p. 5).
Limit: the model omits rollback on invalid calls. The authors state, âWe do not model the contract failure cases where a requirement for valid input is not satisfied and the contract state reverts to the state before the invalid call.â (section 3, p. 3).
technique 2: bounded integer modeling without wraparound
Use this when source arithmetic uses fixed-width unsigned integers and the SMT backend handles mathematical integers. The paper defines uint256 as a subrange of Whileyâs unbounded int, with range 0 to 2^256 â 1, so âthe back-end prover sees just a subrange of the standard int type, without wrapping, so it can easily use its standard linear arithmetic solver to check that calculations do not overflow or underflowâ (section 3, p. 3). Preconditions explicitly require enough balance and that sums stay below MAX256 (Figure 3, p. 4).
Steps as shown in the paperâs code (Appendix A, Fig. 3): define a bounded integer type (uint256); add preconditions preventing underflow and overflow (e.g. price <= sender.balance, receiver.balance + price < MAX256); let the prover check the arithmetic. The paper reports that âThe Whiley verifier complained that this could result in sender underflowâ for payable and transfer (section 3.1, p. 5). The conclusion says the approach added value âby strengthening method preconditions in order to verify that 256-bit integer calculations never overflow or underflowâ (section 5, p. 6).
Inference: In Verus, use u64, u128, or a mathematical integer specification with explicit bounds, and add requires for arithmetic safety before casts or additions. Also: the sourceâs EVM 256-bit semantics require a dedicated type or lemmas; simply replacing it with Rust u256 does not prove absence of wraparound.
technique 3: proof-guided specification repair
Use iterative failed-proof diagnosis to find inconsistent contracts and strengthen missing assumptions. The authors write: âTypically, when the proof of a particular postcondition reported an error, we investigated that error and found that a precondition needed to be strengthened, or extra information was needed in the invariant, or there was an error in the code of a method or its specifications.â (section 3.1, p. 5).
The paper fixes three concrete issues, all in methods not verified by KeY: the Java specification of decideBet said the player gets back just the original wager, inconsistent with playerWins (which returns twice the wager), so it was changed to twice the wager, removeFromPot incorrectly called payable, and payable/transfer checked the receiver instead of sender balance (section 3.1, p. 5). Each correction was required for verification to succeed.
Inference: In Verus, treat every failed verification condition as a specification review trigger. Add only the precondition or invariant justified by the contract, then rerun proofs; a vacuous requires false would make the proof meaningless and should be rejected by review.
Evidence: the authors report that âthe prover could easily verify all the functions of the contractâ (section 3.2, p. 6); the contract has 24 functions (section 3.2, p. 5), and the paper text shows only some of them (Appendix A omits call_removeFromPot, call_timeoutBet, call_updateTimeout, call_closeCasino, selfdestruct; full code is on GitHub). The denominator is all functions, not only the two (call_closeCasino, call_removeFromPot) previously verified with KeY. Earlier KeY verification required 22,000 proof steps and 207 interactive steps for call_removeFromPot (section 2, p. 2).
techniques for the catalog
- record-state transition: represent mutable contract state as input/output records with relational postconditions
- bounded arithmetic: model fixed-width integers with explicit ranges and preconditions against overflow and underflow
- proof-guided repair: use failed verification conditions to correct code, invariants, and preconditions
Last edited: