1+ ------------------------------- MODULE MC -------------------------------
2+ <%#
3+ /*
4+ * An EJS template for generating the initial state from the aggregated state of the contract.
5+ *
6+ * Usage:
7+ *
8+ * npx ejs MCxycloans_monitor. ejs. tla - f state . json > MC . tla
9+ *
10+ * Igor Konnov , 2024
11+ */
12+ %>
13+ (* THIS MODULE IS AUTOGENERATED FROM SOROBAN STATE *)
14+ EXTENDS Integers, Apalache, xycloans_types
15+
16+ \* the set of all possible token amounts
17+ AMOUNTS == Nat
18+ \* the contract address for the xycLoans contract
19+ XYCLOANS == "<%- contractId %>"
20+ \* the token address
21+ XLM_TOKEN_SAC_TESTNET == "<%- storage[contractId].instance.TokenId %>"
22+
23+ < %
24+ const balanceAddrs =
25+ Object. keys(storage[ contractId] . persistent)
26+ . filter( ( key ) => key . startsWith ( "Balance," ) )
27+ . map ( ( key ) => key . split ( "," ) [ 1 ] )
28+ % >
29+ \* user-controlled addresses
30+ USER_ADDR == {
31+ <%-
32+ balanceAddrs
33+ . map ( ( addr ) => ` "${addr}" `)
34+ . join ( ",\n " )
35+ %>
36+ }
37+
38+ <%
39+ const tokenAddrs =
40+ Object . keys ( storage [ storage [ contractId ] . instance . TokenId ] . persistent )
41+ . filter ( ( key ) => key . startsWith ( "Balance," ) )
42+ . filter ( ( key ) => key ! = = `Balance, ${ contractId } `)
43+ . map ( ( key ) => key . split ( "," ) [ 1 ] )
44+ % >
45+ \* addresses that hold token balances
46+ TOKEN_ADDR == {
47+ <%-
48+ tokenAddrs
49+ . map ( ( addr ) => ` "${addr}" `)
50+ . join ( ",\n " )
51+ % >
52+ }
53+
54+ \* the pool of addresses to draw the values from
55+ ADDR = = { XYCLOANS , XLM_TOKEN_SAC_TESTNET } \union TOKEN_ADDR \union USER_ADDR
56+
57+ VARIABLES
58+ \* @type: $tx;
59+ last_tx ,
60+ \* @type: Str -> Int;
61+ shares ,
62+ \* @type: Int;
63+ total_shares ,
64+ \* @type: Int;
65+ fee_per_share_universal ,
66+ \* Keep track of the current storage,
67+ \* which can be only changed by a successful transaction.
68+ \* @type: $storage;
69+ storage
70+
71+ INSTANCE xycloans_monitor
72+
73+ < %
74+ function renderKVStore ( storage , prefix , mapper = ( x ) => x ) {
75+ return Object . keys ( storage )
76+ . filter ( ( key ) => key . startsWith ( prefix ) )
77+ . map ( ( key ) => key . split ( "," ) [ 1 ] )
78+ . map ( ( addr ) => ` << "${addr}" , ${ mapper ( storage [ prefix + addr ] ) } >> `)
79+ . join ( ",\n " )
80+ }
81+ % >
82+
83+ Init ==
84+ LET init_stor == [
85+ self_instance |-> [
86+ FeePerShareUniversal |-> <%- storage [ contractId ] . instance . FeePerShareUniversal %> ,
87+ TokenId |-> "<%- storage[contractId].instance.TokenId %>"
88+ ] ,
89+ self_persistent |-> [
90+ Balance |-> SetAsFun ( {
91+ <%-
92+ renderKVStore ( storage [ contractId ] . persistent , "Balance," )
93+ %>
94+ } ) ,
95+ MaturedFeesParticular |-> SetAsFun ( {
96+ <%-
97+ renderKVStore ( storage [ contractId ] . persistent , "MaturedFeesParticular," )
98+ %>
99+ } ) ,
100+ FeePerShareParticular |-> SetAsFun ( {
101+ <%-
102+ renderKVStore ( storage [ contractId ] . persistent , "FeePerShareParticular," )
103+ %>
104+ } )
105+ ] ,
106+ token_persistent |-> [ Balance |-> SetAsFun ( {
107+ <%-
108+ renderKVStore ( storage [ storage [ contractId ] . instance . TokenId ] . persistent , "Balance," , ( x ) => x . amount )
109+ %>
110+ } ) ]
111+ ]
112+ IN
113+ \* initialize the monitor non-deterministically
114+ /\ shares \in [ USER_ADDR -> Nat ]
115+ /\ total_shares \in Nat
116+ /\ fee_per_share_universal \in Nat
117+ \* initialize the contract state that we model
118+ /\ last_tx = [
119+ call |-> Constructor ( XYCLOANS ) ,
120+ status |-> TRUE ,
121+ env |-> [
122+ current_contract_address |-> XYCLOANS ,
123+ storage |-> init_stor ,
124+ old_storage |-> init_stor
125+ ]
126+ ]
127+ /\ storage = init_stor
128+
129+ Next ==
130+ \* Generate some values for the storage.
131+ \* For value generation, we go over all addresses, not subsets of addresses.
132+ \E fpsu \in AMOUNTS , tid \in { "" , XLM_TOKEN_SAC_TESTNET } :
133+ \E b , mfp , fpsp , tb \in [ ADDR -> AMOUNTS ] :
134+ LET new_stor == [
135+ self_instance |-> [ FeePerShareUniversal |-> fpsu , TokenId |-> tid ] ,
136+ self_persistent |->
137+ [ Balance |-> b , MaturedFeesParticular |-> mfp , FeePerShareParticular |-> fpsp ] ,
138+ token_persistent |-> [ Balance |-> tb ]
139+ ]
140+ env == [
141+ current_contract_address |-> XYCLOANS ,
142+ storage |-> new_stor ,
143+ old_storage |-> storage
144+ ]
145+ IN
146+ \E addr \in USER_ADDR , amount \in AMOUNTS , success \in BOOLEAN :
147+ /\ \/ LET tx == [ env |-> env , call |-> Initialize ( XLM_TOKEN_SAC_TESTNET ) , status |-> success ] IN
148+ initialize ( tx ) /\ last_tx ' = tx
149+ \/ LET tx == [ env |-> env , call |-> Deposit ( addr , amount ) , status |-> success ] IN
150+ deposit ( tx ) /\ last_tx ' = tx
151+ \/ LET tx == [ env |-> env , call |-> Borrow ( addr , amount ) , status |-> success ] IN
152+ borrow ( tx ) /\ last_tx ' = tx
153+ \/ LET tx == [ env |-> env , call |-> UpdateFeeRewards ( addr ) , status |-> success ] IN
154+ update_fee_rewards ( tx ) /\ last_tx ' = tx
155+ /\ storage ' = IF success THEN new_stor ELSE storage
156+
157+ \* restrict the executions to the successful transactions
158+ NextOk = =
159+ Next /\ last_tx ' . status
160+
161+ \* use this falsy invariant to generate examples of successful transactions
162+ NoSuccessInv = =
163+ ~ IsConstructor ( last_tx . call ) => ~last_tx . status
164+
165+ \* use this view to generate better test coverage
166+ \* apalache-mc check --max-error=10 --length=10 --inv=NoSuccessInv --view=View MCxycloans_monitor.tla
167+ View = = << last_tx . status , VariantTag ( last_tx . call ) >>
168+ == =======================================================================================
0 commit comments