├── examples ├── erc20-approve-attack │ ├── requirements.txt │ ├── fig │ │ ├── sim-cpp.pdf │ │ ├── apa-check.pdf │ │ ├── apa-simulate.pdf │ │ ├── apa_check_erc20.csv │ │ ├── sim_erc20.csv │ │ ├── apa_sim_erc20.csv │ │ ├── sim-cpp.csv │ │ ├── apa-simulate.csv │ │ ├── apa-check.csv │ │ └── plot.py │ ├── MC_tlc_check.cfg │ ├── run-experiments-cpp.sh │ ├── MC_tlc_check.tla │ ├── count_combinations.py │ ├── run-experiments-check.sh │ ├── run-experiments-simulate.sh │ ├── ERC20_typedefs.tla │ ├── MC_ERC20.tla │ ├── counterexample5.tla │ ├── test_erc20.py │ └── tlc_counterexample.out ├── token-transfer │ ├── apalache.properties │ ├── MC1.tla │ ├── MC2.tla │ ├── MC5.tla │ ├── MC4.tla │ ├── typedefs.tla │ ├── MC3.tla │ ├── MC6.tla │ ├── MC7.tla │ ├── TokenTransfer1.tla │ ├── MC8.tla │ ├── MC9.tla │ ├── MC10.tla │ ├── Test10.tla │ ├── TokenTransfer2.tla │ ├── TokenTransfer4.tla │ ├── TokenTransfer5.tla │ ├── TokenTransfer6.tla │ ├── TokenTransfer3.tla │ ├── TokenTransfer7.tla │ ├── TokenTransfer3.md │ ├── TokenTransfer8.tla │ ├── TokenTransfer9.tla │ └── TokenTransfer10.tla ├── clock-sync │ ├── MC_ClockSync1.tla │ ├── MC_ClockSync6p.tla │ ├── MC_ClockSync7.tla │ ├── MC_ClockSync2.tla │ ├── run-tests.sh │ ├── ClockSync1.tla │ ├── MC_ClockSync3.tla │ ├── ClockSync2.tla │ ├── MC_ClockSync4.tla │ ├── MC_ClockSync5.tla │ ├── MC_ClockSync6.tla │ ├── ClockSync3.tla │ ├── ClockSync4.tla │ ├── ClockSync5.tla │ ├── ClockSync6.tla │ └── ClockSync7.tla └── erc20 │ ├── erc20_typedefs.tla │ ├── erc20_mempool_typedefs.tla │ ├── README.md │ ├── erc20_tests.tla │ ├── erc20_mempool.tla │ └── erc20.tla ├── docs ├── erc20-steps.md ├── clock-sync-steps.md └── token-transfer-steps.md ├── README.md └── LICENSE /examples/erc20-approve-attack/requirements.txt: -------------------------------------------------------------------------------- 1 | hypothesis 2 | pyunit 3 | -------------------------------------------------------------------------------- /examples/token-transfer/apalache.properties: -------------------------------------------------------------------------------- 1 | search.invariantFilter=[2-4] 2 | search.invariant.mode=after 3 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/sim-cpp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/informalsystems/tla-apalache-workshop/HEAD/examples/erc20-approve-attack/fig/sim-cpp.pdf -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/apa-check.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/informalsystems/tla-apalache-workshop/HEAD/examples/erc20-approve-attack/fig/apa-check.pdf -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/apa-simulate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/informalsystems/tla-apalache-workshop/HEAD/examples/erc20-approve-attack/fig/apa-simulate.pdf -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/apa_check_erc20.csv: -------------------------------------------------------------------------------- 1 | #configuration,time,lower,upper 2 | L5-A3-V02,7.96,7.3,8.6 3 | L5-A3-V10,7.8,7.6,7.3 4 | L5-A3-V20,7.8,7.6,8.2 5 | L5-A3-Int,6.5,6.2,6.9 6 | L5-A5-V20,11.77,11.24,12.3 7 | L5-A5-Int,8.86,7.7,10 8 | L5-A10-V20,83.4,75.4,91.4 9 | L5-A10-Int,80.6,75,86 10 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/sim_erc20.csv: -------------------------------------------------------------------------------- 1 | #configuration,time,lower,upper 2 | L5-A3-V02,3,1.3,3.8 3 | L5-A3-V10,16.9,3.9,30 4 | L5-A3-V20,41.5,0.3,83.8 5 | L5-A5-V20,18,1,40 6 | L5-A10-V20,63,27,100 7 | L10-A3-V02,0.227,0.04,0.4 8 | L10-A3-V10,0.469,0.13,0.8 9 | L10-A3-V20,0.75,0.1,1.75 10 | L10-A5-V20,0.95,0.01,2 11 | L10-A10-V20,3,1.7,4.4 12 | -------------------------------------------------------------------------------- /examples/token-transfer/MC1.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC1 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | 4 | CHAINS == { "A", "B" } 5 | ACCOUNTS == { "reserve", "anna", "boris" } 6 | 7 | \* introduce the global variables 8 | VARIABLES 9 | \* @type: ADDR -> Int; 10 | banks 11 | 12 | INSTANCE TokenTransfer1 13 | =============================================================================== 14 | -------------------------------------------------------------------------------- /examples/token-transfer/MC2.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC2 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | 4 | CHAINS == { "A", "B" } 5 | ACCOUNTS == { "reserve", "anna", "boris" } 6 | 7 | \* introduce the global variables 8 | VARIABLES 9 | \* @type: ADDR -> Int; 10 | banks 11 | 12 | INSTANCE TokenTransfer2 13 | =============================================================================== 14 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/apa_sim_erc20.csv: -------------------------------------------------------------------------------- 1 | #configuration,time,lower,upper 2 | L5-A3-V02,36,13,60 3 | L5-A3-V10,29.5,13,46 4 | L5-A3-V20,40,13,68 5 | L5-A3-Int,31,16,46 6 | L5-A5-V20,50,16,84 7 | L5-A5-Int,46,14,78 8 | L5-A10-V20,94,1,200 9 | L5-A10-Int,55,13,97 10 | L10-A3-V02,10,4,16 11 | L10-A3-V10,11.7,5.7,17.7 12 | L10-A3-V20,18.6,8,29.1 13 | L10-A3-Int,16.1,6.6,25.6 14 | L10-A5-V20,16.7,4.3,29.1 15 | L10-A5-Int,19.5,8,31 16 | L10-A10-V20,24.9,11.4,38.4 17 | L10-A10-Int,28.2,8,48.5 18 | -------------------------------------------------------------------------------- /examples/token-transfer/MC5.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC5 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | 4 | CHAINS == { "A", "B" } 5 | ACCOUNTS == { "reserve", "anna", "boris" } 6 | GENESIS_SUPPLY == [ c \in CHAINS |-> 100 ] 7 | 8 | \* introduce the global variables 9 | VARIABLES 10 | \* @type: ADDR -> Int; 11 | banks 12 | 13 | INSTANCE TokenTransfer5 14 | =============================================================================== 15 | -------------------------------------------------------------------------------- /examples/token-transfer/MC4.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC4 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | EXTENDS Integers 4 | 5 | CHAINS == { "A", "B" } 6 | ACCOUNTS == { "reserve", "anna", "boris" } 7 | AMOUNTS == 0..5 8 | GENESIS_SUPPLY == [ c \in CHAINS |-> 5 ] 9 | 10 | \* introduce the global variables 11 | VARIABLES 12 | \* @type: <> -> Int; 13 | banks 14 | 15 | INSTANCE TokenTransfer4 16 | =============================================================================== 17 | -------------------------------------------------------------------------------- /examples/token-transfer/typedefs.tla: -------------------------------------------------------------------------------- 1 | -------------------------------- MODULE typedefs ------------------------------ 2 | (* 3 | ## Type aliases 4 | 5 | We introduce the following aliases for the types that are used in this 6 | specification. 7 | 8 | @typeAlias: CHAIN = Str; 9 | @typeAlias: ACCOUNT = Str; 10 | @typeAlias: ADDR = <>; 11 | @typeAlias: BANK = (ADDR -> Int); 12 | 13 | @typeAlias: DENOM = Str; 14 | @typeAlias: DADDR = <>; 15 | *) 16 | typedefs_included == TRUE 17 | =============================================================================== 18 | -------------------------------------------------------------------------------- /examples/token-transfer/MC3.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC3 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | CHAINS == { "A", "B" } 4 | ACCOUNTS == { "reserve", "anna", "boris" } 5 | \* We fix the initial supply. 6 | \* Alternatively, we could introduce a constant initializer ConstInit 7 | \* and call apalache with --cinit=ConstInit 8 | GENESIS_SUPPLY == [ c \in CHAINS |-> 100 ] 9 | 10 | \* introduce the global variables 11 | VARIABLES 12 | \* @type: ADDR -> Int; 13 | banks 14 | 15 | INSTANCE TokenTransfer3 16 | =============================================================================== 17 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/MC_tlc_check.cfg: -------------------------------------------------------------------------------- 1 | \* MV CONSTANT declarations 2 | CONSTANTS 3 | A_Alice = A_Alice 4 | A_Bob = A_Bob 5 | A_Eve = A_Eve 6 | \* MV CONSTANT definitions 7 | CONSTANT 8 | ADDR <- const_165365715019242000 9 | \* SYMMETRY definition 10 | SYMMETRY symm_165365715019243000 11 | \* CONSTANT definitions 12 | CONSTANT 13 | AMOUNTS <- const_165365715019244000 14 | 15 | CONSTANT 16 | INITIAL_BALANCES <- const_initial_balances 17 | \* CONSTRAINT definition 18 | CONSTRAINT 19 | constr_165365715019245000 20 | \* INIT definition 21 | INIT 22 | Init 23 | \* NEXT definition 24 | NEXT 25 | Next 26 | \* INVARIANT definition 27 | INVARIANT 28 | NoTransferFromWhileApproveInFlight 29 | \* Generated on Fri May 27 15:12:30 CEST 2022 30 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/run-experiments-cpp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | CSV=./fig/sim-cpp.csv 4 | echo "name,mean,stddev" >$CSV 5 | 6 | for l in 5 10; do 7 | for a in 3 5 10; do 8 | for v in 2 10 20; do 9 | perl -pi -e "s/NADDR = .*;/NADDR = $a;/" sim_erc20.cpp 10 | perl -pi -e "s/NAMOUNTS = .*;/NAMOUNTS = $v;/" sim_erc20.cpp 11 | perl -pi -e "s/NSTEPS = .*;/NSTEPS = $l;/" sim_erc20.cpp 12 | g++ -Wall -O3 -o sim_erc20 sim_erc20.cpp 13 | hyperfine -m 100 --export-csv /tmp/r.csv ./sim_erc20 14 | tail -n 1 /tmp/r.csv \ 15 | | cut -d "," -f 2,3 \ 16 | | sed "s/^/L$l-A$a-V$v,/" >>$CSV 17 | done 18 | done 19 | done 20 | -------------------------------------------------------------------------------- /docs/erc20-steps.md: -------------------------------------------------------------------------------- 1 | # Typing the specification and checking it 2 | 3 | ## Inspecting a complete spec 4 | 5 | 1. Read [EIP-20](https://eips.ethereum.org/EIPS/eip-20) 6 | and try to figure how it is working. 7 | 1. Read the description of the 8 | [attack scenario on EIP-20](https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/) 9 | 1. Open [ERC20.tla](../examples/erc20-approve-attack/ERC20.tla) 10 | and [MC_ERC20.tla](../examples/erc20-approve-attack/MC_ERC20.tla). 11 | 1. Check the trace invariant `NoTransferAboveApproved`: 12 | 13 | ```sh 14 | $ apalache-mc check --inv=NoTransferAboveApproved MC_ERC20.tla 15 | ``` 16 | 1. The tool reports an invariant violation. 17 | 1. Open the counterexample and see, 18 | whether it matches the above attack scenario. 19 | 20 | -------------------------------------------------------------------------------- /examples/token-transfer/MC6.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC6 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | 4 | EXTENDS Integers 5 | 6 | CHAINS == { "A", "B" } 7 | CHANNELS == 8 | LET \* @type: (Str, Str) => <>; 9 | pair(i, j) == <> 10 | IN 11 | { pair("A", "B") } 12 | ACCOUNTS == { "reserve", "escrow", "anna", "boris" } 13 | GENESIS_SUPPLY == [ c \in CHAINS |-> 100 ] 14 | 15 | \* introduce the global variables 16 | VARIABLES 17 | \* @type: <> -> Int; 18 | banks, 19 | \* @type: Set([src: CHAIN, dest: CHAIN, data: [sender: ACCOUNT, receiver: ACCOUNT, amount: Int]]); 20 | sentPackets 21 | 22 | INSTANCE TokenTransfer6 23 | =============================================================================== 24 | -------------------------------------------------------------------------------- /examples/clock-sync/MC_ClockSync1.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE MC_ClockSync1 ----------------------------- 2 | 3 | VARIABLES 4 | \* the reference clock, inaccessible to the processes 5 | \* @type: Int; 6 | time, 7 | \* hardware clock of a process 8 | \* @type: Str -> Int; 9 | hc, 10 | \* clock adjustment of a process 11 | \* @type: Str -> Int; 12 | adj 13 | 14 | INSTANCE ClockSync1 15 | 16 | \* test that the clocks are non-decreasing 17 | Test1_Init == 18 | /\ time \in Nat 19 | /\ hc \in [ Proc -> Nat ] 20 | /\ adj \in [ Proc -> Int ] 21 | 22 | Test1_Next == 23 | \E delta \in Int: 24 | AdvanceClocks(delta) 25 | 26 | Test1_Inv == 27 | /\ time' >= time 28 | /\ \A p \in Proc: hc'[p] >= hc[p] 29 | 30 | =============================================================================== 31 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/MC_tlc_check.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE MC_tlc_check ---- 2 | EXTENDS ERC20, TLC 3 | 4 | \* MV CONSTANT declarations@modelParameterConstants 5 | CONSTANTS 6 | A_Alice, A_Bob, A_Eve 7 | ---- 8 | 9 | \* MV CONSTANT definitions ADDR 10 | const_165365715019242000 == 11 | {A_Alice, A_Bob, A_Eve} 12 | ---- 13 | 14 | const_initial_balances == [a \in {A_Alice, A_Bob, A_Eve} |-> 15] 15 | 16 | \* SYMMETRY definition 17 | symm_165365715019243000 == 18 | Permutations(const_165365715019242000) 19 | ---- 20 | 21 | \* CONSTANT definitions @modelParameterConstants:0AMOUNTS 22 | const_165365715019244000 == 23 | 0..19 24 | ---- 25 | 26 | \* CONSTRAINT definition @modelParameterContraint:0 27 | constr_165365715019245000 == 28 | \A a \in ADDR: balanceOf[a] \in AMOUNTS 29 | ---- 30 | ============================================================================= 31 | \* Modification History 32 | \* Created Fri May 27 15:12:30 CEST 2022 by igor 33 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/sim-cpp.csv: -------------------------------------------------------------------------------- 1 | name,mean,stddev 2 | L5-A3-V2,8.61412638131,8.537701831555756 3 | L5-A3-V10,17.2224861373,14.212263402786853 4 | L5-A3-V20,24.08796024238999,22.353632580310165 5 | L5-A5-V2,14.406201508329996,15.467076105341906 6 | L5-A5-V10,20.568754572670006,18.39052851572552 7 | L5-A5-V20,33.66566996866999,36.510138548780375 8 | L5-A10-V2,191.52897303475,192.23554335704745 9 | L5-A10-V10,64.67388795971003,59.39361145738608 10 | L5-A10-V20,144.62339362832998,143.92345204659821 11 | L10-A3-V2,0.24154140156,0.25448296073973653 12 | L10-A3-V10,0.91363802342,0.8472044703226908 13 | L10-A3-V20,1.18742706365,1.185292257267318 14 | L10-A5-V2,0.5687305313500001,0.5522930500809796 15 | L10-A5-V10,0.62823586266,0.6767391977053123 16 | L10-A5-V20,1.18913402948,1.4388344196177594 17 | L10-A10-V2,6.052669630489999,6.739127706177666 18 | L10-A10-V10,2.65694953691,2.3654690225301125 19 | L10-A10-V20,4.770643583400001,4.707615308251221 20 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/count_combinations.py: -------------------------------------------------------------------------------- 1 | def count(txs, n): 2 | if n > 5: 3 | # produced 5 steps 4 | return 1 5 | 6 | # submit one more transaction 7 | ntransfer = count(txs + [0], n + 1) * 180 8 | napprove = count(txs + [1], n + 1) * 180 9 | ntransfer_from = count(txs + [2], n + 1) * 540 10 | # consume one of the transactions 11 | nctransfer = 0 12 | if 0 in txs: 13 | copy = txs[:] 14 | copy.remove(0) 15 | nctransfer = count(copy, n + 1) * 0.2375 16 | 17 | ncapprove = 0 18 | if 1 in txs: 19 | copy = txs[:] 20 | copy.remove(1) 21 | ncapprove = count(copy, n + 1) * 0.475 22 | 23 | nctransfer_from = 0 24 | if 2 in txs: 25 | copy = txs[:] 26 | copy.remove(2) 27 | nctransfer_from = count(copy, n + 1) * 0.1128125 28 | 29 | return ntransfer + napprove + ntransfer_from \ 30 | + ncapprove + nctransfer + nctransfer_from 31 | 32 | print("number of combinations: %d" % count([], 1)) 33 | -------------------------------------------------------------------------------- /examples/token-transfer/MC7.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC7 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | 4 | EXTENDS Integers 5 | 6 | CHAINS == { "A", "B" } 7 | CHANNELS == 8 | LET \* @type: (Str, Str) => <>; 9 | pair(i, j) == <> 10 | IN 11 | { pair("A", "B") } 12 | ACCOUNTS == { "reserve", "escrow", "anna", "boris" } 13 | GENESIS_SUPPLY == [ c \in CHAINS |-> 100 ] 14 | 15 | \* introduce the global variables 16 | VARIABLES 17 | \* @type: <> -> Int; 18 | banks, 19 | \* @type: Set([seqno: Int, src: Str, dest: Str, data: [sender: Str, receiver: Str, amount: Int]]); 20 | sentPackets, 21 | \* The sequence numbers of delivered packets 22 | \* @type: Set(Int); 23 | deliveredNums, 24 | \* An imaginary global counter that we use to assign unique sequence numbers 25 | \* @type: Int; 26 | seqno 27 | 28 | INSTANCE TokenTransfer7 29 | =============================================================================== 30 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/run-experiments-check.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$APALACHE_HOME" == "" ]; then 4 | echo "set APALACHE_HOME to the directory where Apalache resides" 5 | exit 1 6 | fi 7 | 8 | CSV=./fig/apa-check.csv 9 | echo "name,mean,stddev" >$CSV 10 | 11 | for l in 5 10; do 12 | for a in 3 5 10; do 13 | for v in 2 10 20 Int; do 14 | perl -pi -e "s/ADDR == .*/ADDR == ADDR$a/" MC_ERC20.tla 15 | if [ "$v" == "Int" ]; then 16 | perl -pi -e "s/AMOUNTS == .*/AMOUNTS == Int/" MC_ERC20.tla 17 | else 18 | perl -pi -e "s/AMOUNTS == .*/AMOUNTS == 0..$v/" MC_ERC20.tla 19 | fi 20 | hyperfine -i -m 10 --export-csv /tmp/r.csv \ 21 | "$APALACHE_HOME/bin/apalache-mc check \ 22 | --length=$l \ 23 | --inv=NoTransferFromWhileApproveInFlight MC_ERC20.tla" 24 | tail -n 1 /tmp/r.csv \ 25 | | cut -d "," -f 2,3 \ 26 | | sed "s/^/L$l-A$a-V$v,/" >>$CSV 27 | done 28 | done 29 | done 30 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/run-experiments-simulate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$APALACHE_HOME" == "" ]; then 4 | echo "set APALACHE_HOME to the directory where Apalache resides" 5 | exit 1 6 | fi 7 | 8 | CSV=./fig/apa-simulate.csv 9 | echo "name,mean,stddev" >$CSV 10 | 11 | for l in 5 10; do 12 | for a in 3 5 10; do 13 | for v in 2 10 20 Int; do 14 | perl -pi -e "s/ADDR == .*/ADDR == ADDR$a/" MC_ERC20.tla 15 | if [ "$v" == "Int" ]; then 16 | perl -pi -e "s/AMOUNTS == .*/AMOUNTS == Int/" MC_ERC20.tla 17 | else 18 | perl -pi -e "s/AMOUNTS == .*/AMOUNTS == 0..$v/" MC_ERC20.tla 19 | fi 20 | hyperfine -i -m 10 --export-csv /tmp/r.csv \ 21 | "$APALACHE_HOME/bin/apalache-mc simulate \ 22 | --max-run=10000000 --length=$l \ 23 | --inv=NoTransferFromWhileApproveInFlight MC_ERC20.tla" 24 | tail -n 1 /tmp/r.csv \ 25 | | cut -d "," -f 2,3 \ 26 | | sed "s/^/L$l-A$a-V$v,/" >>$CSV 27 | done 28 | done 29 | done 30 | -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer1.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer1 --------------------------------- 2 | (* 3 | * This is an example of a very simplistic token transfer 4 | * for presentation purposes. 5 | * Do not use it in production, as it may lead to loss of tokens. 6 | * 7 | * Version 1: introducing data structures 8 | * 9 | * Igor Konnov, 2021 10 | *) 11 | EXTENDS Integers, typedefs 12 | 13 | \* check typedefs.tla for type aliases 14 | 15 | CONSTANT 16 | \* A set of blockchains, i.e., their names 17 | \* @type: Set(CHAIN); 18 | CHAINS, 19 | \* A set of accounts, i.e., their names 20 | \* @type: Set(ACCOUNTS); 21 | ACCOUNTS 22 | 23 | VARIABLES 24 | \* For every chain and account, store the amount of tokens in the account 25 | \* @type: ADDR -> Int; 26 | banks 27 | 28 | \* Initialize the world, e.g., from the last upgrade 29 | Init == 30 | \E b \in [ CHAINS \X ACCOUNTS -> Int ]: 31 | banks = b 32 | 33 | \* Update the world 34 | Next == 35 | UNCHANGED banks 36 | 37 | =============================================================================== 38 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/ERC20_typedefs.tla: -------------------------------------------------------------------------------- 1 | ------------------------ MODULE ERC20_typedefs -------------------------------- 2 | (* 3 | Type definitions for the module ERC20. 4 | 5 | An account address, in our case, simply an uninterpreted type ADDR. 6 | 7 | @typeAlias: transfer = { id: Int, fail: Bool, sender: ADDR, toAddr: ADDR, value: Int }; 8 | @typeAlias: approve = { id: Int, fail: Bool, sender: ADDR, spender: ADDR, value: Int }; 9 | @typeAlias: transferFrom = { id: Int, fail: Bool, sender: ADDR, fromAddr: ADDR, toAddr: ADDR, value: Int }; 10 | 11 | A transaction is a discriminated union: 12 | @typeAlias: TX = None(UNIT) | Transfer($transfer) | Approve($approve) | TransferFrom($transferFrom) ; 13 | 14 | A state of the state machine: 15 | @typeAlias: STATE = { 16 | balanceOf: ADDR -> Int, 17 | allowance: <> -> Int, 18 | pendingTransactions: Set(TX), 19 | lastTx: TX, 20 | nextTxId: Int 21 | }; 22 | 23 | Below is a dummy definition to introduce the above type aliases. 24 | *) 25 | ERC20_typedefs == TRUE 26 | =============================================================================== 27 | -------------------------------------------------------------------------------- /examples/erc20/erc20_typedefs.tla: -------------------------------------------------------------------------------- 1 | -------------------------- MODULE erc20_typedefs ------------------------------- 2 | EXTENDS Variants 3 | 4 | (* 5 | Type definitions for erc20. 6 | 7 | ADDR is an uninterpreted type representing the account address. 8 | 9 | An EVM integer is 256 bits. 10 | We are using TLA+ integers and check for overflows manually. 11 | 12 | // A state of an ERC20 contract/token 13 | @typeAlias: state = { 14 | balanceOf: ADDR -> Int, 15 | totalSupply: Int, 16 | allowance: <> -> Int, 17 | owner: ADDR 18 | }; 19 | 20 | // The result of applying an ERC20 method 21 | @typeAlias: result = 22 | Error(Str) 23 | | Ok({ returnedTrue: Bool, state: $state }) 24 | ; 25 | *) 26 | 27 | erc20_typedefs == TRUE 28 | 29 | \* A convenience operator for constructing an Error result. 30 | \* 31 | \* @type: Str => $result; 32 | Error(msg) == 33 | Variant("Error", msg) 34 | 35 | \* A convenience operator for constructing an Ok result. 36 | \* 37 | \* @type: (Bool, $state) => $result; 38 | Ok(returnedTrue, state) == 39 | Variant("Ok", [ returnedTrue |-> returnedTrue, state |-> state ]) 40 | 41 | ================================================================================ -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/apa-simulate.csv: -------------------------------------------------------------------------------- 1 | name,mean,stddev 2 | L5-A3-V2,48.32915018186,30.28855446230841 3 | L5-A3-V10,39.11150956162,29.08964222317152 4 | L5-A3-V20,47.73209327126,39.20317722644216 5 | L5-A3-VInt,29.797669824139994,21.96678313393914 6 | L5-A5-V2,38.49092226233999,22.456279955541458 7 | L5-A5-V10,38.305264012680006,25.323150192734584 8 | L5-A5-V20,31.681548564640003,20.58789165478192 9 | L5-A5-VInt,22.91104267376,16.755445407882675 10 | L5-A10-V2,86.62634158356,47.72973560484153 11 | L5-A10-V10,41.142525121999995,33.54415432127446 12 | L5-A10-V20,75.86417058784,38.9653764364827 13 | L5-A10-VInt,114.12517748644,142.79401694551115 14 | L10-A3-V2,10.62977336172,4.180368341221939 15 | L10-A3-V10,13.067103791040001,6.047467970838041 16 | L10-A3-V20,16.1061434838,9.498186312579131 17 | L10-A3-VInt,18.227410543439998,9.082504296501769 18 | L10-A5-V2,17.1021013917,7.184658539453976 19 | L10-A5-V10,31.264349415959998,19.48361273423121 20 | L10-A5-V20,21.360484513219998,13.798809649038303 21 | L10-A5-VInt,13.812261459479998,4.379959106151217 22 | L10-A10-V2,23.86568376422,15.812521134993188 23 | L10-A10-V10,25.94421955692,12.605866581455492 24 | L10-A10-V20,22.2038004785,19.285140924377494 25 | L10-A10-VInt,22.058115104920002,12.258949372523475 26 | -------------------------------------------------------------------------------- /examples/clock-sync/MC_ClockSync6p.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE MC_ClockSync6p ----------------------------- 2 | 3 | Proc == { "p1", "p2", "p3" } 4 | 5 | CONSTANTS 6 | \* @type: Int; 7 | t_min, 8 | \* maximum message delay 9 | \* @type: Int; 10 | t_max 11 | 12 | VARIABLES 13 | \* the reference clock, inaccessible to the processes 14 | \* @type: Int; 15 | time, 16 | \* hardware clock of a process 17 | \* @type: Str -> Int; 18 | hc, 19 | \* clock adjustment of a process 20 | \* @type: Str -> Int; 21 | adj, 22 | \* clock diff for process j, as seen by a process j 23 | \* @type: <> -> Int; 24 | diff, 25 | \* messages sent by the processes 26 | \* @type: Set([src: Str, ts: Int]); 27 | msgs, 28 | \* messages received by the processes 29 | \* @type: Str -> Set([src: Str, ts: Int]); 30 | rcvd, 31 | \* the control state of a process 32 | \* @type: Str -> Str; 33 | state 34 | 35 | INSTANCE ClockSync6 36 | 37 | \* use --cinit=ConstInit to check for all t_min and t_max 38 | ConstInit == 39 | /\ t_min \in Nat 40 | /\ t_max \in Nat 41 | /\ t_min <= t_max 42 | 43 | =============================================================================== 44 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/apa-check.csv: -------------------------------------------------------------------------------- 1 | name,mean,stddev 2 | L5-A3-V2,8.560148971260002,0.5528989286439818 3 | L5-A3-V10,7.76568748248,0.7807962953319927 4 | L5-A3-V20,9.77925619694,1.9367794407298535 5 | L5-A3-VInt,7.9672250139999985,0.5294348841441368 6 | L5-A5-V2,9.18732471978,0.4209734191095961 7 | L5-A5-V10,10.343898464259999,1.3914369843598602 8 | L5-A5-V20,9.55594823522,0.5754807722371723 9 | L5-A5-VInt,13.649118924200002,1.055696581983742 10 | L5-A10-V2,85.04028448036,10.296992079251455 11 | L5-A10-V10,85.02309630816,7.86352913425745 12 | L5-A10-V20,91.51214424518001,8.510171993965796 13 | L5-A10-VInt,14.241532254180001,2.106926968522944 14 | L10-A3-V2,9.031462484939999,0.22207834397926787 15 | L10-A3-V10,9.264575033540002,0.4838716977292885 16 | L10-A3-V20,9.31052347608,0.5600223746363991 17 | L10-A3-VInt,10.827101651979998,2.0599597544746557 18 | L10-A5-V2,10.835191954819999,1.2904657942748305 19 | L10-A5-V10,10.34188628532,1.5799286993298067 20 | L10-A5-V20,10.682298069639998,0.8546255021265864 21 | L10-A5-VInt,15.207674570539998,0.6440971528391299 22 | L10-A10-V2,86.08995399646,9.317103978630866 23 | L10-A10-V10,89.31935055048001,10.363572288113692 24 | L10-A10-V20,95.07518977964,12.107457908277134 25 | L10-A10-VInt,16.090817656680002,1.3574954227003178 26 | -------------------------------------------------------------------------------- /examples/clock-sync/MC_ClockSync7.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE MC_ClockSync7 ----------------------------- 2 | EXTENDS Integers 3 | 4 | Proc == { "p1", "p2", "p3" } 5 | 6 | CONSTANTS 7 | \* @type: Int; 8 | t_min, 9 | \* maximum message delay 10 | \* @type: Int; 11 | t_max 12 | 13 | ASSUME(t_min >= 0 /\ t_max > t_min) 14 | 15 | VARIABLES 16 | \* the reference clock, inaccessible to the processes 17 | \* @type: Int; 18 | time, 19 | \* hardware clock of a process 20 | \* @type: Str -> Int; 21 | hc, 22 | \* clock adjustment of a process 23 | \* @type: Str -> Int; 24 | adj, 25 | \* clock diff for process j, as seen by a process j 26 | \* @type: <> -> Int; 27 | diff, 28 | \* messages sent by the processes 29 | \* @type: Set([src: Str, ts: Int]); 30 | msgs, 31 | \* messages received by the processes 32 | \* @type: Str -> Set([src: Str, ts: Int]); 33 | rcvd, 34 | \* the control state of a process 35 | \* @type: Str -> Str; 36 | state 37 | 38 | INSTANCE ClockSync7 39 | 40 | \* use --cinit=ConstInit to check for all t_min and t_max 41 | ConstInit == 42 | /\ t_min \in Nat 43 | /\ t_max \in Nat 44 | 45 | =============================================================================== 46 | -------------------------------------------------------------------------------- /examples/token-transfer/MC8.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC8 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | 4 | EXTENDS Integers 5 | 6 | CHAINS == { "A", "B" } 7 | CHANNELS == 8 | LET \* @type: (Str, Str) => <>; 9 | pair(i, j) == <> 10 | IN 11 | { pair("A", "B") } 12 | ACCOUNTS == { "reserve", "escrow", "anna", "boris" } 13 | DENOMS == { "A", "B", "B/A", "A/B" } 14 | GENESIS_SUPPLY == [ c \in CHAINS |-> 100 ] 15 | MK_DENOM == [ 16 | c \in CHAINS, d \in DENOMS |-> 17 | IF c = "A" /\ d = "B" 18 | THEN "A/B" 19 | ELSE IF c = "B" /\ d = "A" 20 | THEN "B/A" 21 | ELSE "invalid" 22 | ] 23 | 24 | \* introduce the global variables 25 | VARIABLES 26 | \* @type: <> -> Int; 27 | banks, 28 | \* @type: Set([seqno: Int, src: Str, dest: Str, data: [sender: Str, receiver: Str, denom: Str, amount: Int]]); 29 | sentPackets, 30 | \* The sequence numbers of delivered packets 31 | \* @type: Set(Int); 32 | deliveredNums, 33 | \* An imaginary global counter that we use to assign unique sequence numbers 34 | \* @type: Int; 35 | seqno 36 | 37 | INSTANCE TokenTransfer8 38 | =============================================================================== 39 | -------------------------------------------------------------------------------- /examples/erc20/erc20_mempool_typedefs.tla: -------------------------------------------------------------------------------- 1 | ---------------------- MODULE erc20_mempool_typedefs -------------------------- 2 | EXTENDS Variants 3 | 4 | (* 5 | Type definitions for the erc20 mempool. 6 | 7 | // The result of applying an ERC20 method 8 | @typeAlias: tx = 9 | NoneTx(NONE) 10 | | TransferTx({ sender: ADDR, toAddr: ADDR, amount: Int }) 11 | | ApproveTx({ sender: ADDR, spender: ADDR, amount: Int }) 12 | | TransferFromTx({ sender: ADDR, fromAddr: ADDR, toAddr: ADDR, amount: Int }) 13 | ; 14 | *) 15 | 16 | erc20_mempool_typedefs == TRUE 17 | 18 | \* @type: $tx; 19 | NoneTx == Variant("NoneTx", "none_OF_NONE") 20 | 21 | \* @type: (ADDR, ADDR, Int) => $tx; 22 | TransferTx(sender, toAddr, amount) == 23 | Variant("TransferTx", 24 | [ sender |-> sender, toAddr |-> toAddr, amount |-> amount ]) 25 | 26 | \* @type: (ADDR, ADDR, Int) => $tx; 27 | ApproveTx(sender, spender, amount) == 28 | Variant("ApproveTx", 29 | [ sender |-> sender, spender |-> spender, amount |-> amount ]) 30 | 31 | \* @type: (ADDR, ADDR, ADDR, Int) => $tx; 32 | TransferFromTx(sender, fromAddr, toAddr, amount) == 33 | Variant("TransferFromTx", 34 | [ sender |-> sender, 35 | fromAddr |-> fromAddr, toAddr |-> toAddr, amount |-> amount ]) 36 | 37 | ================================================================================ -------------------------------------------------------------------------------- /examples/erc20-approve-attack/MC_ERC20.tla: -------------------------------------------------------------------------------- 1 | ----------------------------- MODULE MC_ERC20 --------------------------------- 2 | \* an instance for model checking ERC20.tla with Apalache 3 | EXTENDS Integers, ERC20_typedefs 4 | 5 | \* Use the set of three addresses. 6 | \* We are using uninterpreted values, similar to TLC's model values. 7 | \* See: https://apalache.informal.systems/docs/HOWTOs/uninterpretedTypes.html 8 | ADDR10 == { 9 | "Alice_OF_ADDR", "Bob_OF_ADDR", "Eve_OF_ADDR", "Charlie_OF_ADDR", 10 | "Denis_OF_ADDR", "Fritz_OF_ADDR", "Gabi_OF_ADDR", "Igor_OF_ADDR", 11 | "Josef_OF_ADDR", "Kate_OF_ADDR" 12 | } 13 | ADDR5 == { 14 | "Alice_OF_ADDR", "Bob_OF_ADDR", "Eve_OF_ADDR", 15 | "Charlie_OF_ADDR", "Denis_OF_ADDR" 16 | } 17 | ADDR3 == { "Alice_OF_ADDR", "Bob_OF_ADDR", "Eve_OF_ADDR" } 18 | 19 | ADDR == ADDR3 20 | 21 | \* Apalache can draw constants from the set of all integers 22 | AMOUNTS == Int 23 | 24 | INITIAL_BALANCES == [a \in ADDR |-> 100] 25 | 26 | VARIABLES 27 | \* @type: ADDR -> Int; 28 | balanceOf, 29 | \* @type: <> -> Int; 30 | allowance, 31 | \* @type: Set(TX); 32 | pendingTransactions, 33 | \* Last executed transaction. 34 | \* @type: TX; 35 | lastTx, 36 | \* @type: Int; 37 | nextTxId 38 | 39 | \* instantiate the spec with ADDR, balances, allowances, and pendingTxs 40 | INSTANCE ERC20 41 | 42 | =============================================================================== 43 | -------------------------------------------------------------------------------- /examples/token-transfer/MC9.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC9 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | 4 | EXTENDS Integers 5 | 6 | CHAINS == { "A", "B" } 7 | CHANNELS == 8 | LET \* @type: (Str, Str) => <>; 9 | pair(i, j) == <> 10 | IN 11 | { pair("A", "B") } 12 | ACCOUNTS == { "reserve", "escrow", "anna", "boris" } 13 | DENOMS == { "A", "B", "B/A", "A/B" } 14 | GENESIS_SUPPLY == [ c \in CHAINS |-> 100 ] 15 | MK_DENOM == [ 16 | c \in CHAINS, d \in DENOMS |-> 17 | IF c = "A" /\ d = "B" 18 | THEN "A/B" 19 | ELSE IF c = "B" /\ d = "A" 20 | THEN "B/A" 21 | ELSE "invalid" 22 | ] 23 | UNMK_DENOM == [ 24 | c \in CHAINS, d \in DENOMS |-> 25 | IF c = "A" /\ d = "A/B" 26 | THEN "B" 27 | ELSE IF c = "B" /\ d = "B/A" 28 | THEN "A" 29 | ELSE "invalid" 30 | ] 31 | 32 | \* introduce the global variables 33 | VARIABLES 34 | \* @type: <> -> Int; 35 | banks, 36 | \* @type: Set([seqno: Int, src: Str, dest: Str, data: [sender: Str, receiver: Str, denom: Str, amount: Int]]); 37 | sentPackets, 38 | \* The sequence numbers of delivered packets 39 | \* @type: Set(Int); 40 | deliveredNums, 41 | \* An imaginary global counter that we use to assign unique sequence numbers 42 | \* @type: Int; 43 | seqno 44 | 45 | INSTANCE TokenTransfer9 46 | =============================================================================== 47 | -------------------------------------------------------------------------------- /examples/clock-sync/MC_ClockSync2.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE MC_ClockSync2 ----------------------------- 2 | 3 | t_min == 17 4 | t_max == 91 5 | 6 | VARIABLES 7 | \* the reference clock, inaccessible to the processes 8 | \* @type: Int; 9 | time, 10 | \* hardware clock of a process 11 | \* @type: Str -> Int; 12 | hc, 13 | \* clock adjustment of a process 14 | \* @type: Str -> Int; 15 | adj, 16 | \* messages sent by the processes 17 | \* @type: Set([src: Str, ts: Int]); 18 | msgs, 19 | \* the control state of a process 20 | \* @type: Str -> Str; 21 | state 22 | 23 | INSTANCE ClockSync2 24 | 25 | \* like TypeOK, but used only in initialization 26 | TypeInit == 27 | /\ time \in Nat 28 | /\ hc \in [ Proc -> Nat ] 29 | /\ adj \in [ Proc -> Int ] 30 | /\ state \in [ Proc -> State ] 31 | /\ \E t \in [ Proc -> Int ]: 32 | msgs \in SUBSET { [ src |-> p, ts |-> t[p] ]: p \in Proc } 33 | 34 | \* test that the clocks are non-decreasing 35 | Test1_Init == 36 | TypeInit 37 | 38 | Test1_Next == 39 | \E delta \in Int: 40 | AdvanceClocks(delta) 41 | 42 | Test1_Inv == 43 | /\ time' >= time 44 | /\ \A p \in Proc: hc'[p] >= hc[p] 45 | 46 | \* test that messages are sent 47 | Test2_Inv == 48 | \A p \in Proc: 49 | state[p] = "sent" <=> 50 | \E m \in msgs: 51 | m.src = p 52 | 53 | Test2_Init == 54 | /\ TypeInit 55 | /\ Test2_Inv 56 | 57 | Test2_Next == 58 | \E p \in Proc: 59 | SendMsg(p) 60 | 61 | =============================================================================== 62 | -------------------------------------------------------------------------------- /examples/clock-sync/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Run all unit tests 4 | 5 | set -e 6 | 7 | trap ctrl_c INT 8 | function ctrl_c() { 9 | echo "Terminating the test suite..." 10 | kill -9 $$ 11 | } 12 | 13 | VER="ghcr.io/informalsystems/apalache:unstable" 14 | docker pull "$VER" 15 | apalache="docker run --rm -v $(pwd):/var/apalache $VER" 16 | 17 | TEST="$1" 18 | 19 | function test() { 20 | if [ -z "$TEST" -o "$TEST" == $1 ]; then 21 | $apalache check --init=$2 --next=$3 --inv=$4 --length=1 $5 \ 22 | 2>&1 >test$1.out \ 23 | || echo "TEST $1 FAILED: $5 on $2 $3 $4" 24 | fi 25 | } 26 | 27 | test 1 Test1_Init Test1_Next Test1_Inv MC_ClockSync1.tla 28 | test 2 Test1_Init Test1_Next Test1_Inv MC_ClockSync2.tla 29 | test 3 Test1_Init Test1_Next Test1_Inv MC_ClockSync3.tla 30 | test 4 Test1_Init Test1_Next Test1_Inv MC_ClockSync4.tla 31 | test 5 Test1_Init Test1_Next Test1_Inv MC_ClockSync5.tla 32 | test 6 Test1_Init Test1_Next Test1_Inv MC_ClockSync6.tla 33 | 34 | test 7 Test2_Init Test2_Next Test2_Inv MC_ClockSync2.tla 35 | test 8 Test2_Init Test2_Next Test2_Inv MC_ClockSync3.tla 36 | test 9 Test2_Init Test2_Next Test2_Inv MC_ClockSync4.tla 37 | test 10 Test2_Init Test2_Next Test2_Inv MC_ClockSync5.tla 38 | test 11 Test2_Init Test2_Next Test2_Inv MC_ClockSync6.tla 39 | 40 | test 12 Test3_Init Test3_Next Test3_Inv MC_ClockSync3.tla 41 | test 13 Test3_Init Test3_Next Test3_Inv MC_ClockSync4.tla 42 | test 14 Test3_Init Test3_Next Test3_Inv MC_ClockSync5.tla 43 | test 15 Test3_Init Test3_Next Test3_Inv MC_ClockSync6.tla 44 | 45 | test 16 Test4_Init Test4_Next Test4_Inv MC_ClockSync4.tla 46 | test 17 Test4_Init Test4_Next Test4_Inv MC_ClockSync5.tla 47 | test 18 Test4_Init Test4_Next Test4_Inv MC_ClockSync6.tla 48 | 49 | -------------------------------------------------------------------------------- /examples/token-transfer/MC10.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE MC10 ------------------------------------- 2 | (* Fix the CONSTANTS to run a model checker *) 3 | 4 | EXTENDS Integers 5 | 6 | CHAINS == { "A", "B" } 7 | CHANNELS == 8 | LET \* @type: (Str, Str) => <>; 9 | pair(i, j) == <> 10 | IN 11 | { pair("A", "B") } 12 | ACCOUNTS == { "reserve", "escrow", "anna", "boris" } 13 | DENOMS == { "A", "B", "B/A", "A/B" } 14 | GENESIS_SUPPLY == [ c \in CHAINS |-> 100 ] 15 | MK_DENOM == [ 16 | c \in CHAINS, d \in DENOMS |-> 17 | IF c = "A" /\ d = "B" 18 | THEN "A/B" 19 | ELSE IF c = "B" /\ d = "A" 20 | THEN "B/A" 21 | ELSE "invalid" 22 | ] 23 | UNMK_DENOM == [ 24 | c \in CHAINS, d \in DENOMS |-> 25 | IF c = "A" /\ d = "A/B" 26 | THEN "B" 27 | ELSE IF c = "B" /\ d = "B/A" 28 | THEN "A" 29 | ELSE "invalid" 30 | ] 31 | 32 | \* introduce the global variables 33 | VARIABLES 34 | \* @type: <> -> Int; 35 | banks, 36 | \* @type: Set([seqno: Int, src: Str, dest: Str, data: [sender: Str, receiver: Str, denom: Str, amount: Int]]); 37 | sentPackets, 38 | \* The sequence numbers of delivered packets 39 | \* @type: Set(Int); 40 | deliveredNums, 41 | \* The sequence numbers of the packets that timed out, registered on destination 42 | \* @type: Set(Int); 43 | dstTimeoutNums, 44 | \* The sequence numbers of the packets that timed out, registered on source 45 | \* @type: Set(Int); 46 | srcTimeoutNums, 47 | \* An imaginary global counter that we use to assign unique sequence numbers 48 | \* @type: Int; 49 | seqno 50 | 51 | INSTANCE TokenTransfer10 52 | =============================================================================== 53 | -------------------------------------------------------------------------------- /examples/clock-sync/ClockSync1.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE ClockSync1 ------------------------------ 2 | (* 3 | * Incremental TLA+ specification of the clock synchronization algorithm from: 4 | * 5 | * Hagit Attiya, Jennifer Welch. Distributed Computing. Wiley Interscience, 2004, 6 | * p. 147, Algorithm 20. 7 | * 8 | * Assumptions: timestamps are natural numbers, not reals. 9 | * 10 | * Version 1: Setting up the clocks 11 | *) 12 | EXTENDS Integers 13 | 14 | VARIABLES 15 | \* the reference clock, inaccessible to the processes 16 | \* @type: Int; 17 | time, 18 | \* hardware clock of a process 19 | \* @type: Str -> Int; 20 | hc, 21 | \* clock adjustment of a process 22 | \* @type: Str -> Int; 23 | adj 24 | 25 | (***************************** DEFINITIONS *********************************) 26 | 27 | \* we fix the set to contain two processes 28 | Proc == { "p1", "p2" } 29 | 30 | \* the adjusted clock of process i 31 | AC(i) == hc[i] + adj[i] 32 | 33 | (*************************** INITIALIZATION ********************************) 34 | 35 | \* Initialization 36 | Init == 37 | /\ time \in Nat 38 | /\ hc \in [ Proc -> Nat ] 39 | /\ adj = [ p \in Proc |-> 0 ] 40 | 41 | (******************************* ACTIONS ***********************************) 42 | 43 | \* let the time flow 44 | AdvanceClocks(delta) == 45 | /\ delta > 0 46 | /\ time' = time + delta 47 | /\ hc' = [ p \in Proc |-> hc[p] + delta ] 48 | /\ UNCHANGED adj 49 | 50 | \* all actions together 51 | Next == 52 | \E delta \in Int: 53 | AdvanceClocks(delta) 54 | 55 | (******************************* PROPERTIES ***********************************) 56 | NaiveSkewInv == 57 | \A p, q \in Proc: 58 | AC(p) = AC(q) 59 | 60 | =============================================================================== 61 | -------------------------------------------------------------------------------- /examples/token-transfer/Test10.tla: -------------------------------------------------------------------------------- 1 | ---------------------------------- MODULE Test10 ------------------------------ 2 | \* Unit tests for version 10 3 | EXTENDS MC10 4 | 5 | \* bound sequence numbers for TypeOK 6 | BOUNDED_SEQ_NOS == 0..5 7 | \* bound amounts for TypeOK 8 | BOUNDED_AMOUNTS == 0..100 9 | 10 | \* An invariant on the variable shapes, assuming bounded sequence numbers. 11 | \* As we have to use bounded amount, Init may violate TypeOK! 12 | TypeOK == 13 | /\ banks \in [ CHAINS \X ACCOUNTS \X DENOMS -> BOUNDED_AMOUNTS ] 14 | /\ sentPackets \in SUBSET [ 15 | seqNo: BOUNDED_SEQ_NOS, 16 | src: CHAINS, 17 | dest: CHAINS, 18 | data: [ 19 | sender: ACCOUNTS, 20 | receiver: ACCOUNTS, 21 | denom: DENOMS, 22 | amount: BOUNDED_AMOUNTS 23 | ] 24 | ] 25 | /\ deliveredNums \in SUBSET BOUNDED_SEQ_NOS 26 | /\ dstTimeoutNums \in SUBSET BOUNDED_SEQ_NOS 27 | /\ srcTimeoutNums \in SUBSET BOUNDED_SEQ_NOS 28 | /\ seqno \in BOUNDED_SEQ_NOS 29 | 30 | \* the "classical" approach to produce states 31 | TypeOKandSupply == 32 | /\ TypeOK 33 | /\ AllChainsSupplyUnchanged 34 | 35 | \* the "generators" approach 36 | TestApplyTimeoutRequires == 37 | /\ \E b \in [ CHAINS \X ACCOUNTS \X DENOMS -> Nat ]: 38 | banks = b 39 | /\ \E n \in Nat: 40 | seqno = n 41 | \* bound the sizes of data structures by 100 42 | /\ sentPackets = Gen(100) 43 | /\ deliveredNums = Gen(100) 44 | /\ dstTimeoutNums = Gen(100) 45 | /\ srcTimeoutNums = Gen(100) 46 | /\ AllChainsSupplyUnchanged 47 | 48 | \* action to execute 49 | TestApplyTimeoutAction == 50 | ApplyTimeout 51 | 52 | \* what we expect at the output 53 | TestApplyTimeoutEnsures == 54 | AllChainsSupplyUnchanged 55 | 56 | =============================================================================== 57 | -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer2.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer2 --------------------------------- 2 | (* 3 | * This is an example of a very simplistic token transfer 4 | * for presentation purposes. 5 | * Do not use it in production, as it may lead to loss of tokens. 6 | * 7 | * Version 2: let the banks do the local banking 8 | * Version 1: introducing data structures 9 | * 10 | * Igor Konnov, 2021 11 | *) 12 | EXTENDS Integers, typedefs 13 | 14 | CONSTANT 15 | \* A set of blockchains, i.e., their names 16 | \* @type: Set(CHAIN); 17 | CHAINS, 18 | \* A set of accounts, i.e., their names 19 | \* @type: Set(ACCOUNT); 20 | ACCOUNTS 21 | 22 | VARIABLES 23 | \* For every chain and account, store the amount of tokens in the account 24 | \* @type: ADDR -> Int; 25 | banks 26 | 27 | \* Initialize the world, e.g., from the last upgrade 28 | Init == 29 | \E b \in [ CHAINS \X ACCOUNTS -> Nat ]: 30 | /\ \A c \in CHAINS: 31 | b[c, "reserve"] > 0 32 | /\ banks = b 33 | 34 | \* Transfer the tokens from on account to another (on the same chain) 35 | LocalTransfer(chain, from, to, amount) == 36 | /\ banks[chain, from] >= amount 37 | /\ from /= to 38 | /\ banks[chain, from] - amount >= 0 39 | /\ banks' = [banks EXCEPT 40 | ![chain, from] = banks[chain, from] - amount, 41 | ![chain, to] = banks[chain, to] + amount 42 | ] 43 | 44 | \* Update the world 45 | Next == 46 | \E chain \in CHAINS, from, to \in ACCOUNTS, amount \in Nat: 47 | LocalTransfer(chain, from, to, amount) 48 | 49 | (************************** PROPERTIES ***************************************) 50 | 51 | \* every bank always has reserves 52 | ReservesInv == 53 | \A chain \in CHAINS: 54 | banks[chain, "reserve"] > 0 55 | 56 | \* no bank account goes negative 57 | NoNegativeAccounts == 58 | \A address \in DOMAIN banks: 59 | banks[address] >= 0 60 | 61 | =============================================================================== 62 | -------------------------------------------------------------------------------- /examples/clock-sync/MC_ClockSync3.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE MC_ClockSync3 ----------------------------- 2 | 3 | t_min == 17 4 | t_max == 91 5 | 6 | VARIABLES 7 | \* the reference clock, inaccessible to the processes 8 | \* @type: Int; 9 | time, 10 | \* hardware clock of a process 11 | \* @type: Str -> Int; 12 | hc, 13 | \* clock adjustment of a process 14 | \* @type: Str -> Int; 15 | adj, 16 | \* messages sent by the processes 17 | \* @type: Set([src: Str, ts: Int]); 18 | msgs, 19 | \* messages received by the processes 20 | \* @type: Str -> Set([src: Str, ts: Int]); 21 | rcvd, 22 | \* the control state of a process 23 | \* @type: Str -> Str; 24 | state 25 | 26 | INSTANCE ClockSync3 27 | 28 | \* like TypeOK, but used only in initialization 29 | TypeInit == 30 | /\ time \in Nat 31 | /\ hc \in [ Proc -> Nat ] 32 | /\ adj \in [ Proc -> Int ] 33 | /\ state \in [ Proc -> State ] 34 | /\ \E t \in [ Proc -> Int ]: 35 | msgs \in SUBSET { [ src |-> p, ts |-> t[p] ]: p \in Proc } 36 | /\ rcvd \in [ Proc -> SUBSET msgs ] 37 | 38 | \* test that the clocks are non-decreasing 39 | Test1_Init == 40 | TypeInit 41 | 42 | Test1_Next == 43 | \E delta \in Int: 44 | AdvanceClocks(delta) 45 | 46 | Test1_Inv == 47 | /\ time' >= time 48 | /\ \A p \in Proc: hc'[p] >= hc[p] 49 | 50 | \* test that messages are sent 51 | Test2_Inv == 52 | \A p \in Proc: 53 | state[p] = "sent" <=> 54 | \E m \in msgs: 55 | m.src = p 56 | 57 | Test2_Init == 58 | /\ TypeInit 59 | /\ Test2_Inv 60 | 61 | Test2_Next == 62 | \E p \in Proc: 63 | SendMsg(p) 64 | 65 | \* test that messages are received within [t_min, t_max] 66 | Test3_Inv == 67 | /\ \A m \in msgs: 68 | \* no messages from the future 69 | m.ts <= hc[m.src] 70 | /\ \A p \in Proc: 71 | \A m \in rcvd[p]: 72 | \* the message is received no earlier than after t_min 73 | hc[m.src] >= m.ts + t_min 74 | /\ \A m \in msgs: 75 | \* the message is received no later than before t_max 76 | m.ts >= hc[m.src] + t_max => 77 | \A p \in Proc: 78 | m \in rcvd[p] 79 | 80 | Test3_Init == 81 | /\ TypeInit 82 | /\ Test3_Inv 83 | 84 | Test3_Next == 85 | \/ \E delta \in Int: 86 | AdvanceClocks(delta) 87 | \/ \E p \in Proc: 88 | ReceiveMsg(p) 89 | 90 | =============================================================================== 91 | -------------------------------------------------------------------------------- /examples/clock-sync/ClockSync2.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE ClockSync2 ------------------------------ 2 | (* 3 | * Incremental TLA+ specification of the clock synchronization algorithm from: 4 | * 5 | * Hagit Attiya, Jennifer Welch. Distributed Computing. Wiley Interscience, 2004, 6 | * p. 147, Algorithm 20. 7 | * 8 | * Assumptions: timestamps are natural numbers, not reals. 9 | * 10 | * Version 2: Sending messages 11 | * Version 1: Setting up the clocks 12 | *) 13 | EXTENDS Integers 14 | 15 | CONSTANTS 16 | \* minimum message delay 17 | \* @type: Int; 18 | t_min, 19 | \* maximum message delay 20 | \* @type: Int; 21 | t_max 22 | 23 | ASSUME(t_min >= 0 /\ t_max > t_min) 24 | 25 | VARIABLES 26 | \* the reference clock, inaccessible to the processes 27 | \* @type: Int; 28 | time, 29 | \* hardware clock of a process 30 | \* @type: Str -> Int; 31 | hc, 32 | \* clock adjustment of a process 33 | \* @type: Str -> Int; 34 | adj, 35 | \* messages sent by the processes 36 | \* @type: Set([src: Str, ts: Int]); 37 | msgs, 38 | \* the control state of a process 39 | \* @type: Str -> Str; 40 | state 41 | 42 | (***************************** DEFINITIONS *********************************) 43 | 44 | \* we fix the set to contain two processes 45 | Proc == { "p1", "p2" } 46 | 47 | \* control states 48 | State == { "init", "sent", "done" } 49 | 50 | \* the adjusted clock of process i 51 | AC(i) == hc[i] + adj[i] 52 | 53 | (*************************** INITIALIZATION ********************************) 54 | 55 | \* Initialization 56 | Init == 57 | /\ time \in Nat 58 | /\ hc \in [ Proc -> Nat ] 59 | /\ adj = [ p \in Proc |-> 0 ] 60 | /\ state = [ p \in Proc |-> "init" ] 61 | /\ msgs = {} 62 | 63 | (******************************* ACTIONS ***********************************) 64 | 65 | \* send the value of the hardware clock 66 | SendMsg(p) == 67 | /\ state[p] = "init" 68 | /\ msgs' = msgs \union { [ src |-> p, ts |-> hc[p] ] } 69 | /\ state' = [ state EXCEPT ![p] = "sent" ] 70 | /\ UNCHANGED <> 71 | 72 | \* let the time flow 73 | AdvanceClocks(delta) == 74 | /\ delta > 0 75 | /\ time' = time + delta 76 | /\ hc' = [ p \in Proc |-> hc[p] + delta ] 77 | /\ UNCHANGED <> 78 | 79 | \* all actions together 80 | Next == 81 | \/ \E delta \in Int: 82 | AdvanceClocks(delta) 83 | \/ \E p \in Proc: 84 | SendMsg(p) 85 | 86 | =============================================================================== 87 | -------------------------------------------------------------------------------- /examples/erc20/README.md: -------------------------------------------------------------------------------- 1 | # Specifying the ERC20 interface and mempool 2 | 3 | This directory contains several TLA+ specifications related to the 4 | ERC20 token, see [EIP-20][]: 5 | 6 | - [erc20.tla][] is a specification of the ERC20 7 | that follows the implementation of [ERC20 by OpenZeppelin][]. 8 | It does not specify a state machine, but it specifies the reusable 9 | logic of ERC20. 10 | 11 | - [erc20_tests.tla][] is a state machine that we use to show that: 12 | 13 | - ERC20 preserves its initial total supply, see [totalSupplyInv][], 14 | - ERC20 does not transfer to the address `0x0`, see [zeroAddressInv][], 15 | - ERC20 does not overflow the account balances, see [noOverflowInv][]. 16 | - Method `transfer` satisfies the intuitive post-condition, 17 | see [transferPrePost][]. 18 | 19 | - [erc20_mempool.tla][] is a state machine that models the interaction with an 20 | ERC20 contract via mempool, that is, when the methods are called by external 21 | users. We use this state machine to demonstrate how to detect the well-known 22 | issue with the order of `transferFrom` and `approve`, see the [issue #20][]. 23 | 24 | These specifications were produced out of the Quint specification [erc20.qnt][] 25 | by hand. While doing this translation, we made our best to use the more 26 | expressive constructs of TLA+ such as nested EXCEPTs. Hence, you can use these 27 | two specifications to compare the syntax of Quint with the syntax of TLA+. 28 | 29 | This specification is presented as a running example in the tutorial at 30 | [TLA+ Community Event 2023][] by [Igor Konnov][]. 31 | 32 | [erc20.tla]: ./erc20.tla 33 | [erc20_tests.tla]: ./erc20_tests.tla 34 | [erc20_mempool.tla]: ./erc20_mempool.tla 35 | [EIP-20]: https://eips.ethereum.org/EIPS/eip-20 36 | [ERC20 by OpenZeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol 37 | [totalSupplyInv]: https://github.com/informalsystems/tla-apalache-workshop/blob/0f13bf0547ca7f8c3f9b6ccdcd0c90b940d1a9a5/examples/erc20/erc20_tests.tla#L54 38 | [zeroAddressInv]: https://github.com/informalsystems/tla-apalache-workshop/blob/0f13bf0547ca7f8c3f9b6ccdcd0c90b940d1a9a5/examples/erc20/erc20_tests.tla#L56 39 | [noOverflowInv]: https://github.com/informalsystems/tla-apalache-workshop/blob/0f13bf0547ca7f8c3f9b6ccdcd0c90b940d1a9a5/examples/erc20/erc20_tests.tla#L58 40 | [transferPrePost]: https://github.com/informalsystems/tla-apalache-workshop/blob/0f13bf0547ca7f8c3f9b6ccdcd0c90b940d1a9a5/examples/erc20/erc20_tests.tla#L83-L105 41 | [issue #20]: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 42 | [erc20.qnt]: https://github.com/informalsystems/quint/tree/main/examples/solidity/ERC20 43 | [Quint]: https://github.com/informalsystems/quint 44 | [TLA+ Community Event 2023]: https://conf.tlapl.us/2023/ 45 | [Igor Konnov]: https://github.com/konnov -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer4.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer4 --------------------------------- 2 | (* 3 | * This is an example of a very simplistic token transfer 4 | * for presentation purposes. 5 | * Do not use it in production, as it may lead to loss of tokens. 6 | * 7 | * Version 4: bounding the amounts to help TLC 8 | * Version 3: fixing the invariants and introducing more 9 | * Version 2: let the banks do the local banking 10 | * Version 1: introducing data structures 11 | * 12 | * Igor Konnov, 2021 13 | *) 14 | EXTENDS Integers, Apalache 15 | 16 | CONSTANT 17 | \* A set of blockchains, i.e., their names 18 | \* @type: Set(Str); 19 | CHAINS, 20 | \* A set of accounts, i.e., their names 21 | \* @type: Set(Str); 22 | ACCOUNTS, 23 | \* A set of all possible amounts 24 | \* @type: Set(Int); 25 | AMOUNTS, 26 | \* Initial supply for every chain 27 | \* @type: Str -> Int; 28 | GENESIS_SUPPLY 29 | 30 | VARIABLES 31 | \* For every chain and account, store the amount of tokens in the account 32 | \* @type: <> -> Int; 33 | banks 34 | 35 | (*************************** OPERATORS ***************************************) 36 | \* @type: (ADDR -> Int, Set(ADDR)) => Int; 37 | SumAddresses(amounts, Addrs) == 38 | LET Add(sum, addr) == sum + amounts[addr] IN 39 | ApaFoldSet(Add, 0, Addrs) 40 | 41 | ChainSupply(chain) == 42 | SumAddresses({chain} \X ACCOUNTS) 43 | 44 | (**************************** SYSTEM *****************************************) 45 | 46 | \* Initialize the world, e.g., from the last upgrade 47 | Init == 48 | \E b \in [ CHAINS \X ACCOUNTS -> AMOUNTS ]: 49 | /\ \A chain \in CHAINS: 50 | b[chain, "reserve"] > 0 51 | /\ banks = b 52 | /\ \A c \in CHAINS: 53 | ChainSupply(c) = GENESIS_SUPPLY[c] 54 | 55 | \* Transfer the tokens from on account to another (on the same chain) 56 | LocalTransfer(chain, from, to, amount) == 57 | /\ banks[chain, from] >= amount 58 | /\ from /= to 59 | /\ banks' = [banks EXCEPT 60 | ![chain, from] = banks[chain, from] - amount, 61 | ![chain, to] = banks[chain, to] + amount 62 | ] 63 | 64 | \* Update the world 65 | Next == 66 | \E chain \in CHAINS, from, to \in ACCOUNTS, amount \in AMOUNTS: 67 | LocalTransfer(chain, from, to, amount) 68 | 69 | (************************** PROPERTIES ***************************************) 70 | 71 | \* every bank always has reserves 72 | ReservesInv == 73 | \A chain \in CHAINS: 74 | banks[chain, "reserve"] > 0 75 | 76 | \* no bank account goes negative 77 | NoNegativeAccounts == 78 | \A address \in DOMAIN banks: 79 | banks[address] >= 0 80 | 81 | \* the supply remains constant 82 | ChainSupplyUnchanged == 83 | \A chain \in CHAINS: 84 | LET supply == ChainSupply(chain) IN 85 | supply = GENESIS_SUPPLY[chain] 86 | 87 | 88 | =============================================================================== 89 | -------------------------------------------------------------------------------- /examples/clock-sync/MC_ClockSync4.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE MC_ClockSync4 ----------------------------- 2 | 3 | t_min == 17 4 | t_max == 91 5 | 6 | VARIABLES 7 | \* the reference clock, inaccessible to the processes 8 | \* @type: Int; 9 | time, 10 | \* hardware clock of a process 11 | \* @type: Str -> Int; 12 | hc, 13 | \* clock adjustment of a process 14 | \* @type: Str -> Int; 15 | adj, 16 | \* clock diff for process j, as seen by a process j 17 | \* @type: <> -> Int; 18 | diff, 19 | \* messages sent by the processes 20 | \* @type: Set([src: Str, ts: Int]); 21 | msgs, 22 | \* messages received by the processes 23 | \* @type: Str -> Set([src: Str, ts: Int]); 24 | rcvd, 25 | \* the control state of a process 26 | \* @type: Str -> Str; 27 | state 28 | 29 | INSTANCE ClockSync4 30 | 31 | \* like TypeOK, but used only in initialization 32 | TypeInit == 33 | /\ time \in Nat 34 | /\ hc \in [ Proc -> Nat ] 35 | /\ adj \in [ Proc -> Int ] 36 | /\ diff \in [ Proc \X Proc -> Int ] 37 | /\ state \in [ Proc -> State ] 38 | /\ \E t \in [ Proc -> Int ]: 39 | msgs \in SUBSET { [ src |-> p, ts |-> t[p] ]: p \in Proc } 40 | /\ rcvd \in [ Proc -> SUBSET msgs ] 41 | 42 | \* test that the clocks are non-decreasing 43 | Test1_Init == 44 | TypeInit 45 | 46 | Test1_Next == 47 | \E delta \in Int: 48 | AdvanceClocks(delta) 49 | 50 | Test1_Inv == 51 | /\ time' >= time 52 | /\ \A p \in Proc: hc'[p] >= hc[p] 53 | 54 | \* test that messages are sent 55 | Test2_Inv == 56 | \A p \in Proc: 57 | state[p] \in { "sent", "sync" } <=> 58 | \E m \in msgs: 59 | m.src = p 60 | 61 | Test2_Init == 62 | /\ TypeInit 63 | /\ Test2_Inv 64 | 65 | Test2_Next == 66 | \E p \in Proc: 67 | SendMsg(p) 68 | 69 | \* test that messages are received within [t_min, t_max] 70 | Test3_Inv == 71 | /\ \A m \in msgs: 72 | \* no messages from the future 73 | m.ts <= hc[m.src] 74 | /\ \A p \in Proc: 75 | \A m \in rcvd[p]: 76 | \* the message is received no earlier than after t_min 77 | hc[m.src] >= m.ts + t_min 78 | /\ \A m \in msgs: 79 | \* the message is received no later than before t_max 80 | m.ts >= hc[m.src] + t_max => 81 | \A p \in Proc: 82 | m \in rcvd[p] 83 | 84 | Test3_Init == 85 | /\ TypeInit 86 | /\ Test3_Inv 87 | 88 | Test3_Next == 89 | \/ \E delta \in Int: 90 | AdvanceClocks(delta) 91 | \/ \E p \in Proc: 92 | ReceiveMsg(p) 93 | 94 | \* test that a process is in "sync" state 95 | \* iff it has received messages from all the processes 96 | Test4_Inv == 97 | \A p \in Proc: 98 | state[p] = "sync" <=> 99 | { m.src: m \in rcvd[p] } = Proc 100 | 101 | Test4_Init == 102 | /\ TypeInit 103 | /\ Test4_Inv 104 | 105 | Test4_Next == 106 | \E p \in Proc: 107 | ReceiveMsg(p) 108 | 109 | =============================================================================== 110 | -------------------------------------------------------------------------------- /examples/clock-sync/MC_ClockSync5.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE MC_ClockSync5 ----------------------------- 2 | 3 | t_min == 17 4 | t_max == 91 5 | 6 | VARIABLES 7 | \* the reference clock, inaccessible to the processes 8 | \* @type: Int; 9 | time, 10 | \* hardware clock of a process 11 | \* @type: Str -> Int; 12 | hc, 13 | \* clock adjustment of a process 14 | \* @type: Str -> Int; 15 | adj, 16 | \* clock diff for process j, as seen by a process j 17 | \* @type: <> -> Int; 18 | diff, 19 | \* messages sent by the processes 20 | \* @type: Set([src: Str, ts: Int]); 21 | msgs, 22 | \* messages received by the processes 23 | \* @type: Str -> Set([src: Str, ts: Int]); 24 | rcvd, 25 | \* the control state of a process 26 | \* @type: Str -> Str; 27 | state 28 | 29 | INSTANCE ClockSync5 30 | 31 | \* like TypeOK, but used only in initialization 32 | TypeInit == 33 | /\ time \in Nat 34 | /\ hc \in [ Proc -> Nat ] 35 | /\ adj \in [ Proc -> Int ] 36 | /\ diff \in [ Proc \X Proc -> Int ] 37 | /\ state \in [ Proc -> State ] 38 | /\ \E t \in [ Proc -> Int ]: 39 | msgs \in SUBSET { [ src |-> p, ts |-> t[p] ]: p \in Proc } 40 | /\ rcvd \in [ Proc -> SUBSET msgs ] 41 | 42 | \* test that the clocks are non-decreasing 43 | Test1_Init == 44 | TypeInit 45 | 46 | Test1_Next == 47 | \E delta \in Int: 48 | AdvanceClocks(delta) 49 | 50 | Test1_Inv == 51 | /\ time' >= time 52 | /\ \A p \in Proc: hc'[p] >= hc[p] 53 | 54 | \* test that messages are sent 55 | Test2_Inv == 56 | \A p \in Proc: 57 | state[p] \in { "sent", "sync" } <=> 58 | \E m \in msgs: 59 | m.src = p 60 | 61 | Test2_Init == 62 | /\ TypeInit 63 | /\ Test2_Inv 64 | 65 | Test2_Next == 66 | \E p \in Proc: 67 | SendMsg(p) 68 | 69 | \* test that messages are received within [t_min, t_max] 70 | Test3_Inv == 71 | /\ \A m \in msgs: 72 | \* no messages from the future 73 | m.ts <= hc[m.src] 74 | /\ \A p \in Proc: 75 | \A m \in rcvd[p]: 76 | \* the message is received no earlier than after t_min 77 | hc[m.src] >= m.ts + t_min 78 | /\ \A m \in msgs: 79 | \* the message is received no later than before t_max 80 | m.ts >= hc[m.src] + t_max => 81 | \A p \in Proc: 82 | m \in rcvd[p] 83 | 84 | Test3_Init == 85 | /\ TypeInit 86 | /\ Test3_Inv 87 | 88 | Test3_Next == 89 | \/ \E delta \in Int: 90 | AdvanceClocks(delta) 91 | \/ \E p \in Proc: 92 | ReceiveMsg(p) 93 | 94 | \* test that a process is in "sync" state 95 | \* iff it has received messages from all other processes 96 | Test4_Inv == 97 | \A p \in Proc: 98 | state[p] = "sync" <=> 99 | { m.src: m \in rcvd[p] } = Proc \ { p } 100 | 101 | Test4_Init == 102 | /\ TypeInit 103 | /\ Test4_Inv 104 | 105 | Test4_Next == 106 | \E p \in Proc: 107 | ReceiveMsg(p) 108 | 109 | =============================================================================== 110 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/fig/plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import matplotlib.pyplot as plt 4 | import matplotlib.cbook as cbook 5 | import matplotlib as lib 6 | import numpy as np 7 | from matplotlib.backends.backend_pdf import PdfPages 8 | 9 | def plot_sim(): 10 | data = np.genfromtxt('sim-cpp.csv', dtype=None, delimiter=',', names=True) 11 | 12 | fig = plt.figure() 13 | plt.rcParams.update({'font.size': 11}) 14 | 15 | plt.errorbar(data['name'], data['mean'], data['stddev'], 16 | linestyle='None', marker='o', 17 | label='Random simulation', 18 | ecolor='red', 19 | color='red', 20 | markersize=3, 21 | capsize=3) 22 | plt.xlabel('Simulation for L steps, A addresses, V values') 23 | plt.ylabel('Time, seconds') 24 | plt.xticks(rotation=35) 25 | #plt.semilogy() 26 | plt.ylim([-30, 400]) 27 | plt.grid(True, alpha=.2) 28 | plt.legend(loc=0) 29 | plt.tight_layout() 30 | with PdfPages('sim-cpp.pdf') as pdf: 31 | pdf.savefig(fig) 32 | 33 | plt.clf() 34 | 35 | def plot_apa_sim(): 36 | data = np.genfromtxt('apa-simulate.csv', 37 | dtype=None, delimiter=',', names=True) 38 | 39 | fig = plt.figure() 40 | plt.rcParams.update({'font.size': 11}) 41 | 42 | plt.errorbar(data['name'], data['mean'], data['stddev'], 43 | linestyle='None', marker='o', 44 | label='Apalache simulate', 45 | ecolor='blue', 46 | color='blue', 47 | markersize=3, 48 | capsize=3) 49 | plt.xlabel('Symbolic simulation for L steps, A addresses, V values') 50 | plt.ylabel('Time, seconds') 51 | plt.xticks(rotation=30) 52 | #plt.semilogy() 53 | plt.ylim([-30, 400]) 54 | plt.grid(True, alpha=.2) 55 | plt.legend(loc=0) 56 | plt.tight_layout() 57 | with PdfPages('apa-simulate.pdf') as pdf: 58 | pdf.savefig(fig) 59 | 60 | plt.clf() 61 | 62 | def plot_apa_check(): 63 | data = np.genfromtxt('apa-check.csv', 64 | dtype=None, delimiter=',', names=True) 65 | 66 | fig = plt.figure() 67 | plt.rcParams.update({'font.size': 11}) 68 | 69 | plt.errorbar(data['name'], data['mean'], data['stddev'], 70 | linestyle='None', marker='o', 71 | label='Apalache check', 72 | color='green', 73 | ecolor='green', 74 | markersize=3, 75 | capsize=3) 76 | plt.xlabel('Bounded MC for L steps, A addresses, V values') 77 | plt.ylabel('Time, seconds') 78 | plt.xticks(rotation=40) 79 | #plt.semilogy() 80 | plt.ylim([-30, 400]) 81 | plt.grid(True, alpha=.2) 82 | plt.legend(loc=0) 83 | plt.tight_layout() 84 | with PdfPages('apa-check.pdf') as pdf: 85 | pdf.savefig(fig) 86 | 87 | plt.clf() 88 | 89 | if __name__ == "__main__": 90 | plt.rc('figure', figsize=[7,6]) 91 | plot_sim() 92 | plot_apa_sim() 93 | plot_apa_check() 94 | -------------------------------------------------------------------------------- /examples/clock-sync/MC_ClockSync6.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE MC_ClockSync6 ----------------------------- 2 | 3 | Proc == { "p1", "p2", "p3", "p4" } 4 | t_min == 17 5 | t_max == 91 6 | 7 | VARIABLES 8 | \* the reference clock, inaccessible to the processes 9 | \* @type: Int; 10 | time, 11 | \* hardware clock of a process 12 | \* @type: Str -> Int; 13 | hc, 14 | \* clock adjustment of a process 15 | \* @type: Str -> Int; 16 | adj, 17 | \* clock diff for process j, as seen by a process j 18 | \* @type: <> -> Int; 19 | diff, 20 | \* messages sent by the processes 21 | \* @type: Set([src: Str, ts: Int]); 22 | msgs, 23 | \* messages received by the processes 24 | \* @type: Str -> Set([src: Str, ts: Int]); 25 | rcvd, 26 | \* the control state of a process 27 | \* @type: Str -> Str; 28 | state 29 | 30 | INSTANCE ClockSync6 31 | 32 | \* like TypeOK, but used only in initialization 33 | TypeInit == 34 | /\ time \in Nat 35 | /\ hc \in [ Proc -> Nat ] 36 | /\ adj \in [ Proc -> Int ] 37 | /\ diff \in [ Proc \X Proc -> Int ] 38 | /\ state \in [ Proc -> State ] 39 | /\ \E t \in [ Proc -> Int ]: 40 | msgs \in SUBSET { [ src |-> p, ts |-> t[p] ]: p \in Proc } 41 | /\ rcvd \in [ Proc -> SUBSET msgs ] 42 | 43 | \* test that the clocks are non-decreasing 44 | Test1_Init == 45 | TypeInit 46 | 47 | Test1_Next == 48 | \E delta \in Int: 49 | AdvanceClocks(delta) 50 | 51 | Test1_Inv == 52 | /\ time' >= time 53 | /\ \A p \in Proc: hc'[p] >= hc[p] 54 | 55 | \* test that messages are sent 56 | Test2_Inv == 57 | \A p \in Proc: 58 | state[p] \in { "sent", "sync" } <=> 59 | \E m \in msgs: 60 | m.src = p 61 | 62 | Test2_Init == 63 | /\ TypeInit 64 | /\ Test2_Inv 65 | 66 | Test2_Next == 67 | \E p \in Proc: 68 | SendMsg(p) 69 | 70 | \* test that messages are received within [t_min, t_max] 71 | Test3_Inv == 72 | /\ \A m \in msgs: 73 | \* no messages from the future 74 | m.ts <= hc[m.src] 75 | /\ \A p \in Proc: 76 | \A m \in rcvd[p]: 77 | \* the message is received no earlier than after t_min 78 | hc[m.src] >= m.ts + t_min 79 | /\ \A m \in msgs: 80 | \* the message is received no later than before t_max 81 | m.ts >= hc[m.src] + t_max => 82 | \A p \in Proc: 83 | m \in rcvd[p] 84 | 85 | Test3_Init == 86 | /\ TypeInit 87 | /\ Test3_Inv 88 | 89 | Test3_Next == 90 | \/ \E delta \in Int: 91 | AdvanceClocks(delta) 92 | \/ \E p \in Proc: 93 | ReceiveMsg(p) 94 | 95 | \* test that a process is in "sync" state 96 | \* iff it has received messages from all the processes 97 | Test4_Inv == 98 | \A p \in Proc: 99 | state[p] = "sync" <=> 100 | { m.src: m \in rcvd[p] } = Proc \ { p } 101 | 102 | Test4_Init == 103 | /\ TypeInit 104 | /\ Test4_Inv 105 | 106 | Test4_Next == 107 | \E p \in Proc: 108 | ReceiveMsg(p) 109 | 110 | =============================================================================== 111 | -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer5.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer5 --------------------------------- 2 | (* 3 | * This is an example of a very simplistic token transfer 4 | * for presentation purposes. 5 | * Do not use it in production, as it may lead to loss of tokens. 6 | * 7 | * Version 5: introducing an inductive invariant for Apalache 8 | * Version 4: bounding the amounts to help the model checkers 9 | * Version 3: fixing the invariants and introducing more 10 | * Version 2: let the banks do the local banking 11 | * Version 1: introducing data structures 12 | * 13 | * Igor Konnov, 2021 14 | *) 15 | EXTENDS Integers, Apalache, typedefs 16 | 17 | CONSTANT 18 | \* A set of blockchains, i.e., their names 19 | \* @type: Set(Str); 20 | CHAINS, 21 | \* A set of accounts, i.e., their names 22 | \* @type: Set(Str); 23 | ACCOUNTS, 24 | \* Initial supply for every chain 25 | \* @type: Str -> Int; 26 | GENESIS_SUPPLY 27 | 28 | 29 | VARIABLES 30 | \* For every chain and account, store the amount of tokens in the account 31 | \* @type: <> -> Int; 32 | banks 33 | 34 | (*************************** OPERATORS ***************************************) 35 | \* @type: (ADDR -> Int, Set(ADDR)) => Int; 36 | SumAddresses(amounts, Addrs) == 37 | LET Add(sum, addr) == sum + amounts[addr] IN 38 | ApaFoldSet(Add, 0, Addrs) 39 | 40 | \* @type: (ADDR -> Int, CHAIN) => Int; 41 | ChainSupply(amounts, chain) == 42 | SumAddresses(amounts, {chain} \X ACCOUNTS) 43 | 44 | (**************************** SYSTEM *****************************************) 45 | 46 | \* Initialize the world, e.g., from the last upgrade 47 | Init == 48 | \E b \in [ CHAINS \X ACCOUNTS -> Nat ]: 49 | /\ \A chain \in CHAINS: 50 | /\ b[chain, "reserve"] > 0 51 | /\ ChainSupply(b, chain) = GENESIS_SUPPLY[chain] 52 | /\ banks = b 53 | 54 | 55 | \* Transfer the tokens from on account to another (on the same chain) 56 | LocalTransfer(chain, from, to, amount) == 57 | /\ banks[chain, from] >= amount 58 | /\ from /= to 59 | /\ banks' = [banks EXCEPT 60 | ![chain, from] = banks[chain, from] - amount, 61 | ![chain, to] = banks[chain, to] + amount 62 | ] 63 | 64 | \* Update the world 65 | Next == 66 | \E chain \in CHAINS, from, to \in ACCOUNTS, amount \in Nat: 67 | LocalTransfer(chain, from, to, amount) 68 | 69 | (************************** PROPERTIES ***************************************) 70 | 71 | \* every bank always has reserves 72 | ReservesInv == 73 | \A chain \in CHAINS: 74 | banks[chain, "reserve"] > 0 75 | 76 | \* no bank account goes negative 77 | NoNegativeAccounts == 78 | \A address \in DOMAIN banks: 79 | banks[address] >= 0 80 | 81 | \* the supply remains constant 82 | ChainSupplyUnchanged == 83 | \A chain \in CHAINS: 84 | LET supply == ChainSupply(banks, chain) IN 85 | supply = GENESIS_SUPPLY[chain] 86 | 87 | (***************** INDUCTIVE INVARIANT ***************************************) 88 | TypeOK == 89 | banks \in [ CHAINS \X ACCOUNTS -> Nat ] 90 | 91 | IndInv == 92 | /\ TypeOK 93 | /\ \A c \in CHAINS: 94 | ChainSupply(banks, c) = GENESIS_SUPPLY[c] 95 | 96 | =============================================================================== 97 | -------------------------------------------------------------------------------- /examples/clock-sync/ClockSync3.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE ClockSync3 ------------------------------ 2 | (* 3 | * Incremental TLA+ specification of the clock synchronization algorithm from: 4 | * 5 | * Hagit Attiya, Jennifer Welch. Distributed Computing. Wiley Interscience, 2004, 6 | * p. 147, Algorithm 20. 7 | * 8 | * Assumptions: timestamps are natural numbers, not reals. 9 | * 10 | * Version 3: Receiving messages 11 | * Version 2: Sending messages 12 | * Version 1: Setting up the clocks 13 | *) 14 | EXTENDS Integers 15 | 16 | CONSTANTS 17 | \* minimum message delay 18 | \* @type: Int; 19 | t_min, 20 | \* maximum message delay 21 | \* @type: Int; 22 | t_max 23 | 24 | ASSUME(t_min >= 0 /\ t_max > t_min) 25 | 26 | VARIABLES 27 | \* the reference clock, inaccessible to the processes 28 | \* @type: Int; 29 | time, 30 | \* hardware clock of a process 31 | \* @type: Str -> Int; 32 | hc, 33 | \* clock adjustment of a process 34 | \* @type: Str -> Int; 35 | adj, 36 | \* messages sent by the processes 37 | \* @type: Set([src: Str, ts: Int]); 38 | msgs, 39 | \* messages received by the processes 40 | \* @type: Str -> Set([src: Str, ts: Int]); 41 | rcvd, 42 | \* the control state of a process 43 | \* @type: Str -> Str; 44 | state 45 | 46 | (***************************** DEFINITIONS *********************************) 47 | 48 | \* we fix the set to contain two processes 49 | Proc == { "p1", "p2" } 50 | 51 | \* control states 52 | State == { "init", "sent", "sync" } 53 | 54 | \* the adjusted clock of process i 55 | AC(i) == hc[i] + adj[i] 56 | 57 | (*************************** INITIALIZATION ********************************) 58 | 59 | \* Initialization 60 | Init == 61 | /\ time \in Nat 62 | /\ hc \in [ Proc -> Nat ] 63 | /\ adj = [ p \in Proc |-> 0 ] 64 | /\ state = [ p \in Proc |-> "init" ] 65 | /\ msgs = {} 66 | /\ rcvd = [ p \in Proc |-> {} ] 67 | 68 | (******************************* ACTIONS ***********************************) 69 | 70 | \* send the value of the hardware clock 71 | SendMsg(p) == 72 | /\ state[p] = "init" 73 | /\ msgs' = msgs \union { [ src |-> p, ts |-> hc[p] ] } 74 | /\ state' = [ state EXCEPT ![p] = "sent" ] 75 | /\ UNCHANGED <> 76 | 77 | \* receive a message sent by another process 78 | ReceiveMsg(p) == 79 | /\ \E m \in msgs: 80 | /\ m \notin rcvd[p] 81 | \* the message cannot be received earlier than after t_min 82 | /\ hc[m.src] >= m.ts + t_min 83 | /\ rcvd' = [ rcvd EXCEPT ![p] = rcvd[p] \union { m } ] 84 | /\ UNCHANGED <> 85 | 86 | \* let the time flow 87 | AdvanceClocks(delta) == 88 | /\ delta > 0 89 | \* clocks can be advanced only if there is no pending message 90 | /\ \A m \in msgs: 91 | hc[m.src] + delta > t_max => 92 | \A p \in Proc: 93 | m \in rcvd[p] 94 | \* clocks are advanced uniformly 95 | /\ time' = time + delta 96 | /\ hc' = [ p \in Proc |-> hc[p] + delta ] 97 | /\ UNCHANGED <> 98 | 99 | \* all actions together 100 | Next == 101 | \/ \E delta \in Int: 102 | AdvanceClocks(delta) 103 | \/ \E p \in Proc: 104 | \/ SendMsg(p) 105 | \/ ReceiveMsg(p) 106 | 107 | =============================================================================== 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thinking about protocols with TLA+ and Apalache 2 | 3 | We are introducing TLA+ and the model checker 4 | [Apalache](https://apalache.informal.systems). Prior knowledge of TLA+ 5 | is not required. 6 | 7 | ## 1. Step-by-step instructions 8 | 9 | ### 1.1. Setup 10 | 11 | If you want to reproduce the steps, make sure that you have installed 12 | the following tools first: 13 | 14 | - Apalache version 0.16.5. Check [Apalache releases][]. 15 | - **optional**: [TLA Toolbox][] or [VScode plugin for TLA+][] 16 | 17 | 18 | 19 | ### 1.2. Incremental specification of clock synchronization 20 | 21 | Material for the [TLA+ tutorial](http://conf.tlapl.us/202110/), 22 | co-located with [DISC 2021](http://www.disc-conference.org/wp/disc2021/). 23 | 24 | Check the specifications in [clock-sync](examples/clock-sync) and the [extended 25 | version of the tutorial](https://www.youtube.com/watch?v=Ml7d_3vlH88). Follow 26 | the [clock sync: step-by-step instructions][]. 27 | 28 | 29 | 30 | ### 1.3. Incremental specification of token transfer 31 | 32 | Material for the workshop at [HackAtom RU 2021][]. 33 | 34 | Follow the [token transfer: step-by-step instructions][]. 35 | 36 | **WARNING**: The final specification 37 | [TokenTransfer10.tla](./examples/token-transfer/TokenTransfer10.tla) is **not** a 38 | complete specification of [ICS20][]. If you want to specify [ICS20][], 39 | you have to introduce the following missing features: 40 | 41 | - acknowledgments. 42 | 43 | 44 | 45 | ### 1.4 Specification of ERC20 tokens including the Approve-TransferFrom attack 46 | 47 | Follow the [ERC20: step-by-step instructions][] 48 | 49 | ## 2. Learning more about TLA+ 50 | 51 | - [Apalache model checker][] 52 | - [TLA+ Home Page][] 53 | - [TLA+ Video Course][] 54 | - [Specifying Systems][] 55 | - [TLA+ language manual for engineers][] 56 | - [LearnTla.com][] 57 | - [Community Modules][] 58 | - [TLC model checker][] 59 | - [TLA+ examples][] 60 | 61 | 62 | [TLA+ examples]: https://github.com/tlaplus/examples 63 | [TLA+ language manual for engineers]: https://apalache.informal.systems/docs/lang/index.html 64 | [Apalache model checker]: https://apalache.informal.systems 65 | [TLC model checker]: http://lamport.azurewebsites.net/tla/tools.html 66 | [Summary of TLA]: https://lamport.azurewebsites.net/tla/summary.pdf 67 | [TLA+ Home Page]: http://lamport.azurewebsites.net/tla/tla.html 68 | [Specifying Systems]: http://lamport.azurewebsites.net/tla/book.html?back-link=learning.html 69 | [Community Modules]: https://github.com/tlaplus/CommunityModules 70 | [LearnTla.com]: https://learntla.com 71 | [TLA+ Video Course]: http://lamport.azurewebsites.net/video/videos.html 72 | [TLA Toolbox]: https://lamport.azurewebsites.net/tla/toolbox.html 73 | [VScode plugin for TLA+]: https://marketplace.visualstudio.com/items?itemName=alygin.vscode-tlaplus 74 | [Building Apalache from source]: https://apalache.informal.systems/docs/apalache/installation/source.html 75 | [Apalache releases]: https://github.com/informalsystems/apalache/releases 76 | [token transfer: step-by-step instructions]: ./docs/token-transfer-steps.md 77 | [clock sync: step-by-step instructions]: ./docs/clock-sync-steps.md 78 | [ICS20]: https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer 79 | [HackAtom RU 2021]: https://blog.cosmos.network/hackatom-ru-2021-buckle-up-cosmonauts-we-have-no-problems-and-are-ready-for-takeoff-69c59812b2eb 80 | [ERC20: step-by-step instructions]: ./docs/erc20-steps.md 81 | -------------------------------------------------------------------------------- /docs/clock-sync-steps.md: -------------------------------------------------------------------------------- 1 | # Clock Synchronization with Apalache 2 | 3 | ## Version 1: Introducing clocks 4 | 5 | 1. Open [ClockSync1][] and [MC_ClockSync1][]. 6 | 1. Notice how the variables `time`, `hc`, and `adj` are declared. 7 | 1. Check the type annotations that start with `@type`. 8 | 1. Check the test in [MC_ClockSync1][]. 9 | 1. Run: 10 | 11 | ```sh 12 | $ apalache check --init=Test1_Init \ 13 | --next=Test1_Next --inv=Test1_Inv --length=1 MC_ClockSync1.tla 14 | ``` 15 | 16 | 4. The tool reports no error. 17 | 18 | ## Version 2: Sending messages 19 | 20 | 1. Open [ClockSync2][] and [MC_ClockSync2][]. 21 | 1. Check constants `t_min` and `t_max`. 22 | 1. Notice that variables `msgs` and `state` are added. 23 | 1. Check the test in [MC_ClockSync2][]. 24 | 1. Run: 25 | 26 | ```sh 27 | $ apalache check --init=Test2_Init \ 28 | --next=Test2_Next --inv=Test2_Inv --length=1 MC_ClockSync2.tla 29 | ``` 30 | 31 | 4. The tool reports no error. 32 | 33 | ## Version 3: Receiving messages 34 | 35 | 1. Open [ClockSync3][] and [MC_ClockSync3][]. 36 | 1. Notice that variable `rcvd` is added. 37 | 1. Check the test in [MC_ClockSync3][]. 38 | 1. Run: 39 | 40 | ```sh 41 | $ apalache check --init=Test3_Init \ 42 | --next=Test3_Next --inv=Test3_Inv --length=1 MC_ClockSync3.tla 43 | ``` 44 | 45 | 4. The tool reports no error. 46 | 47 | ## Version 4: Adjusting clocks 48 | 49 | 1. Open [ClockSync4][] and [MC_ClockSync4][]. 50 | 1. Notice that variable `diff` is added. 51 | 1. Check the test in [MC_ClockSync4][]. 52 | 1. Run: 53 | 54 | ```sh 55 | $ apalache check --init=Test4_Init \ 56 | --next=Test4_Next --inv=Test4_Inv --length=1 MC_ClockSync4.tla 57 | ``` 58 | 59 | 5. The tool reports no error. 60 | 1. Check the invariant `SkewInv` in [ClockSync4][]. 61 | 1. Run: 62 | 63 | ```sh 64 | $ apalache check --inv=SkewInv MC_ClockSync4.tla 65 | ``` 66 | 67 | 8. The tool reports an error. Open `counterexample1.tla`. 68 | 69 | ## Version 5: Ignoring self-messages 70 | 71 | 1. Open [ClockSync5][] and [MC_ClockSync5][]. 72 | 1. Notice the changes in `AdjustClock`. 73 | 1. Run: 74 | 75 | ```sh 76 | $ apalache check --inv=SkewInv MC_ClockSync5.tla 77 | ``` 78 | 79 | 5. The tool reports no error. 80 | 81 | ## Version 6: Parameterizing the set of processes 82 | 83 | 1. Open [ClockSync6][] and [MC_ClockSync6][]. 84 | 1. Notice that we made `Proc` a constant. 85 | 1. Check the operator `DiffSum` and how it is used. 86 | 1. Run: 87 | 88 | ```sh 89 | $ apalache check --inv=SkewInv MC_ClockSync6.tla 90 | ``` 91 | 92 | ## Version 6p: ConstInit to check arbitrary t_min and t_max 93 | 94 | 1. Open [MC_ClockSync6p][]. 95 | 1. Check the operator `ConstInit`. 96 | 1. Check how we have changed the bound in `SkewInv`. 97 | 1. Run: 98 | 99 | ```sh 100 | $ apalache check --cinit=ConstInit --inv=SkewInv MC_ClockSync6p.tla 101 | ``` 102 | 103 | 5. The tool reports no error. 104 | 105 | [ClockSync1]: ../examples/clock-sync/ClockSync1.tla 106 | [ClockSync2]: ../examples/clock-sync/ClockSync2.tla 107 | [ClockSync3]: ../examples/clock-sync/ClockSync3.tla 108 | [ClockSync4]: ../examples/clock-sync/ClockSync4.tla 109 | [ClockSync5]: ../examples/clock-sync/ClockSync5.tla 110 | [ClockSync6]: ../examples/clock-sync/ClockSync6.tla 111 | [ClockSync7]: ../examples/clock-sync/ClockSync7.tla 112 | [MC_ClockSync1]: ../examples/clock-sync/MC_ClockSync1.tla 113 | [MC_ClockSync2]: ../examples/clock-sync/MC_ClockSync2.tla 114 | [MC_ClockSync3]: ../examples/clock-sync/MC_ClockSync3.tla 115 | [MC_ClockSync4]: ../examples/clock-sync/MC_ClockSync4.tla 116 | [MC_ClockSync5]: ../examples/clock-sync/MC_ClockSync5.tla 117 | [MC_ClockSync6]: ../examples/clock-sync/MC_ClockSync6.tla 118 | [MC_ClockSync6p]: ../examples/clock-sync/MC_ClockSync6p.tla 119 | [MC_ClockSync7]: ../examples/clock-sync/MC_ClockSync7.tla 120 | 121 | -------------------------------------------------------------------------------- /examples/erc20/erc20_tests.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE erc20_tests -------------------------------- 2 | (* 3 | * A state machine for model checking the contract 4 | * 5 | * Igor Konnov, Informal Systems, 2023 6 | *) 7 | 8 | EXTENDS Integers 9 | 10 | \* we fix the set of addresses to a small set 11 | AllAddresses == { 12 | "0_OF_ADDR", "alice_OF_ADDR", "bob_OF_ADDR", "eve_OF_ADDR" 13 | } 14 | 15 | MAX_UINT == 2^256 - 1 16 | AMOUNTS == 0..MAX_UINT 17 | 18 | INSTANCE erc20 19 | 20 | VARIABLE 21 | \* @type: $state; 22 | state 23 | 24 | Init == 25 | \E sender \in AllAddresses \ { ZERO_ADDRESS }: 26 | \E initialSupply \in AMOUNTS: 27 | state = newErc20(sender, initialSupply) 28 | 29 | \* @type: $result => Bool; 30 | fromResult(result) == 31 | /\ VariantTag(result) = "Ok" 32 | /\ state' = VariantGetUnsafe("Ok", result).state 33 | 34 | \* a single-method Next predicate 35 | Next1 == 36 | \E sender, toAddr \in AllAddresses: 37 | \E amount \in AMOUNTS: 38 | fromResult(transfer(state, sender, toAddr, amount)) 39 | 40 | \* all three methods can be called in any order 41 | Next == 42 | \* execute the contract methods 43 | \E sender \in AllAddresses: 44 | \E amount \in AMOUNTS: 45 | \* transfer 46 | \/ \E toAddr \in AllAddresses: 47 | fromResult(transfer(state, sender, toAddr, amount)) 48 | \* approve 49 | \/ \E spender \in AllAddresses: 50 | fromResult(approve(state, sender, spender, amount)) 51 | \* transferFrom 52 | \/ \E fromAddr, toAddr \in AllAddresses: 53 | fromResult(transferFrom(state, sender, fromAddr, toAddr, amount)) 54 | 55 | (**************************************************************** 56 | * INVARIANTS 57 | ****************************************************************) 58 | totalSupplyInv == isTotalSupplyCorrect(state) 59 | 60 | zeroAddressInv == isZeroAddressEmpty(state) 61 | 62 | noOverflowInv == isNoOverflows(state) 63 | 64 | isValid == 65 | /\ totalSupplyInv 66 | /\ zeroAddressInv 67 | /\ noOverflowInv 68 | 69 | \* Use this predicate to initialize the state space, 70 | \* when proving inductiveness of isValid: 71 | \* 72 | \* apalache-mc check --init=initArbitrary --inv=isValid --length=1 erc20_tests.tla 73 | initArbitrary == 74 | \* Note: we are mapping to balances and allowance to Int, not to AMOUNTS. 75 | \* Otherwise, Apalache attempts to unroll the set AMOUNTS, which is huge. 76 | \E owner \in AllAddresses \ { ZERO_ADDRESS }: 77 | \E balances \in [ AllAddresses -> Int ]: 78 | \E allowances \in [ AllAddresses \X AllAddresses -> Int ]: 79 | /\ state = [ 80 | balanceOf |-> balances, 81 | totalSupply |-> sumOverBalances(balances), 82 | allowance |-> allowances, 83 | owner |-> owner 84 | ] 85 | /\ isValid 86 | 87 | \* Check this action invariant to ensure that 'transfer' is working 88 | \* as expected under the assumption that it has not returned an error, 89 | \* and it has not returned FALSE. 90 | \* 91 | \* We could also check that case when `transfer` returns an error or FALSE, 92 | \* but this would require to define ghost variables. 93 | \* 94 | \* apalache-mc check --init=initArbitrary --next=Next1 \ 95 | \* --inv=transferPrePost --length=2 erc20_tests.tla 96 | transferPrePost == 97 | \E sender, toAddr \in AllAddresses: 98 | LET ob == state.balanceOf IN 99 | LET nb == state'.balanceOf IN 100 | /\ \/ sender = toAddr /\ nb = ob 101 | \/ /\ sender /= toAddr 102 | /\ nb[sender] <= ob[sender] 103 | /\ nb[toAddr] >= ob[toAddr] 104 | /\ nb[toAddr] - ob[toAddr] = ob[sender] - nb[sender] 105 | /\ \A a \in AllAddresses: 106 | a \notin { sender, toAddr } => nb[a] = ob[a] 107 | /\ state'.allowance = state.allowance 108 | /\ state'.owner = state.owner 109 | /\ state'.totalSupply = state.totalSupply 110 | 111 | 112 | ================================================================================ 113 | -------------------------------------------------------------------------------- /examples/erc20/erc20_mempool.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE erc20_mempool ---------------------------- 2 | \* Specifying transaction execution in the mempool. 3 | \* We represent mempool as a set. Since we are not using 'nonce', 4 | \* duplicate transactions are simply ignored. 5 | \* It should be easy to extend this specification with nonces. 6 | \* 7 | \* Igor Konnov, Informal Systems, 2023 8 | EXTENDS Integers, erc20_mempool_typedefs 9 | 10 | AllAddresses == { 11 | "0_OF_ADDR", "alice_OF_ADDR", "bob_OF_ADDR", "eve_OF_ADDR" 12 | } 13 | 14 | MAX_UINT == 2^256 - 1 15 | \* Use a tiny value of MAX_UINT, if you'd like to run it in TLC: 16 | \* MAX_UINT == 2^2 - 1 17 | AMOUNTS == 0..MAX_UINT 18 | 19 | INSTANCE erc20 20 | 21 | VARIABLES 22 | \* @type: $state; 23 | contractState, 24 | \* @type: Set($tx); 25 | mempool, 26 | \* @type: $tx; 27 | lastTx, 28 | \* @type: Str; 29 | lastTxStatus 30 | 31 | Init == 32 | /\ \E sender \in AllAddresses \ { ZERO_ADDRESS }: 33 | \E initialSupply \in AMOUNTS: 34 | contractState = newErc20(sender, initialSupply) 35 | /\ mempool = {} 36 | /\ lastTx = NoneTx 37 | /\ lastTxStatus = "" 38 | 39 | \* Submit a transaction to the memory pool. 40 | \* This transaction is simply added to the pool, but not executed. 41 | submit(tx) == 42 | /\ mempool' = mempool \union { tx } 43 | /\ lastTx' = tx 44 | /\ lastTxStatus' = "pending" 45 | /\ UNCHANGED contractState 46 | 47 | \* an auxilliary action that assigns variables from a method execution result 48 | \* @type: ($tx, $result) => Bool; 49 | fromResult(tx, result) == 50 | /\ lastTx' = tx 51 | /\ IF VariantTag(result) /= "Error" 52 | THEN /\ lastTxStatus' = "success" 53 | /\ contractState' = VariantGetUnsafe("Ok", result).state 54 | ELSE /\ lastTxStatus' = VariantGetUnsafe("Error", result) 55 | /\ UNCHANGED contractState 56 | 57 | \* @type: $tx => Bool; 58 | commit(tx) == 59 | /\ mempool' = mempool \ { tx } 60 | /\ \/ /\ VariantTag(tx) = "TransferTx" 61 | /\ LET ttx == VariantGetUnsafe("TransferTx", tx) IN 62 | LET res == 63 | transfer(contractState, ttx.sender, ttx.toAddr, ttx.amount) IN 64 | fromResult(tx, res) 65 | \/ /\ VariantTag(tx) = "ApproveTx" 66 | /\ LET atx == VariantGetUnsafe("ApproveTx", tx) IN 67 | LET res == 68 | approve(contractState, atx.sender, atx.spender, atx.amount) IN 69 | fromResult(tx, res) 70 | \/ /\ VariantTag(tx) = "TransferFromTx" 71 | /\ LET ftx == VariantGetUnsafe("TransferFromTx", tx) IN 72 | LET res == 73 | transferFrom(contractState, ftx.sender, 74 | ftx.fromAddr, ftx.toAddr, ftx.amount) IN 75 | fromResult(tx, res) 76 | 77 | Next == 78 | \* execute the contract methods 79 | \E sender \in AllAddresses: 80 | \E amount \in AMOUNTS: 81 | \/ \E toAddr \in AllAddresses: 82 | submit(TransferTx(sender, toAddr, amount)) 83 | \/ \E spender \in AllAddresses: 84 | submit(ApproveTx(sender, spender, amount)) 85 | \/ \E fromAddr, toAddr \in AllAddresses: 86 | submit(TransferFromTx(sender, fromAddr, toAddr, amount)) 87 | \/ \E tx \in mempool: 88 | commit(tx) 89 | 90 | (** 91 | * No transferFrom should be possible, while there is a pending approval 92 | * for a smaller amount. This invariant is violated, as explained in: 93 | * 94 | * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 95 | *) 96 | noTransferFromWhileApproveInFlight == 97 | LET BadExample == 98 | /\ lastTxStatus = "success" 99 | /\ VariantTag(lastTx) = "TransferFromTx" 100 | /\ LET ltx == VariantGetUnsafe("TransferFromTx", lastTx) IN 101 | /\ ltx.amount > 0 102 | /\ \E tx \in mempool: 103 | /\ VariantTag(tx) = "ApproveTx" 104 | /\ LET atx == VariantGetUnsafe("ApproveTx", tx) IN 105 | /\ atx.sender = ltx.fromAddr 106 | /\ atx.spender = ltx.sender 107 | /\ atx.amount < ltx.amount 108 | /\ atx.amount > 0 109 | IN 110 | ~BadExample 111 | 112 | ============================================================================== -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer6.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer6 --------------------------------- 2 | (* 3 | * This is an example of a very simplistic token transfer 4 | * for presentation purposes. 5 | * Do not use it in production, as it may lead to loss of tokens. 6 | * 7 | * Version 6: introducing basic packet send from source 8 | * Version 5: introducing an inductive invariant for Apalache 9 | * Version 4: bounding the amounts to help the model checkers 10 | * Version 3: fixing the invariants and introducing more 11 | * Version 2: let the banks do the local banking 12 | * Version 1: introducing data structures 13 | * 14 | * Igor Konnov, 2021 15 | *) 16 | EXTENDS Integers, Apalache, typedefs 17 | 18 | CONSTANT 19 | \* A set of blockchains, i.e., their names 20 | \* @type: Set(Str); 21 | CHAINS, 22 | \* A set of channels, that is, pairs of chains 23 | \* @type: Set(<>); 24 | CHANNELS, 25 | \* A set of accounts, i.e., their names 26 | \* @type: Set(Str); 27 | ACCOUNTS, 28 | \* Initial supply for every chain 29 | \* @type: Str -> Int; 30 | GENESIS_SUPPLY 31 | 32 | 33 | VARIABLES 34 | \* For every chain and account, store the amount of tokens in the account 35 | \* @type: <> -> Int; 36 | banks, 37 | \* Packets that are sent by one chain to another (e.g., via an IBC channel) 38 | \* @type: Set([src: Str, dest: Str, data: [sender: Str, receiver: Str, amount: Int]]); 39 | sentPackets 40 | 41 | (*************************** OPERATORS ***************************************) 42 | \* For simplicity, we fix the name of the escrow account. 43 | \* In ICS20, one introduces one escrow account per channel. 44 | Escrow == "escrow" 45 | 46 | \* @type: (ADDR -> Int, Set(ADDR)) => Int; 47 | SumAddresses(amounts, Addrs) == 48 | LET Add(sum, addr) == sum + amounts[addr] IN 49 | ApaFoldSet(Add, 0, Addrs) 50 | 51 | \* @type: (ADDR -> Int, CHAIN) => Int; 52 | ChainSupply(amounts, chain) == 53 | SumAddresses(amounts, {chain} \X ACCOUNTS) 54 | 55 | (**************************** SYSTEM *****************************************) 56 | 57 | \* Initialize the world, e.g., from the last upgrade 58 | Init == 59 | /\ sentPackets = {} 60 | /\ \E b \in [ CHAINS \X ACCOUNTS -> Nat ]: 61 | /\ \A chain \in CHAINS: 62 | /\ b[chain, "reserve"] > 0 63 | /\ ChainSupply(b, chain) = GENESIS_SUPPLY[chain] 64 | /\ banks = b 65 | 66 | \* Transfer the tokens from on account to another (on the same chain) 67 | LocalTransfer(chain, from, to, amount) == 68 | /\ banks[chain, from] >= amount 69 | /\ from /= to 70 | /\ banks' = [banks EXCEPT 71 | ![chain, from] = banks[chain, from] - amount, 72 | ![chain, to] = banks[chain, to] + amount 73 | ] 74 | 75 | \* A computation on the local chain 76 | LocalStep == 77 | /\ \E chain \in CHAINS, from, to \in ACCOUNTS, amount \in Nat: 78 | /\ from /= Escrow 79 | /\ to /= Escrow 80 | /\ LocalTransfer(chain, from, to, amount) 81 | /\ UNCHANGED sentPackets 82 | 83 | \* Send a packet to transfer tokens 84 | SendPacketFromSource == 85 | \E chan \in CHANNELS, sender, receiver \in ACCOUNTS, amount \in Nat: 86 | /\ sender /= Escrow /\ receiver /= Escrow 87 | \* the source direction: escrow source tokens 88 | /\ LocalTransfer(chan[1], sender, Escrow, amount) 89 | /\ LET data == [sender |-> sender, 90 | receiver |-> receiver, 91 | amount |-> amount] 92 | packet == [src |-> chan[1], dest |-> chan[2], data |-> data] 93 | IN 94 | sentPackets' = sentPackets \union { packet } 95 | \* TODO: add the case ~isSource, that is, the other direction 96 | 97 | \* Update the world 98 | Next == 99 | \/ LocalStep 100 | \/ SendPacketFromSource 101 | 102 | (************************** PROPERTIES ***************************************) 103 | 104 | \* every bank always has reserves 105 | ReservesInv == 106 | \A chain \in CHAINS: 107 | banks[chain, "reserve"] > 0 108 | 109 | \* no bank account goes negative 110 | NoNegativeAccounts == 111 | \A address \in DOMAIN banks: 112 | banks[address] >= 0 113 | 114 | \* the supply remains constant 115 | ChainSupplyUnchanged == 116 | \A chain \in CHAINS: 117 | LET supply == ChainSupply(banks, chain) IN 118 | supply = GENESIS_SUPPLY[chain] 119 | 120 | \* for each in-fly packet, there is enough money in the escrow account 121 | InFlyPacketIsSecured == 122 | \A p \in sentPackets: 123 | banks[p.src, Escrow] >= p.amount 124 | 125 | =============================================================================== 126 | -------------------------------------------------------------------------------- /examples/clock-sync/ClockSync4.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE ClockSync4 ------------------------------ 2 | (* 3 | * Incremental TLA+ specification of the clock synchronization algorithm from: 4 | * 5 | * Hagit Attiya, Jennifer Welch. Distributed Computing. Wiley Interscience, 2004, 6 | * p. 147, Algorithm 20. 7 | * 8 | * Assumptions: timestamps are natural numbers, not reals. 9 | * 10 | * Version 4: Adjusting clock values (test 4 + invariant violation) 11 | * Version 3: Receiving messages (test 3) 12 | * Version 2: Sending messages (test 2) 13 | * Version 1: Setting up the clocks (test 1) 14 | *) 15 | EXTENDS Integers, FiniteSets 16 | 17 | CONSTANTS 18 | \* minimum message delay 19 | \* @type: Int; 20 | t_min, 21 | \* maximum message delay 22 | \* @type: Int; 23 | t_max 24 | 25 | ASSUME(t_min >= 0 /\ t_max > t_min) 26 | 27 | VARIABLES 28 | \* the reference clock, inaccessible to the processes 29 | \* @type: Int; 30 | time, 31 | \* hardware clock of a process 32 | \* @type: Str -> Int; 33 | hc, 34 | \* clock adjustment of a process 35 | \* @type: Str -> Int; 36 | adj, 37 | \* clock diff for process j, as seen by a process j 38 | \* @type: <> -> Int; 39 | diff, 40 | \* messages sent by the processes 41 | \* @type: Set([src: Str, ts: Int]); 42 | msgs, 43 | \* messages received by the processes 44 | \* @type: Str -> Set([src: Str, ts: Int]); 45 | rcvd, 46 | \* the control state of a process 47 | \* @type: Str -> Str; 48 | state 49 | 50 | (***************************** DEFINITIONS *********************************) 51 | 52 | \* we fix the set to contain two processes 53 | Proc == { "p1", "p2" } 54 | 55 | \* control states 56 | State == { "init", "sent", "sync" } 57 | 58 | \* the adjusted clock of process i 59 | AC(i) == hc[i] + adj[i] 60 | 61 | (*************************** INITIALIZATION ********************************) 62 | 63 | \* Initialization 64 | Init == 65 | /\ time \in Nat 66 | /\ hc \in [ Proc -> Nat ] 67 | /\ adj = [ p \in Proc |-> 0 ] 68 | /\ diff = [ <> \in Proc \X Proc |-> 0 ] 69 | /\ state = [ p \in Proc |-> "init" ] 70 | /\ msgs = {} 71 | /\ rcvd = [ p \in Proc |-> {} ] 72 | 73 | (******************************* ACTIONS ***********************************) 74 | 75 | \* send the value of the hardware clock 76 | SendMsg(p) == 77 | /\ state[p] = "init" 78 | /\ msgs' = msgs \union { [ src |-> p, ts |-> hc[p] ] } 79 | /\ state' = [ state EXCEPT ![p] = "sent" ] 80 | /\ UNCHANGED <> 81 | 82 | \* If the process has received a message from all processes, 83 | \* then adjust the clock. Otherwise, accumulate the difference. 84 | \* @type: (Str, <> -> Int, 85 | \* Set([src: Str, ts: Int])) => Bool; 86 | AdjustClock(p, newDiff, newRcvd) == 87 | LET fromAll == { m.src: m \in newRcvd } = Proc IN 88 | IF fromAll 89 | THEN 90 | \* Assuming that Proc = { "p1", "p2" }. 91 | \* See ClockSync6 for the general case. 92 | /\ adj' = [ adj EXCEPT ![p] = (newDiff[p, "p1"] + newDiff[p, "p2"]) \div 2 ] 93 | /\ state' = [ state EXCEPT ![p] = "sync" ] 94 | ELSE 95 | UNCHANGED <> 96 | 97 | \* Receive a message sent by another process. 98 | \* Adjust the clock if the message has been received from all processes. 99 | ReceiveMsg(p) == 100 | /\ state[p] = "sent" 101 | /\ \E m \in msgs: 102 | /\ m \notin rcvd[p] 103 | \* the message cannot be received earlier than after t_min 104 | /\ hc[m.src] >= m.ts + t_min 105 | \* accumulate the difference and adjust the clock if possible 106 | /\ LET delta == m.ts - hc[p] + (t_min + t_max) \div 2 IN 107 | LET newDiff == [ diff EXCEPT ![p, m.src] = delta ] IN 108 | LET newRcvd == rcvd[p] \union { m } IN 109 | /\ AdjustClock(p, newDiff, newRcvd) 110 | /\ rcvd' = [ rcvd EXCEPT ![p] = newRcvd ] 111 | /\ diff' = newDiff 112 | /\ UNCHANGED <> 113 | 114 | \* let the time flow 115 | AdvanceClocks(delta) == 116 | /\ delta > 0 117 | \* clocks can be advanced only if there is no pending message 118 | /\ \A m \in msgs: 119 | hc[m.src] + delta > t_max => 120 | \A p \in Proc: 121 | m \in rcvd[m.src] 122 | \* clocks are advanced uniformly 123 | /\ time' = time + delta 124 | /\ hc' = [ p \in Proc |-> hc[p] + delta ] 125 | /\ UNCHANGED <> 126 | 127 | \* all actions together 128 | Next == 129 | \/ \E delta \in Int: 130 | AdvanceClocks(delta) 131 | \/ \E p \in Proc: 132 | \/ SendMsg(p) 133 | \/ ReceiveMsg(p) 134 | 135 | (******************************* PROPERTIES **********************************) 136 | 137 | \* Theorem 6.15 from AW04: 138 | \* Algorithm achieves u * (1 - 1/n)-synchronization for n processors. 139 | SkewInv == 140 | LET allSync == 141 | \A p \in Proc: state[p] = "sync" 142 | IN 143 | LET boundedSkew == 144 | LET base == Cardinality(Proc) IN 145 | LET bound == (t_max - t_min) * (base - 1) 146 | IN 147 | \A p, q \in Proc: 148 | LET df == AC(p) - AC(q) 149 | IN 150 | -bound <= df * base /\ df * base <= bound 151 | IN 152 | allSync => boundedSkew 153 | 154 | =============================================================================== 155 | -------------------------------------------------------------------------------- /examples/clock-sync/ClockSync5.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE ClockSync5 ------------------------------ 2 | (* 3 | * Incremental TLA+ specification of the clock synchronization algorithm from: 4 | * 5 | * Hagit Attiya, Jennifer Welch. Distributed Computing. Wiley Interscience, 2004, 6 | * p. 147, Algorithm 20. 7 | * 8 | * Assumptions: timestamps are natural numbers, not reals. 9 | * 10 | * Version 5: Ignoring the message by the process itself 11 | * Version 4: Adjusting clock values (test 4 + invariant violation) 12 | * Version 3: Receiving messages (test 3) 13 | * Version 2: Sending messages (test 2) 14 | * Version 1: Setting up the clocks (test 1) 15 | *) 16 | EXTENDS Integers, FiniteSets 17 | 18 | CONSTANTS 19 | \* minimum message delay 20 | \* @type: Int; 21 | t_min, 22 | \* maximum message delay 23 | \* @type: Int; 24 | t_max 25 | 26 | ASSUME(t_min >= 0 /\ t_max > t_min) 27 | 28 | VARIABLES 29 | \* the reference clock, inaccessible to the processes 30 | \* @type: Int; 31 | time, 32 | \* hardware clock of a process 33 | \* @type: Str -> Int; 34 | hc, 35 | \* clock adjustment of a process 36 | \* @type: Str -> Int; 37 | adj, 38 | \* clock diff for process j, as seen by a process j 39 | \* @type: <> -> Int; 40 | diff, 41 | \* messages sent by the processes 42 | \* @type: Set([src: Str, ts: Int]); 43 | msgs, 44 | \* messages received by the processes 45 | \* @type: Str -> Set([src: Str, ts: Int]); 46 | rcvd, 47 | \* the control state of a process 48 | \* @type: Str -> Str; 49 | state 50 | 51 | (***************************** DEFINITIONS *********************************) 52 | 53 | \* we fix the set to contain two processes 54 | Proc == { "p1", "p2" } 55 | 56 | \* the number of processes 57 | NProc == Cardinality(Proc) 58 | 59 | \* control states 60 | State == { "init", "sent", "sync" } 61 | 62 | \* the adjusted clock of process i 63 | AC(i) == hc[i] + adj[i] 64 | 65 | (*************************** INITIALIZATION ********************************) 66 | 67 | \* Initialization 68 | Init == 69 | /\ time \in Nat 70 | /\ hc \in [ Proc -> Nat ] 71 | /\ adj = [ p \in Proc |-> 0 ] 72 | /\ diff = [ <> \in Proc \X Proc |-> 0 ] 73 | /\ state = [ p \in Proc |-> "init" ] 74 | /\ msgs = {} 75 | /\ rcvd = [ p \in Proc |-> {} ] 76 | 77 | (******************************* ACTIONS ***********************************) 78 | 79 | \* send the value of the hardware clock 80 | SendMsg(p) == 81 | /\ state[p] = "init" 82 | /\ msgs' = msgs \union { [ src |-> p, ts |-> hc[p] ] } 83 | /\ state' = [ state EXCEPT ![p] = "sent" ] 84 | /\ UNCHANGED <> 85 | 86 | \* If the process has received a message from all processes (but p), 87 | \* then adjust the clock. Otherwise, accumulate the difference. 88 | \* @type: (Str, <> -> Int, 89 | \* Set([src: Str, ts: Int])) => Bool; 90 | AdjustClock(p, newDiff, newRcvd) == 91 | LET fromAll == { m.src: m \in newRcvd } = Proc \ { p } IN 92 | IF fromAll 93 | THEN 94 | \* Assuming that Proc = { "p1", "p2" }. 95 | \* See ClockSync5 for the general case. 96 | /\ adj' = [ adj EXCEPT ![p] = (newDiff[p, "p1"] + newDiff[p, "p2"]) \div 2 ] 97 | /\ state' = [ state EXCEPT ![p] = "sync" ] 98 | ELSE 99 | UNCHANGED <> 100 | 101 | \* Receive a message sent by another process. 102 | \* Adjust the clock if the message has been received from all processes. 103 | ReceiveMsg(p) == 104 | /\ state[p] = "sent" 105 | /\ \E m \in msgs: 106 | /\ m \notin rcvd[p] 107 | /\ m.src /= p \* ignore the message by p itself 108 | \* the message cannot be received earlier than after t_min 109 | /\ hc[m.src] >= m.ts + t_min 110 | \* accumulate the difference and adjust the clock if possible 111 | /\ LET delta == m.ts - hc[p] + (t_min + t_max) \div 2 IN 112 | LET newDiff == [ diff EXCEPT ![p, m.src] = delta ] IN 113 | LET newRcvd == rcvd[p] \union { m } IN 114 | /\ AdjustClock(p, newDiff, newRcvd) 115 | /\ rcvd' = [ rcvd EXCEPT ![p] = newRcvd ] 116 | /\ diff' = newDiff 117 | /\ UNCHANGED <> 118 | 119 | \* let the time flow 120 | AdvanceClocks(delta) == 121 | /\ delta > 0 122 | \* clocks can be advanced only if there is no pending message 123 | /\ \A m \in msgs: 124 | hc[m.src] + delta > t_max => 125 | \A p \in Proc: 126 | m \in rcvd[m.src] 127 | \* clocks are advanced uniformly 128 | /\ time' = time + delta 129 | /\ hc' = [ p \in Proc |-> hc[p] + delta ] 130 | /\ UNCHANGED <> 131 | 132 | \* all actions together 133 | Next == 134 | \/ \E delta \in Int: 135 | AdvanceClocks(delta) 136 | \/ \E p \in Proc: 137 | \/ SendMsg(p) 138 | \/ ReceiveMsg(p) 139 | 140 | (******************************* PROPERTIES **********************************) 141 | 142 | \* Theorem 6.15 from AW04: 143 | \* Algorithm achieves u * (1 - 1/n)-synchronization for n processors. 144 | SkewInv == 145 | LET allSync == 146 | \A p \in Proc: state[p] = "sync" 147 | IN 148 | LET boundedSkew == 149 | LET bound == (t_max - t_min) * (NProc - 1) 150 | IN 151 | \A p, q \in Proc: 152 | LET df == AC(p) - AC(q) 153 | IN 154 | -bound <= df * NProc /\ df * NProc <= bound 155 | IN 156 | allSync => boundedSkew 157 | 158 | =============================================================================== 159 | -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer3.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer3 --------------------------------- 2 | EXTENDS Integers, Apalache, typedefs 3 | 4 | (*(* Below, we attach the comments to definitions. Otherwise, our tool 5 | would not be able to extract them to a markdown document. *)*) 6 | 7 | CONSTANT 8 | (* 9 | This is an example of a very simplistic token transfer for presentation 10 | purposes. Do not use it in production, as it may lead to loss of tokens. 11 | 12 | - Version 3: fixing the invariants and introducing more 13 | - Version 2: let the banks do the local banking 14 | - Version 1: introducing data structures 15 | 16 | **author:** Igor Konnov, 2021 17 | *) 18 | 19 | (* 20 | ## Protocol constants 21 | 22 | *) 23 | 24 | (* 25 | A real blockchain creates user accounts dynamically. However, to reason 26 | about the token transfer protocol, it is sufficient to fix a predefined 27 | set of accounts. We can imagine that all accounts 28 | exist from the beginning of times and carry zero balance. In a real 29 | blockchains, accounts are associated with private keys. These are 30 | usually either unreadable sequences of letters and digits or sequences of 31 | 24 words. In our specification, they do not have to be that verbose. We 32 | can give normal readable names to the accounts such as Anna and Boris. 33 | 34 | The constant `ACCOUNTS` stores the set of account names. 35 | 36 | @type: Set(ADDR); 37 | *) 38 | ACCOUNTS, 39 | 40 | (* 41 | The protocol works for a system of blockchains. Their unique names 42 | are relevant for our specification. We consider fixed a set of names. 43 | 44 | @type: Set(CHAIN); 45 | *) 46 | CHAINS, 47 | (* 48 | Every blockchain has some initial supply of coins, e.g., 49 | set in the genesis block. The constant `GENESIS_SUPPLY` stores this supply. 50 | 51 | @type: CHAIN -> Int; 52 | *) 53 | GENESIS_SUPPLY 54 | 55 | VARIABLES 56 | (* 57 | 58 | ## Variables of the state machine 59 | 60 | *) 61 | 62 | (* 63 | For every chain and account, `banks` stores the amount of tokens in the account. 64 | 65 | @type: ADDR -> Int; 66 | *) 67 | banks 68 | 69 | (* 70 | ----------------------------------------------------------------------------- 71 | ## Auxiliary definitions 72 | 73 | Before diving into the transitions of the protocol, we introduce auxiliary 74 | definitions. 75 | 76 | *) 77 | 78 | (* 79 | `SumAddresses` computes the amount of coins held by a collection of addresses. 80 | 81 | @type: (ADDR -> Int, Set(ADDR)) => Int; 82 | *) 83 | SumAddresses(amounts, Addrs) == 84 | LET Add(sum, addr) == sum + amounts[addr] IN 85 | ApaFoldSet(Add, 0, Addrs) 86 | 87 | (* 88 | `ChainSupply` computes the supply of every chain in the current state. 89 | 90 | @type: (ADDR -> Int, CHAIN) => Int; 91 | *) 92 | ChainSupply(amounts, chain) == 93 | SumAddresses(amounts, {chain} \X ACCOUNTS) 94 | 95 | (* 96 | ----------------------------------------------------------------------------- 97 | ## Protocol initialization 98 | 99 | *) 100 | 101 | (* 102 | Initialize the global state of our system. We can imagine that this is the 103 | state right after the genesis initialization or after an upgrade. 104 | 105 | Since the protocol parameters can be set to arbitrary values, the initial 106 | state can also be a snapshot of the blockchains that was made at some point. 107 | *) 108 | Init == 109 | \E b \in [ CHAINS \X ACCOUNTS -> Nat ]: 110 | /\ \A chain \in CHAINS: 111 | /\ b[chain, "reserve"] > 0 112 | /\ ChainSupply(b, chain) = GENESIS_SUPPLY[chain] 113 | /\ banks = b 114 | 115 | (* 116 | ----------------------------------------------------------------------------- 117 | ## Protocol transitions 118 | 119 | *) 120 | 121 | (* 122 | `LocalTransfer` specifies how `amount` coins could be transferred from an 123 | account `from` to an account `to` on the same chain `chain`. Note that the 124 | actual implementation of `LocalTransfer` is much more complex than that. But 125 | those details are not important for our specification. 126 | *) 127 | LocalTransfer(chain, from, to, amount) == 128 | /\ banks[chain, from] >= amount 129 | /\ from /= to 130 | /\ banks' = [banks EXCEPT 131 | ![chain, from] = banks[chain, from] - amount, 132 | ![chain, to] = banks[chain, to] + amount 133 | ] 134 | 135 | (* 136 | The predicate `Next` captures all possible transitions that could be 137 | made by the system. In this version, we can only do local transfers. 138 | *) 139 | Next == 140 | \E chain \in CHAINS, from, to \in ACCOUNTS, amount \in Nat: 141 | LocalTransfer(chain, from, to, amount) 142 | 143 | (* 144 | ----------------------------------------------------------------------------- 145 | ## Protocol properties 146 | 147 | *) 148 | 149 | (* 150 | The invariant `ReservesInv` states that every chain should have coins 151 | on the account `reserve`. 152 | *) 153 | ReservesInv == 154 | \A chain \in CHAINS: 155 | banks[chain, "reserve"] > 0 156 | 157 | (* 158 | The invariant `NoNegativeAccounts` states that every account never goes 159 | below zero. 160 | *) 161 | NoNegativeAccounts == 162 | \A address \in DOMAIN banks: 163 | banks[address] >= 0 164 | 165 | (* 166 | The invariant `ChainSupplyUnchanged` is probably the most crucial one. 167 | It states that no coins are lost or created out of thin air. 168 | *) 169 | ChainSupplyUnchanged == 170 | \A chain \in CHAINS: 171 | ChainSupply(banks, chain) = GENESIS_SUPPLY[chain] 172 | 173 | =============================================================================== 174 | -------------------------------------------------------------------------------- /examples/clock-sync/ClockSync6.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE ClockSync6 ------------------------------ 2 | (* 3 | * Incremental TLA+ specification of the clock synchronization algorithm from: 4 | * 5 | * Hagit Attiya, Jennifer Welch. Distributed Computing. Wiley Interscience, 2004, 6 | * p. 147, Algorithm 20. 7 | * 8 | * Assumptions: timestamps are natural numbers, not reals. 9 | * 10 | * Version 6: Make Proc a parameter and use ApaFoldSet 11 | * Version 5: Ignoring the message by the process itself 12 | * Version 4: Adjusting clock values (test 4 + invariant violation) 13 | * Version 3: Receiving messages (test 3) 14 | * Version 2: Sending messages (test 2) 15 | * Version 1: Setting up the clocks (test 1) 16 | *) 17 | EXTENDS Integers, FiniteSets, Apalache 18 | 19 | CONSTANTS 20 | \* the set of processes 21 | \* @type: Set(Str); 22 | Proc, 23 | \* minimum message delay 24 | \* @type: Int; 25 | t_min, 26 | \* maximum message delay 27 | \* @type: Int; 28 | t_max 29 | 30 | ASSUME(t_min >= 0 /\ t_max > t_min) 31 | 32 | VARIABLES 33 | \* the reference clock, inaccessible to the processes 34 | \* @type: Int; 35 | time, 36 | \* hardware clock of a process 37 | \* @type: Str -> Int; 38 | hc, 39 | \* clock adjustment of a process 40 | \* @type: Str -> Int; 41 | adj, 42 | \* clock diff for process j, as seen by a process j 43 | \* @type: <> -> Int; 44 | diff, 45 | \* messages sent by the processes 46 | \* @type: Set([src: Str, ts: Int]); 47 | msgs, 48 | \* messages received by the processes 49 | \* @type: Str -> Set([src: Str, ts: Int]); 50 | rcvd, 51 | \* the control state of a process 52 | \* @type: Str -> Str; 53 | state 54 | 55 | (***************************** DEFINITIONS *********************************) 56 | 57 | \* the number of processes 58 | NProc == Cardinality(Proc) 59 | 60 | \* control states 61 | State == { "init", "sent", "sync" } 62 | 63 | \* the adjusted clock of process i 64 | AC(i) == hc[i] + adj[i] 65 | 66 | \* sum up the clock differences as observed by a process p 67 | \* @type: (<> -> Int, Str) => Int; 68 | DiffSum(df, p) == 69 | LET Add(total, q) == 70 | total + df[p, q] 71 | IN 72 | ApaFoldSet(Add, 0, Proc) 73 | 74 | (*************************** INITIALIZATION ********************************) 75 | 76 | \* Initialization 77 | Init == 78 | /\ time \in Nat 79 | /\ hc \in [ Proc -> Nat ] 80 | /\ adj = [ p \in Proc |-> 0 ] 81 | /\ diff = [ <> \in Proc \X Proc |-> 0 ] 82 | /\ state = [ p \in Proc |-> "init" ] 83 | /\ msgs = {} 84 | /\ rcvd = [ p \in Proc |-> {} ] 85 | 86 | (******************************* ACTIONS ***********************************) 87 | 88 | \* send the value of the hardware clock 89 | SendMsg(p) == 90 | /\ state[p] = "init" 91 | /\ msgs' = msgs \union { [ src |-> p, ts |-> hc[p] ] } 92 | /\ state' = [ state EXCEPT ![p] = "sent" ] 93 | /\ UNCHANGED <> 94 | 95 | \* If the process has received a message from all processes (but p), 96 | \* then adjust the clock. Otherwise, accumulate the difference. 97 | \* @type: (Str, <> -> Int, 98 | \* Set([src: Str, ts: Int])) => Bool; 99 | AdjustClock(p, newDiff, newRcvd) == 100 | LET fromAll == { m.src: m \in newRcvd } = Proc \ { p } IN 101 | IF fromAll 102 | THEN 103 | \* Assuming that Proc = { "p1", "p2" }. 104 | \* See ClockSync5 for the general case. 105 | /\ adj' = [ adj EXCEPT ![p] = DiffSum(newDiff, p) \div NProc ] 106 | /\ state' = [ state EXCEPT ![p] = "sync" ] 107 | ELSE 108 | UNCHANGED <> 109 | 110 | \* Receive a message sent by another process. 111 | \* Adjust the clock if the message has been received from all processes. 112 | ReceiveMsg(p) == 113 | /\ state[p] = "sent" 114 | /\ \E m \in msgs: 115 | /\ m \notin rcvd[p] 116 | /\ m.src /= p \* ignore the message by p itself 117 | \* the message cannot be received earlier than after t_min 118 | /\ hc[m.src] >= m.ts + t_min 119 | \* accumulate the difference and adjust the clock if possible 120 | /\ LET delta == m.ts - hc[p] + (t_min + t_max) \div 2 IN 121 | LET newDiff == [ diff EXCEPT ![p, m.src] = delta ] IN 122 | LET newRcvd == rcvd[p] \union { m } IN 123 | /\ AdjustClock(p, newDiff, newRcvd) 124 | /\ rcvd' = [ rcvd EXCEPT ![p] = newRcvd ] 125 | /\ diff' = newDiff 126 | /\ UNCHANGED <> 127 | 128 | \* let the time flow 129 | AdvanceClocks(delta) == 130 | /\ delta > 0 131 | \* clocks can be advanced only if there is no pending message 132 | /\ \A m \in msgs: 133 | hc[m.src] + delta > t_max => 134 | \A p \in Proc: 135 | m \in rcvd[m.src] 136 | \* clocks are advanced uniformly 137 | /\ time' = time + delta 138 | /\ hc' = [ p \in Proc |-> hc[p] + delta ] 139 | /\ UNCHANGED <> 140 | 141 | \* all actions together 142 | Next == 143 | \/ \E delta \in Int: 144 | AdvanceClocks(delta) 145 | \/ \E p \in Proc: 146 | \/ SendMsg(p) 147 | \/ ReceiveMsg(p) 148 | 149 | (******************************* PROPERTIES **********************************) 150 | 151 | \* Theorem 6.15 from AW04: 152 | \* Algorithm achieves u * (1 - 1/n)-synchronization for n processors. 153 | SkewInv == 154 | LET allSync == 155 | \A p \in Proc: state[p] = "sync" 156 | IN 157 | LET boundedSkew == 158 | LET bound == 159 | \* extend the bound by NProc to account for rounding errors 160 | (t_max - t_min) * (NProc - 1) + NProc * NProc 161 | IN 162 | \A p, q \in Proc: 163 | LET df == AC(p) - AC(q) 164 | IN 165 | -bound <= df * NProc /\ df * NProc <= bound 166 | IN 167 | allSync => boundedSkew 168 | 169 | =============================================================================== 170 | -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer7.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer7 --------------------------------- 2 | (* 3 | * This is an example of a very simplistic token transfer 4 | * for presentation purposes. 5 | * Do not use it in production, as it may lead to loss of tokens. 6 | * 7 | * Version 7: introducing basic packet receive from source 8 | * Version 6: introducing basic packet send from source 9 | * Version 5: introducing an inductive invariant for Apalache 10 | * Version 4: bounding the amounts to help the model checkers 11 | * Version 3: fixing the invariants and introducing more 12 | * Version 2: let the banks do the local banking 13 | * Version 1: introducing data structures 14 | * 15 | * Igor Konnov, 2021 16 | *) 17 | EXTENDS Integers, Apalache, typedefs 18 | 19 | CONSTANT 20 | \* A set of blockchains, i.e., their names 21 | \* @type: Set(Str); 22 | CHAINS, 23 | \* A set of channels, that is, pairs of chains 24 | \* @type: Set(<>); 25 | CHANNELS, 26 | \* A set of accounts, i.e., their names 27 | \* @type: Set(Str); 28 | ACCOUNTS, 29 | \* Initial supply for every chain 30 | \* @type: Str -> Int; 31 | GENESIS_SUPPLY 32 | 33 | 34 | VARIABLES 35 | \* For every chain and account, store the amount of tokens in the account 36 | \* @type: <> -> Int; 37 | banks, 38 | \* Packets that are sent by one chain to another (e.g., via an IBC channel) 39 | \* @type: Set([seqno: Int, src: Str, dest: Str, data: [sender: Str, receiver: Str, amount: Int]]); 40 | sentPackets, 41 | \* The sequence numbers of delivered packets 42 | \* @type: Set(Int); 43 | deliveredNums, 44 | \* An imaginary global counter that we use to assign unique sequence numbers 45 | \* @type: Int; 46 | seqno 47 | 48 | (*************************** OPERATORS ***************************************) 49 | \* For simplicity, we fix the name of the escrow account. 50 | \* In ICS20, one introduces one escrow account per channel. 51 | Escrow == "escrow" 52 | 53 | \* @type: (ADDR -> Int, Set(ADDR)) => Int; 54 | SumAddresses(amounts, Addrs) == 55 | LET Add(sum, addr) == sum + amounts[addr] IN 56 | ApaFoldSet(Add, 0, Addrs) 57 | 58 | \* @type: (ADDR -> Int, CHAIN) => Int; 59 | ChainSupply(amounts, chain) == 60 | SumAddresses(amounts, {chain} \X ACCOUNTS) 61 | 62 | (**************************** SYSTEM *****************************************) 63 | 64 | \* Initialize the world, e.g., from the last upgrade 65 | Init == 66 | /\ seqno = 0 67 | /\ sentPackets = {} 68 | /\ deliveredNums = {} 69 | /\ \E b \in [ CHAINS \X ACCOUNTS -> Nat ]: 70 | /\ \A chain \in CHAINS: 71 | b[chain, "reserve"] > 0 72 | /\ banks = b 73 | /\ \A c \in CHAINS: 74 | ChainSupply(banks, c) = GENESIS_SUPPLY[c] 75 | 76 | \* Transfer the tokens from on account to another (on the same chain) 77 | LocalTransfer(chain, from, to, amount) == 78 | /\ banks[chain, from] >= amount 79 | /\ from /= to 80 | /\ banks' = [banks EXCEPT 81 | ![chain, from] = banks[chain, from] - amount, 82 | ![chain, to] = banks[chain, to] + amount 83 | ] 84 | 85 | \* A computation on the local chain 86 | LocalStep == 87 | /\ \E chain \in CHAINS, from, to \in ACCOUNTS, amount \in Nat: 88 | /\ from /= Escrow 89 | /\ to /= Escrow 90 | /\ LocalTransfer(chain, from, to, amount) 91 | /\ UNCHANGED <> 92 | 93 | \* Send a packet to transfer tokens (from the source) 94 | SendPacketFromSource == 95 | \E chan \in CHANNELS, sender, receiver \in ACCOUNTS, amount \in Nat: 96 | /\ sender /= Escrow /\ receiver /= Escrow 97 | \* the source direction: escrow source tokens 98 | /\ LocalTransfer(chan[1], sender, Escrow, amount) 99 | /\ LET data == [seqno |-> seqno, 100 | sender |-> sender, 101 | receiver |-> receiver, 102 | amount |-> amount] 103 | packet == [src |-> chan[1], dest |-> chan[2], data |-> data] 104 | IN 105 | /\ sentPackets' = sentPackets \union { packet } 106 | /\ seqno' = seqno + 1 107 | /\ UNCHANGED deliveredNums 108 | \* TODO: add the case ~isSource, that is, the other direction 109 | 110 | \* produce `amount` coins in the receiver's account (out of thin air!) 111 | MintCoins(chain, receiver, amount) == 112 | banks' = [banks EXCEPT ![chain, receiver] = 113 | banks[chain, receiver] + amount] 114 | 115 | \* Receive a packet on a non-source chain (note that ICS20 does more than that) 116 | ReceivePacketFromSource == 117 | \E packet \in sentPackets: 118 | /\ packet.seqno \notin deliveredNums 119 | /\ MintCoins(packet.dest, packet.data.receiver, packet.data.amount) 120 | /\ deliveredNums' = deliveredNums \union { packet.seqno } 121 | /\ UNCHANGED <> 122 | 123 | \* Update the world 124 | Next == 125 | \/ LocalStep 126 | \/ SendPacketFromSource 127 | \/ ReceivePacketFromSource 128 | 129 | (************************** PROPERTIES ***************************************) 130 | 131 | \* every bank always has reserves 132 | ReservesInv == 133 | \A chain \in CHAINS: 134 | banks[chain, "reserve"] > 0 135 | 136 | \* no bank account goes negative 137 | NoNegativeAccounts == 138 | \A address \in DOMAIN banks: 139 | banks[address] >= 0 140 | 141 | \* the supply remains constant 142 | ChainSupplyUnchanged == 143 | \A chain \in CHAINS: 144 | LET supply == ChainSupply(banks, chain) IN 145 | supply = GENESIS_SUPPLY[chain] 146 | 147 | \* for each in-fly packet, there is enough money in the escrow account 148 | InFlyPacketIsSecured == 149 | \A p \in sentPackets: 150 | banks[p.src, Escrow] >= p.data.amount 151 | 152 | 153 | =============================================================================== 154 | -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer3.md: -------------------------------------------------------------------------------- 1 | # Specification TokenTransfer3 2 | 3 | _The text below was automatically generated by Apalache from 4 | [TokenTransfer3.tla](TokenTransfer3.tla). This is why it looks 5 | a bit strange._ 6 | 7 | ```Extends Integers, Sequences, FiniteSets, TLC, Apalache``` 8 | 9 | 10 | This is an example of a very simplistic token transfer for presentation 11 | purposes. Do not use it in production, as it may lead to loss of tokens. 12 | 13 | - Version 3: fixing the invariants and introducing more 14 | - Version 2: let the banks do the local banking 15 | - Version 1: introducing data structures 16 | 17 | **author:** Igor Konnov, 2021 18 | 19 | 20 | ## Protocol constants 21 | 22 | 23 | 24 | A real blockchain creates user accounts dynamically. However, to reason 25 | about the token transfer protocol, it is sufficient to fix a predefined 26 | set of accounts. We can imagine that all accounts 27 | exist from the beginning of times and carry zero balance. In a real 28 | blockchains, accounts are associated with private keys. These are 29 | usually either unreadable sequences of letters and digits or sequences of 30 | 24 words. In our specification, they do not have to be that verbose. We 31 | can give normal readable names to the accounts such as Anna and Boris. 32 | 33 | The constant `ACCOUNTS` stores the set of account names. 34 | 35 | ```@type: Set(ADDR);``` 36 | 37 | ``` 38 | Given constant ACCOUNTS 39 | ``` 40 | 41 | 42 | Every blockchain has some initial supply of coins, e.g., 43 | set in the genesis block. The constant `GENESIS_SUPPLY` stores this supply. 44 | 45 | ```@type: CHAIN -> Int;``` 46 | 47 | ``` 48 | Given constant GENESIS_SUPPLY 49 | ``` 50 | 51 | 52 | The protocol works for a system of blockchains. Their unique names 53 | are relevant for our specification. We consider fixed a set of names. 54 | 55 | ```@type: Set(CHAIN);``` 56 | 57 | ``` 58 | Given constant CHAINS 59 | ``` 60 | 61 | 62 | ## Variables of the state machine 63 | 64 | 65 | 66 | For every chain and account, `banks` stores the amount of tokens in the account. 67 | 68 | ```@type: ADDR -> Int;``` 69 | 70 | ``` 71 | Introduce state variable banks 72 | ``` 73 | 74 | 75 | ## Type aliases 76 | 77 | We introduce the following aliases for the types that are used in this 78 | specification. 79 | 80 | ```@typeAlias: CHAIN = Str;``` 81 | 82 | ```@typeAlias: ACCOUNT = Str;``` 83 | 84 | ```@typeAlias: ADDR = <>;``` 85 | 86 | ```@typeAlias: BANK = (ADDR -> Int);``` 87 | 88 | ```@typeAlias: DENOM = Str;``` 89 | 90 | ```@typeAlias: DADDR = <>;``` 91 | 92 | ``` 93 | Define typedefs_included as TRUE 94 | ``` 95 | 96 | 97 | ----------------------------------------------------------------------------- 98 | ## Auxiliary definitions 99 | 100 | Before diving into the transitions of the protocol, we introduce auxiliary 101 | definitions. 102 | 103 | 104 | 105 | `SumAddresses` computes the amount of coins held by a collection of addresses. 106 | 107 | ```@type: (ADDR -> Int, Set(ADDR)) => Int;``` 108 | 109 | ``` 110 | Define SumAddresses(amounts, Addrs) as 111 | Let Add(sum, addr) be sum + amounts[addr] in FoldSet(Add, 0, Addrs) 112 | ``` 113 | 114 | 115 | `ChainSupply` computes the supply of every chain in the current state. 116 | 117 | ```@type: (ADDR -> Int, CHAIN) => Int;``` 118 | 119 | ``` 120 | Define ChainSupply(amounts, chain) as 121 | SumAddresses(amounts, {Cross product of {Set of chain}, ACCOUNTS}) 122 | ``` 123 | 124 | 125 | ----------------------------------------------------------------------------- 126 | ## Protocol transitions 127 | 128 | 129 | 130 | `LocalTransfer` specifies how `amount` coins could be transferred from an 131 | account `from` to an account `to` on the same chain `chain`. Note that the 132 | actual implementation of `LocalTransfer` is much more complex than that. But 133 | those details are not important for our specification. 134 | 135 | ``` 136 | Define LocalTransfer(chain, from, to, amount) as 137 | banks[chain, from] >= amount 138 | and from /= to 139 | and banks' 140 | = [Copy function 141 | banks except 142 | at (chain, from) set banks[chain, from] - amount, 143 | at (chain, to) set banks[chain, to] + amount] 144 | ``` 145 | 146 | 147 | ----------------------------------------------------------------------------- 148 | ## Protocol properties 149 | 150 | 151 | 152 | The invariant `ReservesInv` states that every chain should have coins 153 | on the account `reserve`. 154 | 155 | ``` 156 | Define ReservesInv as For each chain in CHAINS holds banks[chain, "reserve"] > 0 157 | ``` 158 | 159 | 160 | The invariant `NoNegativeAccounts` states that every account never goes 161 | below zero. 162 | 163 | ``` 164 | Define NoNegativeAccounts as 165 | For each address in DOMAIN banks holds banks[address] >= 0 166 | ``` 167 | 168 | 169 | ----------------------------------------------------------------------------- 170 | ## Protocol initialization 171 | 172 | 173 | 174 | Initialize the global state of our system. We can imagine that this is the 175 | state right after the genesis initialization or after an upgrade. 176 | 177 | Since the protocol parameters can be set to arbitrary values, the initial 178 | state can also be a snapshot of the blockchains that was made at some point. 179 | 180 | ``` 181 | Define Init as 182 | Exists b in Set of functions 183 | from {Cross product of CHAINS, ACCOUNTS} 184 | to Nat such that 185 | (For each chain in CHAINS holds 186 | b[chain, "reserve"] > 0 187 | and ChainSupply(b, chain) = GENESIS_SUPPLY[chain]) 188 | and banks = b 189 | holds 190 | ``` 191 | 192 | 193 | The predicate `Next` captures all possible transitions that could be 194 | made by the system. In this version, we can only do local transfers. 195 | 196 | ``` 197 | Define Next as 198 | Exists chain in CHAINS such that 199 | Exists from in ACCOUNTS such that 200 | Exists to in ACCOUNTS such that 201 | Exists amount in Nat such that 202 | LocalTransfer(chain, from, to, amount) 203 | holds 204 | holds 205 | holds 206 | holds 207 | ``` 208 | 209 | 210 | The invariant `ChainSupplyUnchanged` is probably the most crucial one. 211 | It states that no coins are lost or created out of thin air. 212 | 213 | ``` 214 | Define ChainSupplyUnchanged as 215 | For each chain in CHAINS holds 216 | ChainSupply(banks, chain) = GENESIS_SUPPLY[chain] 217 | ``` 218 | 219 | -------------------------------------------------------------------------------- 220 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/counterexample5.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE counterexample ---------------------------- 2 | 3 | EXTENDS MC_ERC20 4 | 5 | (* Constant initialization state *) 6 | ConstInit == TRUE 7 | 8 | (* Initial state *) 9 | State0 == 10 | allowance 11 | = SetAsFun({ <<<<"Bob_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 12 | <<<<"Bob_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 13 | <<<<"Bob_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 14 | <<<<"Eve_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 15 | <<<<"Alice_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 16 | <<<<"Alice_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 17 | <<<<"Eve_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 18 | <<<<"Alice_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 19 | <<<<"Eve_OF_ADDR", "Alice_OF_ADDR">>, 0>> }) 20 | /\ balanceOf 21 | = SetAsFun({ <<"Alice_OF_ADDR", 2>>, 22 | <<"Bob_OF_ADDR", 0>>, 23 | <<"Eve_OF_ADDR", 3>> }) 24 | /\ lastTx = [fail |-> FALSE, id |-> 0, tag |-> "None"] 25 | /\ nextTxId = 0 26 | /\ pendingTransactions = {} 27 | 28 | (* Transition 2 to State1 *) 29 | State1 == 30 | allowance 31 | = SetAsFun({ <<<<"Bob_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 32 | <<<<"Bob_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 33 | <<<<"Bob_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 34 | <<<<"Eve_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 35 | <<<<"Alice_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 36 | <<<<"Alice_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 37 | <<<<"Eve_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 38 | <<<<"Alice_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 39 | <<<<"Eve_OF_ADDR", "Alice_OF_ADDR">>, 0>> }) 40 | /\ balanceOf 41 | = SetAsFun({ <<"Alice_OF_ADDR", 2>>, 42 | <<"Bob_OF_ADDR", 0>>, 43 | <<"Eve_OF_ADDR", 3>> }) 44 | /\ lastTx = [fail |-> FALSE, id |-> 0, tag |-> "None"] 45 | /\ nextTxId = 1 46 | /\ pendingTransactions 47 | = {[fail |-> FALSE, 48 | id |-> 0, 49 | sender |-> "Eve_OF_ADDR", 50 | spender |-> "Bob_OF_ADDR", 51 | tag |-> "approve", 52 | value |-> 3]} 53 | 54 | (* Transition 4 to State2 *) 55 | State2 == 56 | allowance 57 | = SetAsFun({ <<<<"Bob_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 58 | <<<<"Bob_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 59 | <<<<"Bob_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 60 | <<<<"Eve_OF_ADDR", "Bob_OF_ADDR">>, 3>>, 61 | <<<<"Alice_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 62 | <<<<"Alice_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 63 | <<<<"Eve_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 64 | <<<<"Alice_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 65 | <<<<"Eve_OF_ADDR", "Alice_OF_ADDR">>, 0>> }) 66 | /\ balanceOf 67 | = SetAsFun({ <<"Alice_OF_ADDR", 2>>, 68 | <<"Bob_OF_ADDR", 0>>, 69 | <<"Eve_OF_ADDR", 3>> }) 70 | /\ lastTx 71 | = [fail |-> FALSE, 72 | id |-> 0, 73 | sender |-> "Eve_OF_ADDR", 74 | spender |-> "Bob_OF_ADDR", 75 | tag |-> "approve", 76 | value |-> 3] 77 | /\ nextTxId = 1 78 | /\ pendingTransactions = {} 79 | 80 | (* Transition 2 to State3 *) 81 | State3 == 82 | allowance 83 | = SetAsFun({ <<<<"Bob_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 84 | <<<<"Bob_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 85 | <<<<"Bob_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 86 | <<<<"Eve_OF_ADDR", "Bob_OF_ADDR">>, 3>>, 87 | <<<<"Alice_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 88 | <<<<"Alice_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 89 | <<<<"Eve_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 90 | <<<<"Alice_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 91 | <<<<"Eve_OF_ADDR", "Alice_OF_ADDR">>, 0>> }) 92 | /\ balanceOf 93 | = SetAsFun({ <<"Alice_OF_ADDR", 2>>, 94 | <<"Bob_OF_ADDR", 0>>, 95 | <<"Eve_OF_ADDR", 3>> }) 96 | /\ lastTx = [fail |-> FALSE, id |-> 0, tag |-> "None"] 97 | /\ nextTxId = 2 98 | /\ pendingTransactions 99 | = {[fail |-> FALSE, 100 | id |-> 1, 101 | sender |-> "Eve_OF_ADDR", 102 | spender |-> "Bob_OF_ADDR", 103 | tag |-> "approve", 104 | value |-> 2]} 105 | 106 | (* Transition 1 to State4 *) 107 | State4 == 108 | allowance 109 | = SetAsFun({ <<<<"Bob_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 110 | <<<<"Bob_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 111 | <<<<"Bob_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 112 | <<<<"Eve_OF_ADDR", "Bob_OF_ADDR">>, 3>>, 113 | <<<<"Alice_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 114 | <<<<"Alice_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 115 | <<<<"Eve_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 116 | <<<<"Alice_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 117 | <<<<"Eve_OF_ADDR", "Alice_OF_ADDR">>, 0>> }) 118 | /\ balanceOf 119 | = SetAsFun({ <<"Alice_OF_ADDR", 2>>, 120 | <<"Bob_OF_ADDR", 0>>, 121 | <<"Eve_OF_ADDR", 3>> }) 122 | /\ lastTx = [fail |-> FALSE, id |-> 0, tag |-> "None"] 123 | /\ nextTxId = 3 124 | /\ pendingTransactions 125 | = { [fail |-> FALSE, 126 | fromAddr |-> "Eve_OF_ADDR", 127 | id |-> 2, 128 | sender |-> "Bob_OF_ADDR", 129 | tag |-> "transferFrom", 130 | toAddr |-> "Alice_OF_ADDR", 131 | value |-> 3], 132 | [fail |-> FALSE, 133 | id |-> 1, 134 | sender |-> "Eve_OF_ADDR", 135 | spender |-> "Bob_OF_ADDR", 136 | tag |-> "approve", 137 | value |-> 2] } 138 | 139 | (* Transition 6 to State5 *) 140 | State5 == 141 | allowance 142 | = SetAsFun({ <<<<"Bob_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 143 | <<<<"Bob_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 144 | <<<<"Bob_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 145 | <<<<"Eve_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 146 | <<<<"Alice_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 147 | <<<<"Alice_OF_ADDR", "Bob_OF_ADDR">>, 0>>, 148 | <<<<"Eve_OF_ADDR", "Eve_OF_ADDR">>, 0>>, 149 | <<<<"Alice_OF_ADDR", "Alice_OF_ADDR">>, 0>>, 150 | <<<<"Eve_OF_ADDR", "Alice_OF_ADDR">>, 0>> }) 151 | /\ balanceOf 152 | = SetAsFun({ <<"Alice_OF_ADDR", 5>>, 153 | <<"Bob_OF_ADDR", 0>>, 154 | <<"Eve_OF_ADDR", 0>> }) 155 | /\ lastTx 156 | = [fail |-> FALSE, 157 | fromAddr |-> "Eve_OF_ADDR", 158 | id |-> 2, 159 | sender |-> "Bob_OF_ADDR", 160 | tag |-> "transferFrom", 161 | toAddr |-> "Alice_OF_ADDR", 162 | value |-> 3] 163 | /\ nextTxId = 3 164 | /\ pendingTransactions 165 | = {[fail |-> FALSE, 166 | id |-> 1, 167 | sender |-> "Eve_OF_ADDR", 168 | spender |-> "Bob_OF_ADDR", 169 | tag |-> "approve", 170 | value |-> 2]} 171 | 172 | (* The following formula holds true in the last state and violates the invariant *) 173 | InvariantViolation == 174 | LET BadExample_si_2_si__skolem == 175 | lastTx["tag"] = "transferFrom" 176 | /\ lastTx["value"] > 0 177 | /\ ~(lastTx["fail"]) 178 | /\ Skolem((\E approval$2 \in pendingTransactions: 179 | approval$2["tag"] = "approve" 180 | /\ approval$2["sender"] = lastTx["fromAddr"] 181 | /\ approval$2["spender"] = lastTx["sender"] 182 | /\ ~(lastTx["sender"] = lastTx["toAddr"]) 183 | /\ approval$2["value"] < lastTx["value"] 184 | /\ approval$2["value"] > 0)) 185 | IN 186 | BadExample_si_2_si__skolem 187 | 188 | ================================================================================ 189 | (* Created by Apalache on Wed May 25 21:16:03 CEST 2022 *) 190 | (* https://github.com/informalsystems/apalache *) 191 | -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer8.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer8 --------------------------------- 2 | (* 3 | * This is an example of a very simplistic token transfer 4 | * for presentation purposes. 5 | * Do not use it in production, as it may lead to loss of tokens. 6 | * 7 | * Version 8: introducing basic denominations 8 | * Version 7: introducing basic packet receive from source 9 | * Version 6: introducing basic packet send from source 10 | * Version 5: introducing an inductive invariant for Apalache 11 | * Version 4: bounding the amounts to help the model checkers 12 | * Version 3: fixing the invariants and introducing more 13 | * Version 2: let the banks do the local banking 14 | * Version 1: introducing data structures 15 | * 16 | * Igor Konnov, 2021 17 | *) 18 | EXTENDS Integers, Apalache, typedefs 19 | 20 | CONSTANT 21 | \* A set of blockchains, i.e., their names 22 | \* @type: Set(Str); 23 | CHAINS, 24 | \* A set of channels, that is, pairs of chains 25 | \* @type: Set(<>); 26 | CHANNELS, 27 | \* A set of accounts, i.e., their names 28 | \* @type: Set(Str); 29 | ACCOUNTS, 30 | \* A set of all possible amounts 31 | \* @type: Set(Int); 32 | GENESIS_SUPPLY, 33 | \* A set of denominations 34 | \* @type: Set(Str); 35 | DENOMS, 36 | \* A function that produces a new denomination for a chain a denomination 37 | \* @type: <> -> Str; 38 | MK_DENOM 39 | 40 | VARIABLES 41 | \* For every chain and account, store the amount of tokens in the account 42 | \* for each denomination 43 | \* @type: <> -> Int; 44 | banks, 45 | \* Packets that are sent by one chain to another (e.g., via an IBC channel) 46 | \* @type: Set([seqno: Int, src: CHAIN, dest: CHAIN, data: [sender: ACCOUNT, receiver: ACCOUNT, denom: DENOM, amount: Int]]); 47 | sentPackets, 48 | \* The sequence numbers of delivered packets 49 | \* @type: Set(Int); 50 | deliveredNums, 51 | \* An imaginary global counter that we use to assign unique sequence numbers 52 | \* @type: Int; 53 | seqno 54 | 55 | (*************************** OPERATORS ***************************************) 56 | \* For simplicity, we fix the name of the escrow account. 57 | \* In ICS20, one introduces one escrow account per channel. 58 | Escrow == "escrow" 59 | 60 | \* For simplicity, we call the coin native if it has the same name as the chain 61 | Native(chain) == chain 62 | 63 | \* @type: (DADDR -> Int, Set(DADDR)) => Int; 64 | SumAddresses(amounts, Addrs) == 65 | LET Add(sum, addr) == sum + amounts[addr] IN 66 | ApaFoldSet(Add, 0, Addrs) 67 | 68 | \* @type: (DADDR -> Int, CHAIN) => Int; 69 | ChainSupply(amounts, chain) == 70 | SumAddresses(amounts, {chain} \X ACCOUNTS \X { Native(chain) }) 71 | 72 | (**************************** SYSTEM *****************************************) 73 | 74 | \* Initialize the world, e.g., from the last upgrade 75 | Init == 76 | /\ seqno = 0 77 | /\ sentPackets = {} 78 | /\ deliveredNums = {} 79 | /\ \E b \in [ CHAINS \X ACCOUNTS \X DENOMS -> Nat ]: 80 | /\ \A chain \in CHAINS: 81 | /\ b[chain, "reserve", chain] > 0 82 | /\ ChainSupply(b, chain) = GENESIS_SUPPLY[chain] 83 | /\ \A a \in ACCOUNTS, d \in DENOMS: 84 | \* no tokens in foreign denominations 85 | d /= Native(chain) => b[chain, a, d] = 0 86 | /\ banks = b 87 | 88 | \* Transfer the tokens from on account to another (on the same chain) 89 | LocalTransfer(chain, from, to, denom, amount) == 90 | /\ banks[chain, from, denom] >= amount 91 | /\ from /= to 92 | /\ banks' = [banks EXCEPT 93 | ![chain, from, denom] = banks[chain, from, denom] - amount, 94 | ![chain, to, denom] = banks[chain, to, denom] + amount 95 | ] 96 | 97 | \* A computation on the local chain 98 | LocalStep == 99 | /\ \E chain \in CHAINS, from, to \in ACCOUNTS, 100 | denom \in DENOMS, amount \in Nat: 101 | /\ from /= Escrow 102 | /\ to /= Escrow 103 | /\ LocalTransfer(chain, from, to, denom, amount) 104 | /\ UNCHANGED <> 105 | 106 | \* Send a packet to transfer tokens (from the source) 107 | SendPacketFromSource == 108 | \E chan \in CHANNELS, sender, receiver \in ACCOUNTS, 109 | denom \in DENOMS, amount \in Nat: 110 | /\ sender /= Escrow /\ receiver /= Escrow 111 | /\ amount > 0 112 | \* the source direction: escrow source tokens 113 | /\ LocalTransfer(chan[1], sender, Escrow, denom, amount) 114 | /\ LET data == [seqno |-> seqno, 115 | sender |-> sender, 116 | receiver |-> receiver, 117 | denom |-> denom, 118 | amount |-> amount] 119 | packet == [src |-> chan[1], dest |-> chan[2], data |-> data] 120 | IN 121 | /\ sentPackets' = sentPackets \union { packet } 122 | /\ seqno' = seqno + 1 123 | /\ UNCHANGED deliveredNums 124 | \* TODO: add the case ~isSource, that is, the other direction 125 | 126 | \* Produce `amount` coins in the receiver's account (out of thin air!) 127 | 128 | MintCoins(chain, receiver, denom, amount) == 129 | \* do not mint native coins 130 | /\ denom /= Native(chain) 131 | /\ banks' = [banks EXCEPT ![chain, receiver, denom] = 132 | banks[chain, receiver, denom] + amount] 133 | 134 | \* Receive a packet on a non-source chain (note that ICS20 does more than that) 135 | ReceivePacketFromSource == 136 | \E packet \in sentPackets: 137 | /\ packet.seqno \notin deliveredNums 138 | \* In the implementation, we produce a new denomination. 139 | \* Mint coins that are different from native. 140 | /\ LET foreignDenom == MK_DENOM[packet.dest, packet.data.denom] IN 141 | MintCoins(packet.dest, 142 | packet.data.receiver, foreignDenom, packet.data.amount) 143 | /\ deliveredNums' = deliveredNums \union { packet.seqno } 144 | /\ UNCHANGED <> 145 | 146 | \* Update the world 147 | Next == 148 | \/ LocalStep 149 | \/ SendPacketFromSource 150 | \/ ReceivePacketFromSource 151 | 152 | (************************** PROPERTIES ***************************************) 153 | 154 | \* every bank always has reserves 155 | ReservesInv == 156 | \A chain \in CHAINS: 157 | banks[chain, "reserve", Native(chain)] > 0 158 | 159 | \* no bank account goes negative 160 | NoNegativeAccounts == 161 | \A address \in DOMAIN banks: 162 | banks[address] >= 0 163 | 164 | \* the supply remains constant 165 | ChainSupplyUnchanged == 166 | \A chain \in CHAINS: 167 | LET supply == ChainSupply(banks, chain) IN 168 | supply = GENESIS_SUPPLY[chain] 169 | 170 | \* for each in-fly packet, there is enough money in the escrow account 171 | InFlyPacketIsSecured == 172 | \A p \in sentPackets: 173 | banks[p.src, Escrow, p.data.denom] >= p.data.amount 174 | 175 | \* This property should produce a counterexample that demonstrates 176 | \* that a foreign denomination can reach a blockchain 177 | NoForeignCoins == 178 | \A chain \in CHAINS, acc \in ACCOUNTS, d \in DENOMS: 179 | d /= Native(chain) => banks[chain, acc, d] = 0 180 | 181 | =============================================================================== 182 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/test_erc20.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Modeling ERC20 tokens of Ethereum and the Approve-TransferFrom Attack: 4 | # 5 | # EIP-20: https://eips.ethereum.org/EIPS/eip-20 6 | # 7 | # Attack scenario: 8 | # https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit# 9 | # 10 | # Our testing framework is designed towards checking the protocol features. 11 | # We do not model 256-bit integers here, as we are not interested in overflows. 12 | # 13 | # Igor Konnov, Informal Systems, 2021-2022 14 | 15 | 16 | import unittest 17 | 18 | from hypothesis import given, strategies as gen 19 | from hypothesis.stateful import Bundle, RuleBasedStateMachine 20 | from hypothesis.stateful import rule, consumes, invariant, initialize, precondition 21 | from hypothesis import assume, settings, event, Verbosity 22 | 23 | # The list of addresses to use. We could use real addresses here, 24 | # but simple readable names are much nicer. 25 | ADDR = [ "Alice", "Bob", "Eve" ] 26 | 27 | # We restrict the amounts to a small range, to avoid too much randomness 28 | AMOUNTS = range(0, 20) 29 | 30 | 31 | class TransferTx: 32 | """An instance of transfer""" 33 | 34 | def __init__(self, sender, toAddr, value): 35 | self.tag = "transfer" 36 | self.sender = sender 37 | self.toAddr = toAddr 38 | self.value = value 39 | 40 | class TransferFromTx: 41 | """An instance of transferFrom""" 42 | 43 | def __init__(self, sender, fromAddr, toAddr, value): 44 | self.tag = "transferFrom" 45 | self.sender = sender 46 | self.fromAddr = fromAddr 47 | self.toAddr = toAddr 48 | self.value = value 49 | 50 | class ApproveTx: 51 | """An instance of approve""" 52 | 53 | def __init__(self, sender, spender, value): 54 | self.tag = "approve" 55 | self.sender = sender 56 | self.spender = spender 57 | self.value = value 58 | 59 | 60 | class Erc20Simulator(RuleBasedStateMachine): 61 | """ 62 | Model the behavior of the ERC20 API in terms of stateful testing. 63 | """ 64 | 65 | def __init__(self): 66 | super().__init__() 67 | 68 | # This bundle contains the generated transactions 69 | # that are to be processed 70 | pendingTransfers = Bundle("pendingTransfers") 71 | pendingApproves = Bundle("pendingApproves") 72 | pendingTransferFroms = Bundle("pendingTransferFroms") 73 | 74 | @initialize(amounts=gen.lists(gen.sampled_from(AMOUNTS), 75 | min_size=len(ADDR), 76 | max_size=len(ADDR))) 77 | def init(self, amounts): 78 | # balance of every account 79 | self.balanceOf = { 80 | addr: amount for (addr, amount) in zip(ADDR, amounts) 81 | } 82 | # approvals from senders to spenders 83 | self.allowance = { 84 | (sender, spender): 0 for sender in ADDR for spender in ADDR 85 | } 86 | # history variables that we need to express the invariants 87 | self.pendingTxsShadow = set() 88 | self.lastTx = None 89 | 90 | @rule(target=pendingTransfers, _sender=gen.sampled_from(ADDR), 91 | _toAddr=gen.sampled_from(ADDR), _value=gen.sampled_from(AMOUNTS)) 92 | def submit_transfer(self, _sender, _toAddr, _value): 93 | # submit a transfer transaction on the client side 94 | tx = TransferTx(_sender, _toAddr, _value) 95 | self.pendingTxsShadow.add(tx) 96 | self.lastTx = None 97 | return tx 98 | 99 | @rule(target=pendingTransferFroms, _sender=gen.sampled_from(ADDR), 100 | _fromAddr=gen.sampled_from(ADDR), 101 | _toAddr=gen.sampled_from(ADDR), _value=gen.sampled_from(AMOUNTS)) 102 | def submit_transfer_from(self, _sender, _fromAddr, _toAddr, _value): 103 | # submit a transferFrom transaction on the client side 104 | tx = TransferFromTx(_sender, _fromAddr, _toAddr, _value) 105 | self.pendingTxsShadow.add(tx) 106 | self.lastTx = None 107 | return tx 108 | 109 | @rule(target=pendingApproves, _sender=gen.sampled_from(ADDR), 110 | _spender=gen.sampled_from(ADDR), _value=gen.sampled_from(AMOUNTS)) 111 | def submit_approve(self, _sender, _spender, _value): 112 | # submit an approve transaction on the client side 113 | tx = ApproveTx(_sender, _spender, _value) 114 | self.pendingTxsShadow.add(tx) 115 | self.lastTx = None 116 | return tx 117 | 118 | @rule(tx=consumes(pendingTransfers)) 119 | def commit_transfer(self, tx): 120 | # process a transfer transaction somewhere in the blockchain 121 | assume(tx.value <= self.balanceOf[tx.sender] 122 | and tx.value > 0 123 | and tx.sender != tx.toAddr) 124 | self.pendingTxsShadow.remove(tx) 125 | self.balanceOf[tx.sender] -= tx.value 126 | self.balanceOf[tx.toAddr] += tx.value 127 | self.lastTx = tx 128 | event("transfer") 129 | 130 | @rule(tx=consumes(pendingTransferFroms)) 131 | def commit_transfer_from(self, tx): 132 | # process a transferFrom transaction somewhere in the blockchain 133 | assume(tx.value > 0 134 | and tx.value <= self.balanceOf[tx.fromAddr] 135 | and tx.value <= self.allowance[(tx.fromAddr, tx.sender)] 136 | and tx.fromAddr != tx.toAddr) 137 | self.pendingTxsShadow.remove(tx) 138 | self.balanceOf[tx.fromAddr] -= tx.value 139 | self.balanceOf[tx.toAddr] += tx.value 140 | self.allowance[(tx.fromAddr, tx.sender)] -= tx.value 141 | self.lastTx = tx 142 | event("transferFrom") 143 | 144 | @rule(tx=consumes(pendingApproves)) 145 | def commit_approve(self, tx): 146 | # process an approve transaction somewhere in the blockchain 147 | assume(tx.value > 0 and tx.sender != tx.spender) 148 | self.pendingTxsShadow.remove(tx) 149 | self.allowance[(tx.sender, tx.spender)] = tx.value 150 | self.lastTx = tx 151 | event("approve") 152 | 153 | @invariant() 154 | def no_negative_balances(self): 155 | # a simple invariant to make sure that the balances do not go negative 156 | for addr in ADDR: 157 | assert(self.balanceOf[addr] >= 0) 158 | 159 | @invariant() 160 | def all_transfers_approved(self): 161 | # If this invariant is violated, then it is possible to transfer tokens 162 | # (based on an earlier approval), while there is an approval for a 163 | # smaller amount in the pending transactions 164 | last = self.lastTx 165 | if last: 166 | if last.tag == "transferFrom" and last.value > 0: 167 | for p in self.pendingTxsShadow: 168 | if p.tag == "approve" \ 169 | and p.sender == last.fromAddr \ 170 | and p.spender == last.sender \ 171 | and last.sender != last.toAddr \ 172 | and p.value < last.value and p.value > 0: 173 | assert(False) 174 | 175 | # Uncomment the following invariant to check, 176 | # whether it is possible to have allowances in progress. 177 | # @invariant() 178 | # def no_approval(self): 179 | # for sender in ADDR: 180 | # for spender in ADDR: 181 | # assert(self.allowance[(sender, spender)] <= 0) 182 | 183 | 184 | # run stateful testing 185 | TestTrees = Erc20Simulator.TestCase 186 | Erc20Simulator.TestCase.settings = settings( 187 | max_examples=100000, 188 | stateful_step_count=5, 189 | deadline=None 190 | ) 191 | 192 | if __name__ == "__main__": 193 | unittest.main() 194 | -------------------------------------------------------------------------------- /examples/erc20/erc20.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE erc20 ------------------------------------- 2 | (* 3 | * A specification of simple ERC20 that should be easy to use in other specs. 4 | * 5 | * The module erc20 closely follows the implementation by OpenZeppelin. 6 | * Since, TLA+ is different, we adapt the modeling primitives to TLA+. 7 | * 8 | * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol 9 | * 10 | * Our approach to the TLA+ specification may look non-standard at first. 11 | * We have first specified this module in Quint and then translated it to TLA+ (by hand). 12 | * 13 | * https://github.com/informalsystems/quint/blob/main/examples/solidity/ERC20/erc20.qnt 14 | * 15 | * Igor Konnov, Informal Systems, 2023 16 | *) 17 | 18 | EXTENDS Integers, Apalache, erc20_typedefs 19 | 20 | CONSTANTS 21 | \* The set of all addresses that we are using 22 | \* 23 | \* @type: Set(ADDR); 24 | AllAddresses, 25 | \* The maximal value of the unsigned integer in EVM 26 | \* 27 | \* @type: Int; 28 | MAX_UINT 29 | 30 | \* The special address, which corresponds to the special address 0x0 in EVM 31 | ZERO_ADDRESS == "0_OF_ADDR" 32 | 33 | ASSUME(ZERO_ADDRESS \in AllAddresses) 34 | 35 | \* The predicate that we use to check for overflows 36 | isUint(i) == 0 <= i /\ i <= MAX_UINT 37 | 38 | \* Construct a new ERC20 contract 39 | \* 40 | \* @type: (ADDR, Int) => $state; 41 | newErc20(sender, initialSupply) == [ 42 | balanceOf |-> 43 | [ a \in AllAddresses |-> IF a /= sender THEN 0 ELSE initialSupply ], 44 | totalSupply |-> initialSupply, 45 | allowance |-> [ a, b \in AllAddresses |-> 0], 46 | owner |-> sender 47 | ] 48 | 49 | \* Returns the amount of tokens in existence 50 | \* @type: $state => Int; 51 | totalSupply(state) == state.totalSupply 52 | 53 | \* Returns the amount of tokens owned by account 54 | \* @type: ($state, ADDR) => Int; 55 | balanceOf(state, account) == state.balanceOf[account] 56 | 57 | \* An internal implementation, similar to OpenZeppelin's 58 | \* https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ca822213f2275a14c26167bd387ac3522da67fe9/contracts/token/ERC20/ERC20.sol#L222 59 | \* 60 | \* @type: ($state, ADDR, ADDR, Int) => $result; 61 | _transfer(state, fromAddr, toAddr, amount) == 62 | LET fromBalance == state.balanceOf[fromAddr] IN 63 | \* check the require(...) statements 64 | LET err == 65 | CASE ~(fromAddr /= ZERO_ADDRESS) -> "ERC20: transfer from the zero address" 66 | [] ~(toAddr /= ZERO_ADDRESS) -> "ERC20: transfer to the zero address" 67 | [] ~(fromBalance >= amount) -> "ERC20: transfer amount exceeds balance" 68 | [] OTHER -> "" 69 | IN 70 | IF err /= "" 71 | THEN Error(err) 72 | ELSE 73 | LET newBalances == 74 | IF (fromAddr = toAddr) 75 | THEN state.balanceOf 76 | ELSE 77 | \* Comment from ERC20.sol (see the above link): 78 | \* Overflow not possible: the sum of all balances is capped 79 | \* by totalSupply, and the sum is preserved by 80 | \* decrementing then incrementing. 81 | [ 82 | state.balanceOf EXCEPT ![fromAddr] = fromBalance - amount, 83 | ![toAddr] = @ + amount 84 | ] 85 | IN 86 | Ok(TRUE, [ state EXCEPT !.balanceOf = newBalances ]) 87 | 88 | (* 89 | * ERC20: Moves amount tokens from the sender’s account to `toAddress`. 90 | * Returns a boolean value indicating whether the operation succeeded. 91 | * 92 | * @type: ($state, ADDR, ADDR, Int) => $result; 93 | *) 94 | transfer(state, sender, toAddr, amount) == 95 | \* `transfer` always returns true, but we should check Erc20Result.error 96 | _transfer(state, sender, toAddr, amount) 97 | 98 | (* 99 | * ERC20: Returns the remaining number of tokens that spender will be allowed to 100 | * spend on behalf of owner through transferFrom. This is zero by default in EVM. 101 | * 102 | * This value may change when approve or transferFrom are called. 103 | * 104 | * TLA+: the actual allowance is set up to 0 in newErc20. 105 | * 106 | * @type: ($state, ADDR, ADDR) => Int; 107 | *) 108 | allowance(state, owner, spender) == 109 | state.allowance[owner, spender] 110 | 111 | (* 112 | * ERC20: Sets amount as the allowance of spender over the caller’s tokens. 113 | * 114 | * Returns a boolean value (and the new state) indicating whether the 115 | * operation succeeded. 116 | * 117 | * @type: ($state, ADDR, ADDR, Int) => $result; 118 | *) 119 | approve(state, sender, spender, amount) == 120 | LET err == 121 | CASE ~(sender /= ZERO_ADDRESS) -> "ERC20: transfer from the zero address" 122 | [] ~(spender /= ZERO_ADDRESS) -> "ERC20: transfer to the zero address" 123 | [] OTHER -> "" 124 | IN 125 | IF err /= "" 126 | THEN Error(err) 127 | ELSE 128 | \* the case of sender == spender seems to be allowed 129 | Ok(TRUE, [ state EXCEPT !.allowance[sender, spender] = amount ]) 130 | 131 | (* 132 | * Moves amount tokens from `fromAddr` to `toAddr` using the allowance mechanism. 133 | * amount is then deducted from the caller’s allowance. 134 | * 135 | * Returns a boolean value indicating whether the operation succeeded. 136 | * 137 | * 138 | * @type: ($state, ADDR, ADDR, ADDR, Int) => $result; 139 | *) 140 | transferFrom(state, sender, fromAddr, toAddr, amount) == 141 | \* _spendAllowance 142 | LET currentAllowance == state.allowance[fromAddr, sender] IN 143 | LET err == 144 | CASE ~(currentAllowance >= amount) -> "ERC20: insufficient allowance" 145 | [] ~(fromAddr /= ZERO_ADDRESS) -> "ERC20: approve from the zero address" 146 | [] ~(toAddr /= ZERO_ADDRESS) -> "ERC20: approve to the zero address" 147 | [] OTHER -> "" 148 | IN 149 | LET updatedState == 150 | IF currentAllowance = MAX_UINT 151 | THEN state 152 | ELSE [ state EXCEPT !.allowance[fromAddr, sender] = @ - amount ] 153 | IN 154 | IF err /= "" 155 | THEN Error(err) 156 | ELSE _transfer(updatedState, fromAddr, toAddr, amount) 157 | 158 | (**************************************************************** 159 | * PROPERTIES that do not belong too the original EIP20 spec, 160 | * but they should hold true. 161 | ****************************************************************) 162 | 163 | \* Compute the sum over all balances. 164 | \* 165 | \* @type: (ADDR -> Int) => Int; 166 | sumOverBalances(balances) == 167 | LET \* @type: (Int, ADDR) => Int; 168 | Add(sum, addr) == sum + balances[addr] 169 | IN 170 | \* NOTE: we could use FiniteSetsExt!FoldSet from the community modules. 171 | \* https://github.com/tlaplus/CommunityModules/blob/a206a33c7e40a75beb06554482bde26b8f40d5f4/modules/FiniteSetsExt.tla#L7-L14 172 | \* We just did not want to copy one more module. 173 | ApaFoldSet(Add, 0, DOMAIN balances) 174 | 175 | \* The total supply, as stored in the state, 176 | \* is equal to the sum of amounts over all balances. 177 | \* 178 | \* @type: $state => Bool; 179 | isTotalSupplyCorrect(state) == 180 | sumOverBalances(state.balanceOf) = state.totalSupply 181 | 182 | \* Zero address should not carry coins. 183 | \* 184 | \* @type: $state => Bool; 185 | isZeroAddressEmpty(state) == 186 | state.balanceOf[ZERO_ADDRESS] = 0 187 | 188 | \* There are no overflows in totalSupply, balanceOf, and approve. 189 | \* 190 | \* @type: $state => Bool; 191 | isNoOverflows(state) == 192 | /\ isUint(state.totalSupply) 193 | /\ \A a \in DOMAIN state.balanceOf: 194 | isUint(state.balanceOf[a]) 195 | /\ \A p \in DOMAIN state.allowance: 196 | isUint(state.allowance[p]) 197 | 198 | ================================================================================ -------------------------------------------------------------------------------- /examples/clock-sync/ClockSync7.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE ClockSync7 ------------------------------ 2 | (* 3 | * Incremental TLA+ specification of the clock synchronization algorithm from: 4 | * 5 | * Hagit Attiya, Jennifer Welch. Distributed Computing. Wiley Interscience, 2004, 6 | * p. 147, Algorithm 20. 7 | * 8 | * Assumptions: timestamps are natural numbers, not reals. 9 | * 10 | * Version 7: Add an inductive invariant for SkewInv (wip) 11 | * Version 6: Make Proc a parameter and use ApaFoldSet 12 | * Version 5: Ignoring the message by the process itself 13 | * Version 4: Adjusting clock values (test 4 + invariant violation) 14 | * Version 3: Receiving messages (test 3) 15 | * Version 2: Sending messages (test 2) 16 | * Version 1: Setting up the clocks (test 1) 17 | *) 18 | EXTENDS Integers, FiniteSets, Apalache 19 | 20 | CONSTANTS 21 | \* the set of processes 22 | \* @type: Set(Str); 23 | Proc, 24 | \* minimum message delay 25 | \* @type: Int; 26 | t_min, 27 | \* maximum message delay 28 | \* @type: Int; 29 | t_max 30 | 31 | ASSUME(t_min >= 0 /\ t_max > t_min) 32 | 33 | VARIABLES 34 | \* the reference clock, inaccessible to the processes 35 | \* @type: Int; 36 | time, 37 | \* hardware clock of a process 38 | \* @type: Str -> Int; 39 | hc, 40 | \* clock adjustment of a process 41 | \* @type: Str -> Int; 42 | adj, 43 | \* clock diff for process j, as seen by a process j 44 | \* @type: <> -> Int; 45 | diff, 46 | \* messages sent by the processes 47 | \* @type: Set([src: Str, ts: Int]); 48 | msgs, 49 | \* messages received by the processes 50 | \* @type: Str -> Set([src: Str, ts: Int]); 51 | rcvd, 52 | \* the control state of a process 53 | \* @type: Str -> Str; 54 | state 55 | 56 | (***************************** DEFINITIONS *********************************) 57 | 58 | \* the number of processes 59 | NProc == Cardinality(Proc) 60 | 61 | \* control states 62 | State == { "init", "sent", "sync" } 63 | 64 | \* the adjusted clock of process i 65 | AC(i) == hc[i] + adj[i] 66 | 67 | \* sum up the clock differences as observed by a process p 68 | \* @type: (<> -> Int, Str) => Int; 69 | DiffSum(df, p) == 70 | LET Add(total, q) == 71 | total + df[p, q] 72 | IN 73 | ApaFoldSet(Add, 0, Proc) 74 | 75 | (*************************** INITIALIZATION ********************************) 76 | 77 | \* Initialization 78 | Init == 79 | /\ time \in Nat 80 | /\ hc \in [ Proc -> Nat ] 81 | /\ adj = [ p \in Proc |-> 0 ] 82 | /\ diff = [ <> \in Proc \X Proc |-> 0 ] 83 | /\ state = [ p \in Proc |-> "init" ] 84 | /\ msgs = {} 85 | /\ rcvd = [ p \in Proc |-> {} ] 86 | 87 | (******************************* ACTIONS ***********************************) 88 | 89 | \* send the value of the hardware clock 90 | SendMsg(p) == 91 | /\ state[p] = "init" 92 | /\ msgs' = msgs \union { [ src |-> p, ts |-> hc[p] ] } 93 | /\ state' = [ state EXCEPT ![p] = "sent" ] 94 | /\ UNCHANGED <> 95 | 96 | \* If the process has received a message from all processes (but p), 97 | \* then adjust the clock. Otherwise, accumulate the difference. 98 | \* @type: (Str, <> -> Int, 99 | \* Set([src: Str, ts: Int])) => Bool; 100 | AdjustClock(p, newDiff, newRcvd) == 101 | LET fromAll == { m.src: m \in newRcvd } = Proc \ { p } IN 102 | IF fromAll 103 | THEN 104 | \* Assuming that Proc = { "p1", "p2" }. 105 | \* See ClockSync5 for the general case. 106 | /\ adj' = [ adj EXCEPT ![p] = DiffSum(newDiff, p) \div NProc ] 107 | /\ state' = [ state EXCEPT ![p] = "sync" ] 108 | ELSE 109 | UNCHANGED <> 110 | 111 | \* Receive a message sent by another process. 112 | \* Adjust the clock if the message has been received from all processes. 113 | ReceiveMsg(p) == 114 | /\ state[p] = "sent" 115 | /\ \E m \in msgs: 116 | /\ m \notin rcvd[p] 117 | /\ m.src /= p \* ignore the message by p itself 118 | \* the message cannot be received earlier than after t_min 119 | /\ hc[m.src] >= m.ts + t_min 120 | \* accumulate the difference and adjust the clock if possible 121 | /\ LET delta == m.ts - hc[p] + (t_min + t_max) \div 2 IN 122 | LET newDiff == [ diff EXCEPT ![p, m.src] = delta ] IN 123 | LET newRcvd == rcvd[p] \union { m } IN 124 | /\ AdjustClock(p, newDiff, newRcvd) 125 | /\ rcvd' = [ rcvd EXCEPT ![p] = newRcvd ] 126 | /\ diff' = newDiff 127 | /\ UNCHANGED <> 128 | 129 | \* let the time flow 130 | AdvanceClocks(delta) == 131 | /\ delta > 0 132 | \* clocks can be advanced only if there is no pending message 133 | /\ \A m \in msgs: 134 | hc[m.src] + delta > t_max => 135 | \A p \in Proc: 136 | m \in rcvd[m.src] 137 | \* clocks are advanced uniformly 138 | /\ time' = time + delta 139 | /\ hc' = [ p \in Proc |-> hc[p] + delta ] 140 | /\ UNCHANGED <> 141 | 142 | \* all actions together 143 | Next == 144 | \/ \E delta \in Int: 145 | AdvanceClocks(delta) 146 | \/ \E p \in Proc: 147 | \/ SendMsg(p) 148 | \/ ReceiveMsg(p) 149 | 150 | (******************************* PROPERTIES *********************************) 151 | 152 | \* Theorem 6.15 from AW04: 153 | \* Algorithm achieves u * (1 - 1/n)-synchronization for n processors. 154 | SkewInv == 155 | LET allSync == 156 | \A p \in Proc: state[p] = "sync" 157 | IN 158 | LET boundedSkew == 159 | LET bound == 160 | \* extend the bound by NProc to account for rounding errors 161 | (t_max - t_min) * (NProc - 1) + NProc * NProc 162 | IN 163 | \A p, q \in Proc: 164 | LET df == AC(p) - AC(q) 165 | IN 166 | -bound <= df * NProc /\ df * NProc <= bound 167 | IN 168 | allSync => boundedSkew 169 | 170 | (******************** INDUCTIVE INVARIANT **********************************) 171 | 172 | \* check it as follows: 173 | \* apalache check --cinit=ConstInit --init=IndInvInit --inv=IndInv \ 174 | \* --length=1 MC_ClockSync7.tla 175 | 176 | \* like TypeOK, but used only in initialization 177 | TypeInit == 178 | /\ time \in Nat 179 | /\ hc \in [ Proc -> Nat ] 180 | /\ adj \in [ Proc -> Int ] 181 | /\ diff \in [ Proc \X Proc -> Int ] 182 | /\ state \in [ Proc -> State ] 183 | /\ \E t \in [ Proc -> Int ]: 184 | msgs \in SUBSET { [ src |-> p, ts |-> t[p] ]: p \in Proc } 185 | /\ rcvd \in [ Proc -> SUBSET msgs ] 186 | 187 | IndInv == 188 | \* we do not include TypeInit here, 189 | \* as apalache would not be able to prove TypeInit as an invariant 190 | /\ SkewInv 191 | \* copy & paste from Test2_Inv 192 | /\ \A p \in Proc: 193 | state[p] \in { "sent", "sync" } <=> 194 | \E m \in msgs: 195 | m.src = p 196 | \* copy & paste from Test3_Inv 197 | /\ \A m \in msgs: 198 | \* no messages from the future 199 | m.ts <= hc[m.src] 200 | /\ \A p \in Proc: 201 | \A m \in rcvd[p]: 202 | \* the message is received no earlier than after t_min 203 | hc[m.src] >= m.ts + t_min 204 | /\ \A m \in msgs: 205 | \* the message is received no later than before t_max 206 | m.ts >= hc[m.src] + t_max => 207 | \A p \in Proc: 208 | m \in rcvd[p] 209 | \* copy & paste from Test4_Inv 210 | /\ \A p \in Proc: 211 | state[p] = "sync" <=> 212 | { m.src: m \in rcvd[p] } = Proc \ { p } 213 | \* no adjustments made before entering "sync" 214 | /\ \A p \in Proc: 215 | state[p] /= "sync" => adj[p] = 0 216 | \* as we see from AdjustClock, here is how 'adj' is computed 217 | /\ \A p \in Proc: 218 | state[p] = "sync" => adj[p] = DiffSum(diff, p) \div NProc 219 | \* we need an invariant about diff 220 | \* TBD 221 | 222 | 223 | IndInvInit == 224 | TypeInit /\ IndInv 225 | 226 | =============================================================================== 227 | -------------------------------------------------------------------------------- /examples/erc20-approve-attack/tlc_counterexample.out: -------------------------------------------------------------------------------- 1 | State 1: 2 | /\ allowance = (<> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0) 3 | /\ lastTx = [id |-> 0, tag |-> "None", fail |-> FALSE] 4 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 11 @@ A_Eve :> 4) 5 | /\ nextTxId = 0 6 | /\ pendingTransactions = {} 7 | 8 | State 2: 9 | /\ allowance = (<> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0) 10 | /\ lastTx = [id |-> 0, tag |-> "None", fail |-> FALSE] 11 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 11 @@ A_Eve :> 4) 12 | /\ nextTxId = 1 13 | /\ pendingTransactions = {[id |-> 0, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 5, spender |-> A_Alice]} 14 | 15 | State 3: 16 | /\ allowance = (<> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0) 17 | /\ lastTx = [id |-> 0, tag |-> "None", fail |-> FALSE] 18 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 11 @@ A_Eve :> 4) 19 | /\ nextTxId = 2 20 | /\ pendingTransactions = {[id |-> 0, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 5, spender |-> A_Alice], [id |-> 1, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 12, spender |-> A_Alice]} 21 | 22 | State 4: 23 | /\ allowance = (<> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0) 24 | /\ lastTx = [id |-> 0, tag |-> "None", fail |-> FALSE] 25 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 11 @@ A_Eve :> 4) 26 | /\ nextTxId = 3 27 | /\ pendingTransactions = {[id |-> 0, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 5, spender |-> A_Alice], [id |-> 1, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 12, spender |-> A_Alice], [id |-> 2, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Bob, toAddr |-> A_Eve, value |-> 6, fromAddr |-> A_Alice]} 28 | 29 | State 5: 30 | /\ allowance = (<> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 12 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0) 31 | /\ lastTx = [id |-> 1, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 12, spender |-> A_Alice] 32 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 11 @@ A_Eve :> 4) 33 | /\ nextTxId = 3 34 | /\ pendingTransactions = {[id |-> 0, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 5, spender |-> A_Alice], [id |-> 2, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Bob, toAddr |-> A_Eve, value |-> 6, fromAddr |-> A_Alice]} 35 | 36 | State 6: 37 | /\ allowance = (<> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 12 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0) 38 | /\ lastTx = [id |-> 0, tag |-> "None", fail |-> FALSE] 39 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 11 @@ A_Eve :> 4) 40 | /\ nextTxId = 4 41 | /\ pendingTransactions = {[id |-> 0, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 5, spender |-> A_Alice], [id |-> 3, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 2, spender |-> A_Bob], [id |-> 2, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Bob, toAddr |-> A_Eve, value |-> 6, fromAddr |-> A_Alice]} 42 | 43 | State 7: 44 | /\ allowance = (<> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 12 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0) 45 | /\ lastTx = [id |-> 0, tag |-> "None", fail |-> FALSE] 46 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 11 @@ A_Eve :> 4) 47 | /\ nextTxId = 5 48 | /\ pendingTransactions = {[id |-> 0, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 5, spender |-> A_Alice], [id |-> 3, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 2, spender |-> A_Bob], [id |-> 2, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Bob, toAddr |-> A_Eve, value |-> 6, fromAddr |-> A_Alice], [id |-> 4, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Eve, toAddr |-> A_Bob, value |-> 18, fromAddr |-> A_Bob]} 49 | 50 | State 8: 51 | /\ allowance = (<> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 12 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0) 52 | /\ lastTx = [id |-> 0, tag |-> "None", fail |-> FALSE] 53 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 11 @@ A_Eve :> 4) 54 | /\ nextTxId = 6 55 | /\ pendingTransactions = {[id |-> 0, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 5, spender |-> A_Alice], [id |-> 3, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 2, spender |-> A_Bob], [id |-> 2, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Bob, toAddr |-> A_Eve, value |-> 6, fromAddr |-> A_Alice], [id |-> 4, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Eve, toAddr |-> A_Bob, value |-> 18, fromAddr |-> A_Bob], [id |-> 5, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Alice, toAddr |-> A_Eve, value |-> 10, fromAddr |-> A_Bob]} 56 | 57 | State 9: 58 | /\ allowance = (<> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 12 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0 @@ <> :> 0) 59 | /\ lastTx = [id |-> 0, tag |-> "None", fail |-> FALSE] 60 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 11 @@ A_Eve :> 4) 61 | /\ nextTxId = 7 62 | /\ pendingTransactions = {[id |-> 0, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 5, spender |-> A_Alice], [id |-> 3, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 2, spender |-> A_Bob], [id |-> 2, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Bob, toAddr |-> A_Eve, value |-> 6, fromAddr |-> A_Alice], [id |-> 4, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Eve, toAddr |-> A_Bob, value |-> 18, fromAddr |-> A_Bob], [id |-> 5, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Alice, toAddr |-> A_Eve, value |-> 10, fromAddr |-> A_Bob], [id |-> 6, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Alice, toAddr |-> A_Alice, value |-> 15, fromAddr |-> A_Alice]} 63 | 64 | State 10: 65 | /\ allowance = [pair \in {<>, <>, <>, <>, <>, <>, <>, <>, <>} |-> ] 66 | /\ lastTx = [id |-> 5, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Alice, toAddr |-> A_Eve, value |-> 10, fromAddr |-> A_Bob] 67 | /\ balanceOf = (A_Alice :> 15 @@ A_Bob :> 1 @@ A_Eve :> 14) 68 | /\ nextTxId = 7 69 | /\ pendingTransactions = {[id |-> 0, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 5, spender |-> A_Alice], [id |-> 3, tag |-> "approve", fail |-> FALSE, sender |-> A_Bob, value |-> 2, spender |-> A_Bob], [id |-> 2, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Bob, toAddr |-> A_Eve, value |-> 6, fromAddr |-> A_Alice], [id |-> 4, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Eve, toAddr |-> A_Bob, value |-> 18, fromAddr |-> A_Bob], [id |-> 6, tag |-> "transferFrom", fail |-> FALSE, sender |-> A_Alice, toAddr |-> A_Alice, value |-> 15, fromAddr |-> A_Alice]} 70 | -------------------------------------------------------------------------------- /docs/token-transfer-steps.md: -------------------------------------------------------------------------------- 1 | # Typing the specification and checking it 2 | 3 | ## Version 1: Introducing data structures 4 | 5 | 1. Open [TokenTransfer1.tla](../examples/token-transfer/TokenTransfer1.tla) and [MC1.tla](../examples/token-transfer/MC1.tla). 6 | 1. Notice how `CHAINS`, `ACCOUNTS`, and `banks` are declared. 7 | 1. Run: 8 | 9 | ```sh 10 | $ apalache-mc check MC1.tla 11 | ``` 12 | 13 | 4. The tool reports no errors. 14 | 15 | ## Version 2: Let the banks do local banking 16 | 17 | 1. Open [TokenTransfer2.tla](../examples/token-transfer/TokenTransfer2.tla) and [MC2.tla](../examples/token-transfer/MC2.tla). 18 | 1. Inspect the operators `LocalTransfer` and `Next`. Do you see any issues? 19 | 1. Inspect the expected invariants `ReservesInv` and `NoNegativeAccounts`. 20 | Do you see any issues? 21 | 1. Check the property `ReservesInv` by running: 22 | 23 | ```sh 24 | $ apalache-mc check --inv=ReservesInv MC2.tla 25 | ``` 26 | 27 | 1. The tool reports on a counterexample. Check `counterexample.tla`. 28 | Can you fix the issue? 29 | 30 | 1. Check the property `NoNegativeAccounts` by running: 31 | 32 | ```sh 33 | $ apalache-mc check --inv=NoNegativeAccounts MC2.tla 34 | ``` 35 | 36 | 1. The tool reports on a counterexample. Check `counterexample.tla`. 37 | Can you fix the issue? 38 | 39 | 40 | ## Version 3: Fixing the invariants and introducing more properties 41 | 42 | 1. Open [TokenTransfer3.tla](../examples/token-transfer/TokenTransfer3.tla) and [MC3.tla](../examples/token-transfer/MC3.tla). 43 | 1. See how we have fixed `NoNegativeAccounts` by using `Nat` instead of `Int`. 44 | 1. Notice how we introduced the constant `GENESIS_SUPPLY` and 45 | the operators `SumAddresses` and `ChainSupply`. 46 | 1. Check what has changed in `Init` and `Next`. 47 | 1. Inspect the new invariant `ChainSupplyUnchanged`. 48 | 1. Observe how we have introduced `GENESIS_SUPPLY` in 49 | [MC3.tla](../examples/token-transfer/MC3.tla). 50 | 51 | 1. Check the property `ChainSupplyUnchanged` by running: 52 | 53 | ```sh 54 | $ apalache-mc check --inv=ChainSupplyUnchanged MC3.tla 55 | ``` 56 | 57 | 1. Apalache is stuck after some time. Any ideas why? 58 | 59 | 1. Check the detailed log by typing: 60 | 61 | ```sh 62 | $ tail -f detailed.log 63 | ``` 64 | 65 | 1. You can see the results of preprocessing steps in the directory 66 | that is called like `x/hh.mm-DD.MM.YYYY-.*`. The file `out-analysis.tla` 67 | is the final result of all preprocessing steps. 68 | 69 | 1. You can tell Apalache to produce the log of SMT constraints: 70 | 71 | ```sh 72 | $ apalache-mc check --inv=ChainSupplyUnchanged --debug MC3.tla 73 | ``` 74 | 75 | 1. The log file can be found in `log0.smt`. If you are feeling adventurous 76 | today, you can install [Microsoft Z3](https://github.com/Z3Prover/z3) and run 77 | the log directly with `z3`: 78 | 79 | ```sh 80 | $ z3 -smt2 log0.smt 81 | ``` 82 | 83 | ## Version 4: skipped 84 | 85 | ## Version 5: Make Apalache fast again 86 | 87 | 1. Open [TokenTransfer5.tla](../examples/token-transfer/TokenTransfer5.tla) and 88 | [MC5.tla](../examples/token-transfer/MC5.tla). 89 | 1. Notice that we have added two new operators: `TypeOK` and `IndInv`. 90 | 1. Run Apalache as follows: 91 | 92 | ```sh 93 | $ apalache-mc check --init=IndInv --inv=IndInv --length=1 MC5.tla 94 | $ apalache-mc check --init=Init --inv=IndInv --length=0 MC5.tla 95 | $ apalache-mc check --init=IndInv --inv=ChainSupplyUnchanged --length=0 MC5.tla 96 | ``` 97 | 98 | 1. Why do you think we have checked `ChainSupplyUnchanged` for the executions 99 | of arbitrary length? 100 | 101 | ## Version 6: Send packets! 102 | 103 | 1. Open [TokenTransfer6.tla](../examples/token-transfer/TokenTransfer6.tla) and 104 | [MC6.tla](../examples/token-transfer/MC6.tla). 105 | 1. Notice the changes: 106 | - new constant `CHANNELS` 107 | - new variable `sentPackets` 108 | - new definition `Escrow == "escrow"` 109 | - update in `Init` and `Next` 110 | - new operators `LocalStep` and `SendPacketFromSource` 111 | 112 | 1. Check the invariant `InFlyPacketIsSecured` by running: 113 | 114 | ```sh 115 | $ apalache-mc check --inv=InFlyPacketIsSecured MC6.tla 116 | ``` 117 | 118 | 1. Do you think it is a good invariant? Can we improve it? 119 | 120 | 1. Can you write a version of `InFlyPacketIsSecured` that counts 121 | the sum of token over all in-fly packets? 122 | 123 | ## Version 7: Receive packets 124 | 125 | 1. Open [TokenTransfer7.tla](../examples/token-transfer/TokenTransfer7.tla) and 126 | [MC7.tla](../examples/token-transfer/MC7.tla). 127 | 1. Notice the changes: 128 | - new field `seqno` in sentPackets 129 | - new variable `seqno` 130 | - new variable `deliveredNums` 131 | - new operators `MintCoins` and `ReceivePacketFromSource` 132 | - updates in `Init` and `Next` 133 | 134 | 1. Do you think our spec is still OK? 135 | 1. Check the property `ChainSupplyUnchanged` by running: 136 | 137 | ```sh 138 | $ apalache-mc check --inv=ChainSupplyUnchanged MC7.tla 139 | ``` 140 | 141 | 1. If it takes too long, let's cheat a bit and check how to tune the model 142 | checker. Read the page on [tuning 143 | parameters](https://apalache.informal.systems/docs/apalache/tuning.html). 144 | 145 | 1. Create the file `apalache.properties` and write the follows: 146 | 147 | ```sh 148 | search.invariantFilter=[2-4] 149 | search.invariant.mode=after 150 | ``` 151 | 152 | The first option instructs Apalache to check the invariant only after 2, 3, or 153 | 4 steps. The second option instructs Apalache to check the invariant after 154 | choosing a transition in a step, not before. 155 | 156 | 1. Run Apalache with the tuning options: 157 | 158 | ```sh 159 | $ apalache-mc check --inv=ChainSupplyUnchanged --tuning=apalache.properties MC7.tla 160 | ``` 161 | 162 | 1. Apalache should find an error very quickly. Check `counterexample1.tla`. 163 | Do you understand what happened? 164 | 165 | ## Version 8: Introducing denominations 166 | 167 | 1. Open [TokenTransfer8.tla](../examples/token-transfer/TokenTransfer8.tla) and 168 | [MC8.tla](../examples/token-transfer/MC8.tla). 169 | 1. Notice the changes: 170 | - new constants `DENOMS` and `MK_DENOM` 171 | - new operator `Native` 172 | - updates in many operators to attach denomination to an account 173 | 174 | 1. Check the property `NoForeignCoins` by running: 175 | 176 | ```sh 177 | $ apalache-mc check --inv=NoForeignCoins MC8.tla 178 | ``` 179 | 180 | 1. Check `counterexample.tla` for an example. 181 | 182 | 1. We can run Apalache again to check `ChainSupplyUnchanged`: 183 | 184 | ```sh 185 | $ apalache-mc check --inv=ChainSupplyUnchanged --tuning=apalache.properties MC7.tla 186 | ``` 187 | 188 | 1. This time, Apalache does not come back that fast. If you want to prove 189 | this property for all executions, construct an inductive invariant, 190 | similar to what we did with `MC5.tla`. 191 | 192 | ## Version 9: Sending coins back 193 | 194 | 1. Open [TokenTransfer9.tla](../examples/token-transfer/TokenTransfer9.tla) and 195 | [MC9.tla](../examples/token-transfer/MC9.tla). 196 | 1. Notice the changes: 197 | - new constant `UNMK_DENOM` 198 | - new operators: `SendPacket`, `BurnCoins`, `SendPacketToSource`, 199 | `ReceivePacketOnSource` 200 | 1. Check the property `InFlyPacketIsSecured` by running: 201 | 202 | ```sh 203 | $ apalache-mc check --inv=InFlyPacketIsSecured MC9.tla 204 | ``` 205 | 206 | 1. Check `counterexample.tla` for an example. 207 | 208 | 1. Uncomment the precondition in the definition of `InFlyPacketIsSecured` 209 | and check the property again. 210 | 211 | ## Version 10: Adding timeouts 212 | 213 | 1. Open [TokenTransfer10.tla](../examples/token-transfer/TokenTransfer10.tla) and 214 | [MC10.tla](../examples/token-transfer/MC10.tla). 215 | 1. Notice the changes: 216 | - new variables: `srcTimeoutNums` and `dstTimeoutNums` 217 | - new operators: `RegisterTimeout` and `ApplyTimeout` 218 | 1. Check the property `InFlyPacketIsSecured` by running: 219 | 220 | ```sh 221 | $ apalache-mc check --inv=InFlyPacketIsSecured MC10.tla 222 | ``` 223 | 224 | 1. Check the property `AllChainsSupplyUnchanged` by running: 225 | 226 | ```sh 227 | $ apalache-mc check --inv=AllChainsSupplyUnchanged MC10.tla 228 | ``` 229 | 230 | ## Writing unit tests 231 | 232 | 1. Open [Test10.tla](../examples/token-transfer/Test10.tla). 233 | 1. Read the definitions of operators `TestApplyTimeoutRequires`, 234 | `TestApplyTimeoutAction`, and `TestApplyTimeoutEnsures`. 235 | 1. Run the test as follows: 236 | 237 | ```sh 238 | $ apalache-mc check --init=TestApplyTimeoutRequires \ 239 | --next=TestApplyTimeoutAction --inv=TestApplyTimeoutEnsures --length=1 Test10.tla 240 | ``` 241 | 242 | ## Version NNN: Fungible token transfer 243 | 244 | If you are not tired, you can check 245 | [ICS20](https://github.com/cosmos/ics/tree/master/spec/ics-020-fungible-token-transfer) 246 | and improve `TokenTransfer10.tla` to support the full token transfer protocol. 247 | 248 | -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer9.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer9 --------------------------------- 2 | (* 3 | * This is an example of a very simplistic token transfer 4 | * for presentation purposes. 5 | * Do not use it in production, as it may lead to loss of tokens. 6 | * 7 | * Version 9: receive and send in the backward direction 8 | * Version 8: introducing basic denominations 9 | * Version 7: introducing basic packet receive from source 10 | * Version 6: introducing basic packet send from source 11 | * Version 5: introducing an inductive invariant for Apalache 12 | * Version 4: bounding the amounts to help the model checkers 13 | * Version 3: fixing the invariants and introducing more 14 | * Version 2: let the banks do the local banking 15 | * Version 1: introducing data structures 16 | * 17 | * Igor Konnov, 2021 18 | *) 19 | EXTENDS Integers, Apalache, typedefs 20 | 21 | CONSTANT 22 | \* A set of blockchains, i.e., their names 23 | \* @type: Set(Str); 24 | CHAINS, 25 | \* A set of channels, that is, pairs of chains 26 | \* @type: Set(<>); 27 | CHANNELS, 28 | \* A set of accounts, i.e., their names 29 | \* @type: Set(ACCOUNT); 30 | ACCOUNTS, 31 | \* Initial supply for every chain 32 | \* @type: CHAIN -> Int; 33 | GENESIS_SUPPLY, 34 | \* A set of denominations 35 | \* @type: Set(DENOM); 36 | DENOMS, 37 | \* A function that produces a new denomination for a chain a denomination 38 | \* @type: <> -> DENOM; 39 | MK_DENOM, 40 | \* A function that should work as an inverse of MK_DENOM 41 | \* @type: <> -> DENOM; 42 | UNMK_DENOM 43 | 44 | VARIABLES 45 | \* For every chain and account, store the amount of tokens in the account 46 | \* for each denomination 47 | \* @type: DADDR -> Int; 48 | banks, 49 | \* Packets that are sent by one chain to another (e.g., via an IBC channel) 50 | \* @type: Set([seqno: Int, src: CHAIN, dest: CHAIN, data: [sender: ACCOUNT, receiver: ACCOUNT, denom: DENOM, amount: Int]]); 51 | sentPackets, 52 | \* The sequence numbers of delivered packets 53 | \* @type: Set(Int); 54 | deliveredNums, 55 | \* An imaginary global counter that we use to assign unique sequence numbers 56 | \* @type: Int; 57 | seqno 58 | 59 | (*************************** OPERATORS ***************************************) 60 | \* For simplicity, we fix the name of the escrow account. 61 | \* In ICS20, one introduces one escrow account per channel. 62 | Escrow == "escrow" 63 | 64 | \* For simplicity, we call the coin native if it has the same name as the chain 65 | Native(chain) == chain 66 | 67 | \* @type: (DADDR -> Int, Set(DADDR)) => Int; 68 | SumAddresses(amounts, Addrs) == 69 | LET Add(sum, addr) == sum + amounts[addr] IN 70 | ApaFoldSet(Add, 0, Addrs) 71 | 72 | \* @type: (DADDR -> Int, CHAIN) => Int; 73 | ChainSupply(amounts, chain) == 74 | SumAddresses(amounts, {chain} \X ACCOUNTS \X { Native(chain) }) 75 | 76 | (**************************** SYSTEM *****************************************) 77 | 78 | \* Initialize the world, e.g., from the last upgrade 79 | Init == 80 | /\ seqno = 0 81 | /\ sentPackets = {} 82 | /\ deliveredNums = {} 83 | /\ \E b \in [ CHAINS \X ACCOUNTS \X DENOMS -> Nat ]: 84 | /\ \A chain \in CHAINS: 85 | /\ b[chain, "reserve", chain] > 0 86 | /\ ChainSupply(b, chain) = GENESIS_SUPPLY[chain] 87 | /\ \A a \in ACCOUNTS, d \in DENOMS: 88 | \* no tokens in foreign denominations 89 | d /= Native(chain) => b[chain, a, d] = 0 90 | /\ banks = b 91 | 92 | \* Transfer the tokens from on account to another (on the same chain) 93 | LocalTransfer(chain, from, to, denom, amount) == 94 | /\ banks[chain, from, denom] >= amount 95 | /\ from /= to 96 | /\ banks' = [banks EXCEPT 97 | ![chain, from, denom] = banks[chain, from, denom] - amount, 98 | ![chain, to, denom] = banks[chain, to, denom] + amount 99 | ] 100 | 101 | \* A computation on the local chain 102 | LocalStep == 103 | /\ \E chain \in CHAINS, from, to \in ACCOUNTS, 104 | denom \in DENOMS, amount \in Nat: 105 | /\ from /= Escrow 106 | /\ to /= Escrow 107 | /\ LocalTransfer(chain, from, to, denom, amount) 108 | /\ UNCHANGED <> 109 | 110 | \* send a packet over a channel 111 | \* @type: (<>, Str, Str, Str, Str, Int) => Bool; 112 | SendPacket(chan, dir, sender, receiver, denom, amount) == 113 | LET data == [seqno |-> seqno, 114 | sender |-> sender, 115 | receiver |-> receiver, 116 | denom |-> denom, 117 | amount |-> amount] 118 | src == IF dir = "forward" THEN chan[1] ELSE chan[2] 119 | dst == IF dir = "forward" THEN chan[2] ELSE chan[1] 120 | packet == [src |-> src, dest |-> dst, data |-> data] 121 | IN 122 | /\ sentPackets' = sentPackets \union { packet } 123 | /\ seqno' = seqno + 1 124 | /\ UNCHANGED deliveredNums 125 | 126 | \* Send a packet to transfer tokens (from the source) 127 | SendPacketFromSource == 128 | \E chan \in CHANNELS, sender, receiver \in ACCOUNTS, 129 | denom \in DENOMS, amount \in Nat: 130 | /\ sender /= Escrow /\ receiver /= Escrow 131 | /\ amount > 0 132 | \* the source direction: escrow source tokens 133 | /\ LocalTransfer(chan[1], sender, Escrow, denom, amount) 134 | /\ SendPacket(chan, "forward", sender, receiver, denom, amount) 135 | 136 | \* Burn `amount` coins in the sender's account 137 | BurnCoins(chain, sender, denom, amount) == 138 | \* do not burn native coins 139 | /\ denom /= Native(chain) 140 | \* do not let to burn more coins than we have 141 | /\ LET newAmount == banks[chain, sender, denom] - amount IN 142 | /\ newAmount >= 0 143 | /\ banks' = [banks EXCEPT ![chain, sender, denom] = newAmount] 144 | 145 | \* Send a packet to return tokens (to the source) 146 | SendPacketToSource == 147 | \E chan \in CHANNELS, sender, receiver \in ACCOUNTS, 148 | denom \in DENOMS, amount \in Nat: 149 | /\ sender /= Escrow /\ receiver /= Escrow 150 | /\ amount > 0 151 | \* in the direction of the source: burn coins 152 | /\ BurnCoins(chan[2], sender, denom, amount) 153 | /\ SendPacket(chan, "backward", sender, receiver, denom, amount) 154 | 155 | \* Produce `amount` coins in the receiver's account (out of thin air!) 156 | MintCoins(chain, receiver, denom, amount) == 157 | \* do not mint native coins 158 | /\ denom /= Native(chain) 159 | /\ banks' = [banks EXCEPT ![chain, receiver, denom] = 160 | banks[chain, receiver, denom] + amount] 161 | 162 | \* Receive a packet on a non-source chain (note that ICS20 does more than that) 163 | ReceivePacketFromSource == 164 | \E packet \in sentPackets: 165 | /\ packet.seqno \notin deliveredNums 166 | \* In the implementation, we produce a new denomination. 167 | \* Mint coins that are different from native. 168 | /\ LET foreignDenom == MK_DENOM[packet.dest, packet.data.denom] IN 169 | MintCoins(packet.dest, 170 | packet.data.receiver, foreignDenom, packet.data.amount) 171 | /\ deliveredNums' = deliveredNums \union { packet.seqno } 172 | /\ UNCHANGED <> 173 | 174 | \* Receive a packet on the source chain 175 | ReceivePacketOnSource == 176 | \E packet \in sentPackets: 177 | /\ packet.seqno \notin deliveredNums 178 | \* translate the coin denomination, e.g., by pruning the prefix 179 | /\ LET sourceDenom == UNMK_DENOM[packet.denom] IN 180 | /\ sourceDenom /= "invalid" 181 | /\ LocalTransfer(packet.dst, packet.receiver, Escrow, 182 | sourceDenom, packet.amount) 183 | /\ deliveredNums' = deliveredNums \union { packet.seqno } 184 | /\ UNCHANGED <> 185 | 186 | \* Update the world 187 | Next == 188 | \/ LocalStep 189 | \/ SendPacketFromSource 190 | \/ ReceivePacketFromSource 191 | \/ SendPacketToSource 192 | \/ ReceivePacketOnSource 193 | 194 | (************************** PROPERTIES ***************************************) 195 | 196 | \* every bank always has reserves 197 | ReservesInv == 198 | \A chain \in CHAINS: 199 | banks[chain, "reserve", Native(chain)] > 0 200 | 201 | \* no bank account goes negative 202 | NoNegativeAccounts == 203 | \A address \in DOMAIN banks: 204 | banks[address] >= 0 205 | 206 | \* the supply remains constant 207 | ChainSupplyUnchanged == 208 | \A chain \in CHAINS: 209 | LET supply == ChainSupply(banks, chain) IN 210 | supply = GENESIS_SUPPLY[chain] 211 | 212 | \* for each in-fly packet, there is enough money in the escrow account 213 | InFlyPacketIsSecured == 214 | \A p \in sentPackets: 215 | p.seqno \notin deliveredNums => 216 | banks[p.src, Escrow, p.data.denom] >= p.data.amount 217 | 218 | (************* PROPERTIES TO PRODUCE COUNTEREXAMPLES *************************) 219 | 220 | \* This property should produce a counterexample that demonstrates 221 | \* that a foreign denomination can reach a blockchain 222 | NoForeignCoins == 223 | \A chain \in CHAINS, acc \in ACCOUNTS, d \in DENOMS: 224 | d /= Native(chain) => banks[chain, acc, d] = 0 225 | 226 | =============================================================================== 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /examples/token-transfer/TokenTransfer10.tla: -------------------------------------------------------------------------------- 1 | ----------------------- MODULE TokenTransfer10 --------------------------------- 2 | (* 3 | * This is an example of a very simplistic token transfer 4 | * for presentation purposes. 5 | * Do not use it in production, as it may lead to loss of tokens. 6 | * 7 | * Version 10: timeouts 8 | * Version 9: receive and send in the backward direction 9 | * Version 8: introducing basic denominations 10 | * Version 7: introducing basic packet receive from source 11 | * Version 6: introducing basic packet send from source 12 | * Version 5: introducing an inductive invariant for Apalache 13 | * Version 4: bounding the amounts to help the model checkers 14 | * Version 3: fixing the invariants and introducing more 15 | * Version 2: let the banks do the local banking 16 | * Version 1: introducing data structures 17 | * 18 | * Igor Konnov, 2021 19 | *) 20 | EXTENDS Integers, Apalache, typedefs 21 | 22 | CONSTANT 23 | \* A set of blockchains, i.e., their names 24 | \* @type: Set(CHAIN); 25 | CHAINS, 26 | \* A set of channels, that is, pairs of chains 27 | \* @type: Set(<>); 28 | CHANNELS, 29 | \* A set of accounts, i.e., their names 30 | \* @type: Set(ACCOUNT); 31 | ACCOUNTS, 32 | \* A set of all possible amounts 33 | \* @type: Set(Int); 34 | GENESIS_SUPPLY, 35 | \* A set of denominations 36 | \* @type: Set(DENOM); 37 | DENOMS, 38 | \* A function that produces a new denomination for a chain a denomination 39 | \* @type: <> -> DENOM; 40 | MK_DENOM, 41 | \* A function that should work as an inverse of MK_DENOM 42 | \* @type: <> -> DENOM; 43 | UNMK_DENOM 44 | 45 | VARIABLES 46 | \* For every chain and account, store the amount of tokens in the account 47 | \* for each denomination 48 | \* @type: DADDR -> Int; 49 | banks, 50 | \* Packets that are sent by one chain to another (e.g., via an IBC channel) 51 | \* @type: Set([seqno: Int, src: CHAIN, dest: CHAIN, data: [sender: ACCOUNT, receiver: ACCOUNT, denom: DENOM, amount: Int]]); 52 | sentPackets, 53 | \* The sequence numbers of delivered packets 54 | \* @type: Set(Int); 55 | deliveredNums, 56 | \* The sequence numbers of the packets that timed out 57 | \* @type: Set(Int); 58 | dstTimeoutNums, 59 | \* The sequence numbers of the packets that timed out, registered on source 60 | \* @type: Set(Int); 61 | srcTimeoutNums, 62 | \* An imaginary global counter that we use to assign unique sequence numbers 63 | \* @type: Int; 64 | seqno 65 | 66 | (*************************** OPERATORS ***************************************) 67 | \* For simplicity, we fix the name of the escrow account. 68 | \* In ICS20, one introduces one escrow account per channel. 69 | Escrow == "escrow" 70 | 71 | \* For simplicity, we call the coin native if it has the same name as the chain 72 | Native(chain) == chain 73 | 74 | \* Compute the sum of tokens over addresses. 75 | \* @type: (DADDR -> Int, Set(DADDR)) => Int; 76 | SumAddresses(amounts, Addrs) == 77 | LET Add(sum, addr) == sum + amounts[addr] IN 78 | ApaFoldSet(Add, 0, Addrs) 79 | 80 | \* Compute token supply in one chain. 81 | \* @type: (DADDR -> Int, CHAIN) => Int; 82 | ChainSupply(amounts, chain) == 83 | SumAddresses(amounts, {chain} \X ACCOUNTS \X { Native(chain) }) 84 | 85 | \* Compute token supply across all chains. 86 | \* @type: (CHAIN => Int) => Int; 87 | AllChainsSupply(GetChainSupply(_)) == 88 | LET Add(sum, chain) == sum + GetChainSupply(chain) IN 89 | ApaFoldSet(Add, 0, CHAINS) 90 | 91 | \* Compute chain supply in the genesis block. 92 | AllChainsGenesisSupply == 93 | LET Get(c) == GENESIS_SUPPLY[c] IN 94 | AllChainsSupply(Get) 95 | 96 | \* Compute chain supply given an "amounts" function 97 | \* @type: (DADDR -> Int) => Int; 98 | AllChainsAmountsSupply(amounts) == 99 | LET Get(c) == ChainSupply(amounts, c) IN 100 | AllChainsSupply(Get) 101 | 102 | (**************************** SYSTEM *****************************************) 103 | 104 | \* Initialize the world, e.g., from the last upgrade 105 | Init == 106 | /\ seqno = 0 107 | /\ sentPackets = {} 108 | /\ deliveredNums = {} 109 | /\ dstTimeoutNums = {} 110 | /\ srcTimeoutNums = {} 111 | /\ \E b \in [ CHAINS \X ACCOUNTS \X DENOMS -> Nat ]: 112 | /\ \A chain \in CHAINS: 113 | /\ b[chain, "reserve", chain] > 0 114 | /\ ChainSupply(b, chain) = GENESIS_SUPPLY[chain] 115 | /\ \A a \in ACCOUNTS, d \in DENOMS: 116 | \* no tokens in foreign denominations 117 | d /= Native(chain) => b[chain, a, d] = 0 118 | /\ banks = b 119 | 120 | \* Transfer the tokens from on account to another (on the same chain) 121 | LocalTransfer(chain, from, to, denom, amount) == 122 | /\ banks[chain, from, denom] >= amount 123 | /\ from /= to 124 | /\ banks' = [banks EXCEPT 125 | ![chain, from, denom] = banks[chain, from, denom] - amount, 126 | ![chain, to, denom] = banks[chain, to, denom] + amount 127 | ] 128 | 129 | \* A computation on the local chain 130 | LocalStep == 131 | /\ \E chain \in CHAINS, from, to \in ACCOUNTS, 132 | denom \in DENOMS, amount \in Nat: 133 | /\ from /= Escrow 134 | /\ to /= Escrow 135 | /\ LocalTransfer(chain, from, to, denom, amount) 136 | /\ UNCHANGED <> 138 | 139 | \* send a packet over a channel 140 | \* @type: (<>, Str, Str, Str, Str, Int) => Bool; 141 | SendPacket(chan, dir, sender, receiver, denom, amount) == 142 | LET data == [seqno |-> seqno, 143 | sender |-> sender, 144 | receiver |-> receiver, 145 | denom |-> denom, 146 | amount |-> amount] 147 | src == IF dir = "forward" THEN chan[1] ELSE chan[2] 148 | dst == IF dir = "forward" THEN chan[2] ELSE chan[1] 149 | packet == [src |-> src, dest |-> dst, data |-> data] 150 | IN 151 | /\ sentPackets' = sentPackets \union { packet } 152 | /\ seqno' = seqno + 1 153 | /\ UNCHANGED <> 154 | 155 | \* Send a packet to transfer tokens (from the source) 156 | SendPacketFromSource == 157 | \E chan \in CHANNELS, sender, receiver \in ACCOUNTS, 158 | denom \in DENOMS, amount \in Nat: 159 | /\ sender /= Escrow /\ receiver /= Escrow 160 | /\ amount > 0 161 | \* the source direction: escrow source tokens 162 | /\ LocalTransfer(chan[1], sender, Escrow, denom, amount) 163 | /\ SendPacket(chan, "forward", sender, receiver, denom, amount) 164 | 165 | \* Burn `amount` coins in the sender's account 166 | BurnCoins(chain, sender, denom, amount) == 167 | \* do not burn native coins 168 | /\ denom /= Native(chain) 169 | \* do not let to burn more coins than we have 170 | /\ LET newAmount == banks[chain, sender, denom] - amount IN 171 | /\ newAmount >= 0 172 | /\ banks' = [banks EXCEPT ![chain, sender, denom] = newAmount] 173 | 174 | \* Send a packet to return tokens (to the source) 175 | SendPacketToSource == 176 | \E chan \in CHANNELS, sender, receiver \in ACCOUNTS, 177 | denom \in DENOMS, amount \in Nat: 178 | /\ sender /= Escrow /\ receiver /= Escrow 179 | /\ amount > 0 180 | \* in the direction of the source: burn coins 181 | /\ BurnCoins(chan[2], sender, denom, amount) 182 | /\ SendPacket(chan, "backward", sender, receiver, denom, amount) 183 | 184 | \* Produce `amount` coins in the receiver's account (out of thin air!) 185 | MintCoins(chain, receiver, denom, amount) == 186 | \* do not mint native coins 187 | /\ denom /= Native(chain) 188 | /\ banks' = [banks EXCEPT ![chain, receiver, denom] = 189 | banks[chain, receiver, denom] + amount] 190 | 191 | \* Receive a packet on a non-source chain (note that ICS20 does more than that) 192 | ReceivePacketFromSource == 193 | \E packet \in sentPackets: 194 | /\ packet.seqno \notin deliveredNums 195 | /\ packet.seqno \notin dstTimeoutNums 196 | \* In the implementation, we produce a new denomination. 197 | \* Mint coins that are different from native. 198 | /\ LET foreignDenom == MK_DENOM[packet.dest, packet.data.denom] IN 199 | MintCoins(packet.dest, 200 | packet.data.receiver, foreignDenom, packet.data.amount) 201 | /\ deliveredNums' = deliveredNums \union { packet.seqno } 202 | /\ UNCHANGED <> 203 | 204 | \* Receive a packet on the source chain 205 | ReceivePacketOnSource == 206 | \E packet \in sentPackets: 207 | /\ packet.seqno \notin deliveredNums 208 | /\ packet.seqno \notin dstTimeoutNums 209 | \* translate the coin denomination, e.g., by pruning the prefix 210 | /\ LET sourceDenom == UNMK_DENOM[packet.denom] IN 211 | /\ sourceDenom /= "invalid" 212 | /\ LocalTransfer(packet.dst, packet.receiver, Escrow, 213 | sourceDenom, packet.amount) 214 | /\ deliveredNums' = deliveredNums \union { packet.seqno } 215 | /\ UNCHANGED <> 216 | 217 | \* A non-determinstic timeout. 218 | \* Note that the timeout should be registered first on the destination chain. 219 | RegisterTimeout == 220 | \E packet \in sentPackets: 221 | /\ packet.seqno \notin deliveredNums 222 | /\ packet.seqno \notin dstTimeoutNums 223 | /\ dstTimeoutNums' = dstTimeoutNums \union { packet.seqno } 224 | /\ UNCHANGED <> 225 | 226 | \* Observe a timeout on the destination chain and refund the coins on the source. 227 | \* Note that this cannot be simply done by measuring time. 228 | \* We need a confirmation on the destination chain that the timeout has occured. 229 | ApplyTimeout == 230 | \E packet \in sentPackets: 231 | /\ packet.seqno \in dstTimeoutNums 232 | /\ srcTimeoutNums' = srcTimeoutNums \union { packet.seqno } 233 | /\ IF Native(packet.src) = packet.denom 234 | THEN LocalTransfer(packet.src, packet.data.sender, Escrow, 235 | packet.data.denom, packet.data.amount) 236 | ELSE MintCoins(packet.dest, packet.data.receiver, 237 | packet.data.denom, packet.data.amount) 238 | /\ UNCHANGED <> 239 | 240 | 241 | \* Update the world 242 | Next == 243 | \/ LocalStep 244 | \/ SendPacketFromSource 245 | \/ ReceivePacketFromSource 246 | \/ SendPacketToSource 247 | \/ ReceivePacketOnSource 248 | \/ RegisterTimeout 249 | \/ ApplyTimeout 250 | 251 | (************************** PROPERTIES ***************************************) 252 | 253 | \* every bank always has reserves 254 | ReservesInv == 255 | \A chain \in CHAINS: 256 | banks[chain, "reserve", Native(chain)] > 0 257 | 258 | \* no bank account goes negative 259 | NoNegativeAccounts == 260 | \A address \in DOMAIN banks: 261 | banks[address] >= 0 262 | 263 | \* the supply remains constant 264 | ChainSupplyUnchanged == 265 | \A chain \in CHAINS: 266 | LET supply == ChainSupply(banks, chain) IN 267 | supply = GENESIS_SUPPLY[chain] 268 | 269 | \* for each in-fly packet, there is enough money in the escrow account 270 | InFlyPacketIsSecured == 271 | \A p \in sentPackets: 272 | (p.seqno \notin deliveredNums /\ p.seqno \notin srcTimeoutNums) 273 | => 274 | banks[p.src, Escrow, p.data.denom] >= p.data.amount 275 | 276 | \* the supply over all chains remains constant 277 | AllChainsSupplyUnchanged == 278 | AllChainsGenesisSupply = AllChainsAmountsSupply(banks) 279 | 280 | (************* PROPERTIES TO PRODUCE COUNTEREXAMPLES *************************) 281 | 282 | \* This property should produce a counterexample that demonstrates 283 | \* that a foreign denomination can reach a blockchain 284 | NoForeignCoins == 285 | \A chain \in CHAINS, acc \in ACCOUNTS, d \in DENOMS: 286 | d /= Native(chain) => banks[chain, acc, d] = 0 287 | 288 | =============================================================================== 289 | --------------------------------------------------------------------------------