├── .envrc ├── elixir ├── .envrc ├── test │ ├── test_helper.exs │ └── generated_code │ │ ├── traffic_semaphores_test.exs │ │ └── jarros_de_agua_test.exs ├── .formatter.exs ├── default.nix ├── README.md ├── .gitignore ├── mix.exs └── lib │ ├── trace_runner.ex │ ├── mix │ └── tasks │ │ ├── single_semaphore_loop.ex │ │ ├── traffic_semaphores_main_starter.ex │ │ ├── ew_d840_node0_starter.ex │ │ ├── ew_d840_node1_starter.ex │ │ ├── ew_d840_node2_starter.ex │ │ ├── er_c20_bob_starter.ex │ │ ├── er_c20_eve_starter.ex │ │ ├── er_c20_alice_starter.ex │ │ ├── er_c20_blockchain_starter.ex │ │ ├── termination.ex │ │ └── exploit_trace.ex │ ├── oracle_examples │ ├── oracle.ex │ ├── random_oracle.ex │ ├── managers_oracle.ex │ └── trace_checker_oracle.ex │ └── generated_code │ ├── ew_d840_node0.ex │ ├── ew_d840_node1.ex │ ├── ew_d840_node2.ex │ ├── traffic_semaphores_main.ex │ ├── er_c20_blockchain.ex │ ├── er_c20_bob.ex │ ├── er_c20_eve.ex │ ├── er_c20_alice.ex │ ├── traffic_semaphores.ex │ ├── ew_d840.ex │ └── er_c20.ex ├── two_phase_commit_example ├── test │ └── test_helper.exs ├── .formatter.exs ├── .gitignore ├── mix.exs ├── resource_manager.ex ├── README.md └── lib │ ├── managers_oracle.ex │ └── two_phase_commit.ex ├── rules.pdf ├── .hindent.yaml ├── dotToJSON.sh ├── tla_specifications ├── old │ ├── ewd998 │ │ ├── ATD.cfg │ │ ├── MC4_EWD998.cfg │ │ ├── README.md │ │ ├── AsyncTerminationDetection.tla │ │ ├── Functions.tla │ │ └── EWD998.tla │ ├── JarrosDeAgua.cfg │ ├── TwoPhaseCommit │ │ ├── states.png │ │ ├── TwoPhaseCommit.cfg │ │ ├── TCommit.tla │ │ └── TwoPhaseCommit.tla │ ├── die_hard.cfg │ ├── die_hard.tla │ ├── JarrosDeAgua.tla │ └── TransactionCommit.tla ├── ERC20 │ ├── _apalache-out │ │ └── ERC20.tla │ │ │ ├── 2022-08-07T11-21-21_17911601904741265504 │ │ │ ├── run.txt │ │ │ └── detailed.log │ │ │ ├── 2022-08-07T11-21-34_5532659583529748549 │ │ │ ├── run.txt │ │ │ └── detailed.log │ │ │ ├── 2022-08-07T12-20-28_8051551923294457393 │ │ │ ├── run.txt │ │ │ └── detailed.log │ │ │ └── 2022-08-07T12-27-27_7551923235785992484 │ │ │ ├── run.txt │ │ │ └── detailed.log │ ├── ERC20.cfg │ ├── ERC20_typedefs.tla │ ├── MC_ERC20.tla │ ├── config.json │ ├── Apalache.tla │ └── ERC20.tla ├── ewd840 │ ├── states.png │ ├── EWD840.cfg │ ├── README.md │ ├── config.json │ └── trace.out ├── traffic_semaphores │ ├── states.png │ ├── MC.cfg │ ├── MC.tla │ ├── config.json │ ├── TrafficSemaphores.tla │ └── states.json └── traffic_semaphores_with_problem │ ├── states.png │ ├── MC.cfg │ ├── trace.out │ ├── MC.tla │ ├── config.json │ ├── TrafficSemaphores.tla │ ├── states.dot │ └── states.json ├── Makefile ├── package.yaml ├── .gitignore ├── DocHandler.hs ├── config-sample.json ├── LICENSE ├── README.md ├── CodeGenerator.hs ├── default.nix ├── BlackboxTestGenerator.hs ├── Snippets.hs ├── Math.hs ├── stack.yaml ├── ConfigParser.hs ├── Head.hs ├── WhiteboxTestGenerator.hs └── Helpers.hs /.envrc: -------------------------------------------------------------------------------- 1 | use nix; 2 | -------------------------------------------------------------------------------- /elixir/.envrc: -------------------------------------------------------------------------------- 1 | use nix; 2 | -------------------------------------------------------------------------------- /elixir/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /two_phase_commit_example/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /rules.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugarela/tla-transmutation/HEAD/rules.pdf -------------------------------------------------------------------------------- /.hindent.yaml: -------------------------------------------------------------------------------- 1 | indent-size: 2 2 | line-length: 120 3 | force-trailing-newline: true 4 | line-breaks: [":>", ":<|>"] 5 | -------------------------------------------------------------------------------- /dotToJSON.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | dot -Txdot_json $1 | jq '.objects = (.objects | map(. | select(.label!=null)))' > $2 3 | -------------------------------------------------------------------------------- /tla_specifications/old/ewd998/ATD.cfg: -------------------------------------------------------------------------------- 1 | SPECIFICATION Spec 2 | CONSTANT 3 | N = 4 4 | INVARIANTS 5 | TypeOK 6 | Safe 7 | 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: compile 2 | 3 | compile: 4 | ghc BlackboxTestGenerator.hs && ghc WhiteboxTestGenerator.hs && ghc CodeGenerator.hs 5 | -------------------------------------------------------------------------------- /elixir/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/_apalache-out/ERC20.tla/2022-08-07T11-21-21_17911601904741265504/run.txt: -------------------------------------------------------------------------------- 1 | parse --output=ERC20.json ERC20.tla 2 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/_apalache-out/ERC20.tla/2022-08-07T11-21-34_5532659583529748549/run.txt: -------------------------------------------------------------------------------- 1 | parse --output=ERC20.json ERC20.tla 2 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/_apalache-out/ERC20.tla/2022-08-07T12-20-28_8051551923294457393/run.txt: -------------------------------------------------------------------------------- 1 | parse --output=ERC20.json ERC20.tla 2 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/_apalache-out/ERC20.tla/2022-08-07T12-27-27_7551923235785992484/run.txt: -------------------------------------------------------------------------------- 1 | parse --output=ERC20.json ERC20.tla 2 | -------------------------------------------------------------------------------- /tla_specifications/ewd840/states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugarela/tla-transmutation/HEAD/tla_specifications/ewd840/states.png -------------------------------------------------------------------------------- /tla_specifications/ewd840/EWD840.cfg: -------------------------------------------------------------------------------- 1 | CONSTANT 2 | N = 3 3 | 4 | INVARIANT 5 | full 6 | 7 | INIT 8 | Init 9 | 10 | NEXT 11 | Next 12 | -------------------------------------------------------------------------------- /two_phase_commit_example/.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /tla_specifications/old/JarrosDeAgua.cfg: -------------------------------------------------------------------------------- 1 | CONSTANT 2 | 3 | INVARIANTS 4 | Inv 5 | 6 | PROPERTY 7 | 8 | INIT 9 | Init 10 | 11 | NEXT 12 | Next 13 | -------------------------------------------------------------------------------- /tla_specifications/old/TwoPhaseCommit/states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugarela/tla-transmutation/HEAD/tla_specifications/old/TwoPhaseCommit/states.png -------------------------------------------------------------------------------- /tla_specifications/old/die_hard.cfg: -------------------------------------------------------------------------------- 1 | CONSTANT 2 | 3 | INVARIANTS 4 | TypeOK 5 | 6 | PROPERTY 7 | 8 | INIT 9 | Init 10 | 11 | NEXT 12 | Next 13 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores/states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugarela/tla-transmutation/HEAD/tla_specifications/traffic_semaphores/states.png -------------------------------------------------------------------------------- /tla_specifications/ewd840/README.md: -------------------------------------------------------------------------------- 1 | # EWD840 2 | 3 | Examples imported from https://github.com/informalsystems/apalache-bench/tree/trunk/performance/ewd840 4 | 5 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores/MC.cfg: -------------------------------------------------------------------------------- 1 | CONSTANT 2 | SEMAPHORES = {0, 1} 3 | 4 | INVARIANTS 5 | 6 | PROPERTY 7 | full 8 | 9 | INIT 10 | Init 11 | 12 | NEXT 13 | Next 14 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores_with_problem/states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bugarela/tla-transmutation/HEAD/tla_specifications/traffic_semaphores_with_problem/states.png -------------------------------------------------------------------------------- /tla_specifications/ERC20/ERC20.cfg: -------------------------------------------------------------------------------- 1 | CONSTANT 2 | ADDR = {"Alice_OF_ADDR", "Bob_OF_ADDR"} 3 | AMOUNTS = {0, 100} 4 | 5 | INVARIANT 6 | Full 7 | 8 | INIT 9 | Init 10 | 11 | NEXT 12 | Next 13 | -------------------------------------------------------------------------------- /tla_specifications/old/TwoPhaseCommit/TwoPhaseCommit.cfg: -------------------------------------------------------------------------------- 1 | CONSTANT 2 | RM = {"r1", "r2"} 3 | 4 | INVARIANTS 5 | TPTypeOK 6 | 7 | PROPERTY 8 | 9 | INIT 10 | TPInit 11 | 12 | NEXT 13 | TPNext 14 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores_with_problem/MC.cfg: -------------------------------------------------------------------------------- 1 | CONSTANT 2 | SEMAPHORES = {0, 1} 3 | 4 | INVARIANTS 5 | 6 | PROPERTY 7 | never_stuck 8 | 9 | INIT 10 | Init 11 | 12 | NEXT 13 | Next 14 | -------------------------------------------------------------------------------- /tla_specifications/old/ewd998/MC4_EWD998.cfg: -------------------------------------------------------------------------------- 1 | CONSTANTS 2 | N = 4 3 | 4 | CONSTRAINTS 5 | StateConstraint 6 | 7 | SPECIFICATION 8 | Spec 9 | 10 | INVARIANT 11 | Inv 12 | TypeOK 13 | 14 | CHECK_DEADLOCK 15 | FALSE -------------------------------------------------------------------------------- /elixir/default.nix: -------------------------------------------------------------------------------- 1 | { nixpkgs ? import {} }: 2 | let 3 | inherit (nixpkgs) pkgs; 4 | 5 | nixPackages = [ 6 | pkgs.elixir 7 | ]; 8 | in 9 | pkgs.stdenv.mkDerivation { 10 | name = "env"; 11 | buildInputs = nixPackages; 12 | } 13 | -------------------------------------------------------------------------------- /tla_specifications/old/ewd998/README.md: -------------------------------------------------------------------------------- 1 | # EWD998 2 | 3 | Examples imported from https://github.com/informalsystems/apalache-bench/tree/trunk/performance/ewd998 4 | 5 | JSON files are parsed with [Apalache](https://github.com/informalsystems/apalache): 6 | 7 | ``` sh 8 | apalache-mc parse --output=EWD998.json EWD998.tla 9 | ``` 10 | -------------------------------------------------------------------------------- /elixir/README.md: -------------------------------------------------------------------------------- 1 | # Elixir Environment 2 | 3 | This is a bootstrap mix project where the generated code can be ran, with some oracle examples to help simulate interaction with multiple processes. 4 | 5 | The default oracle will read input each time it needs help choosing the next action. If you change the Oracle spawned by the generated code to the `RandomOracle`, the choices will be done randomly with a 1 second interval so the output is human readable. 6 | 7 | Run with `mix` 8 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: tla-transmutation 2 | version: 0.0.1 3 | synopsis: Elixir code generation from TLA+ specifications 4 | description: See README at 5 | maintainer: Gabriela Mafra 6 | github: gabrielamafra/tla-trasmutation 7 | category: Development 8 | 9 | ghc-options: -Wall 10 | 11 | dependencies: 12 | - base >= 4.9 && < 5 13 | - split 14 | - parsec 15 | - casing 16 | - extra 17 | - mtl 18 | - aeson 19 | 20 | executable: 21 | main: Main.hs 22 | source-dirs: . 23 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores_with_problem/trace.out: -------------------------------------------------------------------------------- 1 | State 1: 2 | /\ colors = (0 :> "red" @@ 1 :> "red") 3 | /\ next_to_open = 0 4 | 5 | State 2: 6 | /\ colors = (0 :> "red" @@ 1 :> "green") 7 | /\ next_to_open = 0 8 | 9 | State 3: 10 | /\ colors = (0 :> "red" @@ 1 :> "yellow") 11 | /\ next_to_open = 0 12 | 13 | Back to state 1: 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/haskell 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=haskell 3 | 4 | ### Haskell ### 5 | dist 6 | dist-* 7 | cabal-dev 8 | *.o 9 | *.hi 10 | *.hie 11 | *.chi 12 | *.chs.h 13 | *.dyn_o 14 | *.dyn_hi 15 | .hpc 16 | .hsenv 17 | .cabal-sandbox/ 18 | cabal.sandbox.config 19 | *.prof 20 | *.aux 21 | *.hp 22 | *.eventlog 23 | .stack-work/ 24 | cabal.project.local 25 | cabal.project.local~ 26 | .HTF/ 27 | .ghc.environment.* 28 | 29 | # End of https://www.toptal.com/developers/gitignore/api/haskell 30 | -------------------------------------------------------------------------------- /DocHandler.hs: -------------------------------------------------------------------------------- 1 | module DocHandler where 2 | 3 | import Data.List 4 | 5 | moduleDoc [] = [] 6 | moduleDoc doc = "@moduledoc \"\"\"\n" ++ (intercalate "\n" (map cleanTrailing doc)) ++ "\n\"\"\"\n" 7 | 8 | funDoc [] = [] 9 | funDoc doc = "@doc \"\"\"\n" ++ (intercalate "\n" (map cleanTrailing doc)) ++ "\n\"\"\"\n" 10 | 11 | comment doc = intercalate "\n" (map (((++) "# ") . cleanTrailing) doc) ++ "\n" 12 | 13 | allSpaces s = dropWhile (== ' ') s == [] 14 | 15 | cleanTrailing [] = [] 16 | cleanTrailing (x:xs) = 17 | if allSpaces xs 18 | then [x] 19 | else x : (cleanTrailing xs) 20 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores/MC.tla: -------------------------------------------------------------------------------- 1 | ------------------- MODULE MC ----------------------- 2 | EXTENDS TrafficSemaphores, TLC 3 | 4 | no_collision == Cardinality({s \in SEMAPHORES : colors[s] = "green"}) <= 1 5 | 6 | never_stuck == WF_<>(Next) => \A s \in SEMAPHORES : <>(colors[s] = "green") 7 | 8 | property == WF_<>(Next) => \A s \in SEMAPHORES : (colors[s] = "yellow" ~> colors[s] = "red") 9 | 10 | full == TRUE 11 | 12 | ------------------------------------------------------------ 13 | ============================================================ 14 | \* Modification History 15 | \* Created Tue May 25 15:11:17 2021 by Gabriela Moreira Mafra 16 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores_with_problem/MC.tla: -------------------------------------------------------------------------------- 1 | ------------------- MODULE MC ----------------------- 2 | EXTENDS TrafficSemaphores, TLC 3 | 4 | no_collision == Cardinality({s \in SEMAPHORES : colors[s] = "green"}) <= 1 5 | 6 | never_stuck == WF_<>(Next) => \A s \in SEMAPHORES : <>(colors[s] = "green") 7 | 8 | property == WF_<>(Next) => \A s \in SEMAPHORES : (colors[s] = "yellow" ~> colors[s] = "red") 9 | 10 | full == TRUE 11 | 12 | ------------------------------------------------------------ 13 | ============================================================ 14 | \* Modification History 15 | \* Created Tue May 25 15:11:17 2021 by Gabriela Moreira Mafra 16 | -------------------------------------------------------------------------------- /elixir/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | elixir-*.tar 24 | 25 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/_apalache-out/ERC20.tla/2022-08-07T11-21-34_5532659583529748549/detailed.log: -------------------------------------------------------------------------------- 1 | 11:21:34.854 [main] INFO a.f.a.t.Tool$ - # APALACHE version: v0.20.3-1485-gb46aa9e76 | build: v0.20.3-1485-gb46aa9e76 2 | 11:21:34.967 [main] INFO a.f.a.t.Tool$ - Parse ERC20.tla 3 | 11:21:34.970 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #0: SanyParser 4 | 11:21:35.331 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #0: SanyParser [OK] 5 | 11:21:35.331 [main] INFO a.f.a.t.Tool$ - Parsed successfully 6 | Root module: ERC20 with 21 declarations. 7 | 11:21:35.331 [main] INFO a.f.a.t.Tool$ - It took me 0 days 0 hours 0 min 0 sec 8 | 11:21:35.332 [main] INFO a.f.a.t.Tool$ - Total time: 0.474 sec 9 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/_apalache-out/ERC20.tla/2022-08-07T12-20-28_8051551923294457393/detailed.log: -------------------------------------------------------------------------------- 1 | 12:20:28.568 [main] INFO a.f.a.t.Tool$ - # APALACHE version: v0.20.3-1485-gb46aa9e76 | build: v0.20.3-1485-gb46aa9e76 2 | 12:20:28.683 [main] INFO a.f.a.t.Tool$ - Parse ERC20.tla 3 | 12:20:28.686 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #0: SanyParser 4 | 12:20:28.992 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #0: SanyParser [OK] 5 | 12:20:28.992 [main] INFO a.f.a.t.Tool$ - Parsed successfully 6 | Root module: ERC20 with 16 declarations. 7 | 12:20:28.993 [main] INFO a.f.a.t.Tool$ - It took me 0 days 0 hours 0 min 0 sec 8 | 12:20:28.993 [main] INFO a.f.a.t.Tool$ - Total time: 0.421 sec 9 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/_apalache-out/ERC20.tla/2022-08-07T12-27-27_7551923235785992484/detailed.log: -------------------------------------------------------------------------------- 1 | 12:27:27.790 [main] INFO a.f.a.t.Tool$ - # APALACHE version: v0.20.3-1485-gb46aa9e76 | build: v0.20.3-1485-gb46aa9e76 2 | 12:27:27.904 [main] INFO a.f.a.t.Tool$ - Parse ERC20.tla 3 | 12:27:27.907 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #0: SanyParser 4 | 12:27:28.209 [main] DEBUG a.f.a.i.p.PassChainExecutor - PASS #0: SanyParser [OK] 5 | 12:27:28.209 [main] INFO a.f.a.t.Tool$ - Parsed successfully 6 | Root module: ERC20 with 16 declarations. 7 | 12:27:28.210 [main] INFO a.f.a.t.Tool$ - It took me 0 days 0 hours 0 min 0 sec 8 | 12:27:28.210 [main] INFO a.f.a.t.Tool$ - Total time: 0.417 sec 9 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "processes": [ 3 | { 4 | "process_id": "main", 5 | "actions": [ 6 | "\\E s \\in SEMAPHORES : TurnGreen(s) \\/ TurnYellow(s) \\/ TurnRed(s)" 7 | ] 8 | } 9 | ], 10 | "shared_variables": ["colors", "next_to_open"], 11 | "constants": [ 12 | { 13 | "name": "SEMAPHORES", 14 | "value": "{0, 1}" 15 | } 16 | ], 17 | "init": "Init", 18 | "next": "Next", 19 | "module_name": "TrafficSemaphores", 20 | "input_format": "json", 21 | "input_file": "TrafficSemaphores.json", 22 | "state_graph": "states.json", 23 | "blackbox_tests": [], 24 | "destination_folder": "../../elixir" 25 | } 26 | -------------------------------------------------------------------------------- /elixir/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Elixir.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :elixir, 7 | version: "0.1.0", 8 | elixir: "~> 1.9", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger] 18 | ] 19 | end 20 | 21 | # Run "mix help deps" to learn about dependencies. 22 | defp deps do 23 | [ 24 | # {:dep_from_hexpm, "~> 0.3.0"}, 25 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/_apalache-out/ERC20.tla/2022-08-07T11-21-21_17911601904741265504/detailed.log: -------------------------------------------------------------------------------- 1 | 11:21:22.011 [main] INFO a.f.a.t.Tool$ - # APALACHE version: v0.20.3-1485-gb46aa9e76 | build: v0.20.3-1485-gb46aa9e76 2 | 11:21:22.192 [main] INFO a.f.a.t.Tool$ - Parse ERC20.tla 3 | 11:21:22.195 [main] INFO a.f.a.i.p.PassChainExecutor - PASS #0: SanyParser 4 | 11:21:22.334 [main] ERROR a.f.a.t.Tool$ - Error by TLA+ parser: *** Abort messages: 1 5 | 6 | Unknown location 7 | 8 | Cannot find source file for module ERC20_typedefs imported in module ERC20. 9 | 10 | 11 | 12 | 11:21:22.335 [main] INFO a.f.a.t.Tool$ - It took me 0 days 0 hours 0 min 0 sec 13 | 11:21:22.335 [main] INFO a.f.a.t.Tool$ - Total time: 0.319 sec 14 | -------------------------------------------------------------------------------- /two_phase_commit_example/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | two_phase_commit_example-*.tar 24 | 25 | -------------------------------------------------------------------------------- /two_phase_commit_example/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule TwoPhaseCommitExample.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :two_phase_commit_example, 7 | version: "0.1.0", 8 | elixir: "~> 1.9", 9 | start_permanent: Mix.env() == :prod, 10 | deps: deps() 11 | ] 12 | end 13 | 14 | # Run "mix help compile.app" to learn about applications. 15 | def application do 16 | [ 17 | extra_applications: [:logger] 18 | ] 19 | end 20 | 21 | # Run "mix help deps" to learn about dependencies. 22 | defp deps do 23 | [ 24 | # {:dep_from_hexpm, "~> 0.3.0"}, 25 | # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} 26 | ] 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores_with_problem/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "processes": [ 3 | { 4 | "process_id": "main", 5 | "actions": [ 6 | "\\E s \\in SEMAPHORES : TurnGreen(s) \\/ TurnYellow(s) \\/ TurnRed(s)" 7 | ] 8 | } 9 | ], 10 | "shared_variables": ["colors", "next_to_open"], 11 | "constants": [ 12 | { 13 | "name": "SEMAPHORES", 14 | "value": "{0, 1}" 15 | } 16 | ], 17 | "init": "Init", 18 | "next": "Next", 19 | "module_name": "TrafficSemaphores", 20 | "input_format": "json", 21 | "input_file": "TrafficSemaphores.json", 22 | "state_graph": "states.json", 23 | "blackbox_tests": [{"name": "SingleSemaphoreLoop", "trace": "trace.out"}], 24 | "destination_folder": "../../elixir" 25 | } 26 | -------------------------------------------------------------------------------- /two_phase_commit_example/resource_manager.ex: -------------------------------------------------------------------------------- 1 | defmodule ResourceManager do 2 | def start(id) do 3 | Node.connect(:oracle@localhost) 4 | :global.register_name(id, self()) 5 | 6 | spawn(ResourceManager, :read, []) 7 | listen(:undefined) 8 | end 9 | 10 | def listen(input) do 11 | receive do 12 | {p, as} -> show_options(as) 13 | end 14 | 15 | listen(input) 16 | end 17 | 18 | def read do 19 | {i, _} = Integer.parse(IO.gets("Escolha Gerenciador de Recursos: ")) 20 | 21 | send :global.whereis_name("oracle"), {:choice, i} 22 | 23 | read() 24 | end 25 | 26 | def show_options(as) do 27 | IEx.Helpers.clear 28 | IO.puts Enum.join(as, "\n") 29 | IO.puts "" 30 | end 31 | end 32 | 33 | [arg1] = System.argv 34 | ResourceManager.start(arg1) 35 | -------------------------------------------------------------------------------- /elixir/lib/trace_runner.ex: -------------------------------------------------------------------------------- 1 | defmodule TraceRunner do 2 | def check(trace, index, current_state, next_state) do 3 | Enum.at(trace, index) == current_state && Enum.at(trace, index + 1) == next_state 4 | end 5 | 6 | def choose(trace, index, states) do 7 | # IO.inspect(states) 8 | # IO.inspect(Enum.map(actions, fn a -> a[:state][:pending_transactions] end), limit: 100) 9 | r = Enum.find_index(states, fn s -> s == Enum.at(trace, index + 1) end) 10 | 11 | if !r do 12 | IO.puts("Towards state: #{index + 1}") 13 | IO.inspect(Enum.at(trace, index + 1)) 14 | # IO.inspect(Enum.map(states, fn a -> a[:pending_transactions] end), limit: 1000) 15 | end 16 | 17 | # if !r do 18 | # IO.inspect(states) 19 | # {i, _} = Integer.parse(IO.gets("Next Action: ")) 20 | # end 21 | 22 | r 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/single_semaphore_loop.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.SingleSemaphoreLoop do 2 | @moduledoc "Runs blackblox testing using the oracle" 3 | @shortdoc "Runs trace checking for a witness" 4 | use Mix.Task 5 | 6 | @impl Mix.Task 7 | def run(_) do 8 | trace = [ 9 | %{ 10 | colors: %{ 0 => "red", 1 => "red" }, 11 | next_to_open: 0 12 | }, 13 | %{ 14 | colors: %{ 0 => "red", 1 => "green" }, 15 | next_to_open: 0 16 | }, 17 | %{ 18 | colors: %{ 0 => "red", 1 => "yellow" }, 19 | next_to_open: 0 20 | }, 21 | %{ 22 | colors: %{ 0 => "red", 1 => "red" }, 23 | next_to_open: 0 24 | } 25 | ] 26 | 27 | modules = [ 28 | TrafficSemaphores_main 29 | ] 30 | pids = Enum.map(modules, fn m -> spawn(m, :main, [self(), Enum.at(trace, 0), 0]) end) 31 | TraceCheckerOracle.start(trace, 0, nil, pids) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /elixir/lib/oracle_examples/oracle.ex: -------------------------------------------------------------------------------- 1 | defmodule Oracle do 2 | def start do 3 | :global.register_name("oracle", self()) 4 | 5 | spawn(Oracle, :read, []) 6 | listen() 7 | end 8 | 9 | def listen do 10 | IO.puts("Oracle at [#{inspect(self())}] is listening") 11 | 12 | receive do 13 | {:choose, p, _, as} -> send(p, {:ok, input_option(as)}) 14 | end 15 | 16 | listen() 17 | end 18 | 19 | def input_option(actions) do 20 | enumerated_actions = actions |> Enum.with_index() |> Enum.map(fn {x, i} -> "#{i} => #{x}" end) 21 | IO.puts(inspect(enumerated_actions)) 22 | 23 | receive do 24 | {:choice, i} -> i 25 | end 26 | end 27 | 28 | def read do 29 | {i, _} = Integer.parse(IO.gets("Next Action: ")) 30 | 31 | send(:global.whereis_name("oracle"), {:choice, i}) 32 | 33 | read() 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/traffic_semaphores_main_starter.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.TrafficSemaphoresMainStarter do 2 | @moduledoc "Printed when the user requests `mix help echo`" 3 | @shortdoc "Echoes arguments" 4 | use Mix.Task 5 | import TrafficSemaphores 6 | import TrafficSemaphores_main 7 | 8 | @impl Mix.Task 9 | def run(args) do 10 | variables = %{} 11 | initial_state = %{ 12 | colors: Map.new(TrafficSemaphores.semaphores, fn(s) -> {s, "red"} end), 13 | next_to_open: 0 14 | } 15 | oracle_host = String.to_atom(Enum.at(args, 0)) 16 | Node.connect(oracle_host) 17 | oracle_pid = find_oracle() 18 | IO.puts(inspect(oracle_pid)) 19 | main(oracle_pid, initial_state, 0) 20 | end 21 | 22 | def find_oracle() do 23 | o = :global.whereis_name("oracle") 24 | if o == :undefined do 25 | find_oracle() 26 | else 27 | o 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/ew_d840_node0_starter.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.EwD840Node0Starter do 2 | @moduledoc "Printed when the user requests `mix help echo`" 3 | @shortdoc "Echoes arguments" 4 | use Mix.Task 5 | import EWD840 6 | import EWD840_node0 7 | 8 | @impl Mix.Task 9 | def run(args) do 10 | variables = %{} 11 | initial_state = %{ 12 | active: Map.new(nodes(variables), fn(n) -> {n, true} end), 13 | color: Map.new(nodes(variables), fn(n) -> {n, "white"} end), 14 | tpos: 0, 15 | tcolor: "black" 16 | } 17 | oracle_host = String.to_atom(Enum.at(args, 0)) 18 | Node.connect(oracle_host) 19 | oracle_pid = find_oracle() 20 | IO.puts(inspect(oracle_pid)) 21 | main(oracle_pid, initial_state, 0) 22 | end 23 | 24 | def find_oracle() do 25 | o = :global.whereis_name("oracle") 26 | if o == :undefined do 27 | find_oracle() 28 | else 29 | o 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/ew_d840_node1_starter.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.EwD840Node1Starter do 2 | @moduledoc "Printed when the user requests `mix help echo`" 3 | @shortdoc "Echoes arguments" 4 | use Mix.Task 5 | import EWD840 6 | import EWD840_node1 7 | 8 | @impl Mix.Task 9 | def run(args) do 10 | variables = %{} 11 | initial_state = %{ 12 | active: Map.new(nodes(variables), fn(n) -> {n, true} end), 13 | color: Map.new(nodes(variables), fn(n) -> {n, "white"} end), 14 | tpos: 0, 15 | tcolor: "black" 16 | } 17 | oracle_host = String.to_atom(Enum.at(args, 0)) 18 | Node.connect(oracle_host) 19 | oracle_pid = find_oracle() 20 | IO.puts(inspect(oracle_pid)) 21 | main(oracle_pid, initial_state, 0) 22 | end 23 | 24 | def find_oracle() do 25 | o = :global.whereis_name("oracle") 26 | if o == :undefined do 27 | find_oracle() 28 | else 29 | o 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/ew_d840_node2_starter.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.EwD840Node2Starter do 2 | @moduledoc "Printed when the user requests `mix help echo`" 3 | @shortdoc "Echoes arguments" 4 | use Mix.Task 5 | import EWD840 6 | import EWD840_node2 7 | 8 | @impl Mix.Task 9 | def run(args) do 10 | variables = %{} 11 | initial_state = %{ 12 | active: Map.new(nodes(variables), fn(n) -> {n, true} end), 13 | color: Map.new(nodes(variables), fn(n) -> {n, "white"} end), 14 | tpos: 0, 15 | tcolor: "black" 16 | } 17 | oracle_host = String.to_atom(Enum.at(args, 0)) 18 | Node.connect(oracle_host) 19 | oracle_pid = find_oracle() 20 | IO.puts(inspect(oracle_pid)) 21 | main(oracle_pid, initial_state, 0) 22 | end 23 | 24 | def find_oracle() do 25 | o = :global.whereis_name("oracle") 26 | if o == :undefined do 27 | find_oracle() 28 | else 29 | o 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /two_phase_commit_example/README.md: -------------------------------------------------------------------------------- 1 | # Two Phase Commit Protocol Example 2 | 3 | Here's a more realistic and complete example, with the generated code for the `TwoPhaseCommit.tla` specification. It includes an special oracle that works as a transaction manager, and a code for simulating some resource managers. 4 | 5 | The intention here is to run each manager on a separate terminal emulator (or even machine!). First run the model with the oracle as a transaction manager: 6 | ```sh 7 | iex --sname "oracle@localhost" --cookie auth -S mix 8 | ``` 9 | 10 | Then start some managers, each on its own window: 11 | ```sh 12 | iex --sname "manager1@localhost" --cookie auth resource_manager.ex "r1" 13 | iex --sname "manager2@localhost" --cookie auth resource_manager.ex "r2" 14 | ``` 15 | 16 | When everything is up and running, hit `Enter` on the first terminal prompt and start playing around choosing actions on any prompt, the order doesn't matter - that's the whole point of TLA+ :D 17 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/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 | A transaction (a la discriminated union but all fields are packed together): 8 | @typeAlias: TX = [ 9 | tag: Str, 10 | id: Int, 11 | fail: Bool, 12 | sender: ADDR, 13 | spender: ADDR, 14 | fromAddr: ADDR, 15 | toAddr: ADDR, 16 | value: Int 17 | ]; 18 | 19 | A state of the state machine: 20 | @typeAlias: STATE = [ 21 | balanceOf: ADDR -> Int, 22 | allowance: <> -> Int, 23 | pendingTransactions: Set(TX), 24 | lastTx: TX, 25 | nextTxId: Int 26 | ]; 27 | 28 | Below is a dummy definition to introduce the above type aliases. 29 | *) 30 | ERC20_typedefs == TRUE 31 | =============================================================================== 32 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/er_c20_bob_starter.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.ErC20BobStarter do 2 | @moduledoc "Printed when the user requests `mix help echo`" 3 | @shortdoc "Echoes arguments" 4 | use Mix.Task 5 | import ERC20 6 | import ERC20_bob 7 | 8 | @impl Mix.Task 9 | def run(args) do 10 | variables = %{} 11 | initial_state = %{ 12 | balance_of: Map.new(ERC20.addr, fn(a) -> {a, 100} end), 13 | allowance: Map.new((for x <- ERC20.addr, y <- ERC20.addr, do: {x, y}), fn(pair) -> {pair, 0} end), 14 | pending_transactions: MapSet.new([]), 15 | next_tx_id: 0, 16 | last_tx: %{ "id" => 0, "tag" => "None", "fail" => false } 17 | } 18 | oracle_host = String.to_atom(Enum.at(args, 0)) 19 | Node.connect(oracle_host) 20 | oracle_pid = find_oracle() 21 | IO.puts(inspect(oracle_pid)) 22 | main(oracle_pid, initial_state, 0) 23 | end 24 | 25 | def find_oracle() do 26 | o = :global.whereis_name("oracle") 27 | if o == :undefined do 28 | find_oracle() 29 | else 30 | o 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/er_c20_eve_starter.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.ErC20EveStarter do 2 | @moduledoc "Printed when the user requests `mix help echo`" 3 | @shortdoc "Echoes arguments" 4 | use Mix.Task 5 | import ERC20 6 | import ERC20_eve 7 | 8 | @impl Mix.Task 9 | def run(args) do 10 | variables = %{} 11 | initial_state = %{ 12 | balance_of: Map.new(ERC20.addr, fn(a) -> {a, 100} end), 13 | allowance: Map.new((for x <- ERC20.addr, y <- ERC20.addr, do: {x, y}), fn(pair) -> {pair, 0} end), 14 | pending_transactions: MapSet.new([]), 15 | next_tx_id: 0, 16 | last_tx: %{ "id" => 0, "tag" => "None", "fail" => false } 17 | } 18 | oracle_host = String.to_atom(Enum.at(args, 0)) 19 | Node.connect(oracle_host) 20 | oracle_pid = find_oracle() 21 | IO.puts(inspect(oracle_pid)) 22 | main(oracle_pid, initial_state, 0) 23 | end 24 | 25 | def find_oracle() do 26 | o = :global.whereis_name("oracle") 27 | if o == :undefined do 28 | find_oracle() 29 | else 30 | o 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/er_c20_alice_starter.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.ErC20AliceStarter do 2 | @moduledoc "Printed when the user requests `mix help echo`" 3 | @shortdoc "Echoes arguments" 4 | use Mix.Task 5 | import ERC20 6 | import ERC20_alice 7 | 8 | @impl Mix.Task 9 | def run(args) do 10 | variables = %{} 11 | initial_state = %{ 12 | balance_of: Map.new(ERC20.addr, fn(a) -> {a, 100} end), 13 | allowance: Map.new((for x <- ERC20.addr, y <- ERC20.addr, do: {x, y}), fn(pair) -> {pair, 0} end), 14 | pending_transactions: MapSet.new([]), 15 | next_tx_id: 0, 16 | last_tx: %{ "id" => 0, "tag" => "None", "fail" => false } 17 | } 18 | oracle_host = String.to_atom(Enum.at(args, 0)) 19 | Node.connect(oracle_host) 20 | oracle_pid = find_oracle() 21 | IO.puts(inspect(oracle_pid)) 22 | main(oracle_pid, initial_state, 0) 23 | end 24 | 25 | def find_oracle() do 26 | o = :global.whereis_name("oracle") 27 | if o == :undefined do 28 | find_oracle() 29 | else 30 | o 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /elixir/lib/oracle_examples/random_oracle.ex: -------------------------------------------------------------------------------- 1 | defmodule RandomOracle do 2 | def start(current_variables, step, lock) do 3 | ref = Process.monitor(if lock != nil, do: lock, else: self()) 4 | IO.puts("RandomOracle at #{inspect(self())} is listening") 5 | 6 | receive do 7 | {:lock, p} -> IO.inspect(lock); if lock != nil, do: send(p, {:already_locked, %{}}), else: send(p, {:lock_acquired, current_variables}); start(current_variables, step, p) 8 | {:choose, p, as} -> send(p, {:ok, random_choice(as)}); start(current_variables, step, lock) 9 | {:notify, _, _, variables} -> IO.puts("notified"); IO.inspect(variables); start(variables, step + 1, nil) 10 | {:DOWN, ^ref, _, _, _} -> IO.puts "Process #{inspect(ref)} is down"; start(current_variables, step, nil) 11 | end 12 | end 13 | 14 | def random_choice(as) do 15 | Process.sleep(500) 16 | n = length(as) - 1 17 | choice = Enum.random(0..n) 18 | IO.puts("From #{inspect(as)}, oracle chose #{inspect(Enum.at(as, choice))}\n") 19 | 20 | choice 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/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 | ADDR == { "Alice_OF_ADDR", "Bob_OF_ADDR", "Eve_OF_ADDR" } 9 | 10 | \* Apalache can draw constants from the set of all integers 11 | AMOUNTS == Int 12 | 13 | VARIABLES 14 | \* @type: ADDR -> Int; 15 | balanceOf, 16 | \* @type: <> -> Int; 17 | allowance, 18 | \* @type: Set(TX); 19 | pendingTransactions, 20 | \* Last executed transaction. 21 | \* @type: TX; 22 | lastTx, 23 | \* @type: Int; 24 | nextTxId 25 | 26 | \* instantiate the spec with ADDR, balances, allowances, and pendingTxs 27 | INSTANCE ERC20 28 | 29 | =============================================================================== 30 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/er_c20_blockchain_starter.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.ErC20BlockchainStarter do 2 | @moduledoc "Printed when the user requests `mix help echo`" 3 | @shortdoc "Echoes arguments" 4 | use Mix.Task 5 | import ERC20 6 | import ERC20_blockchain 7 | 8 | @impl Mix.Task 9 | def run(args) do 10 | variables = %{} 11 | initial_state = %{ 12 | balance_of: Map.new(ERC20.addr, fn(a) -> {a, 100} end), 13 | allowance: Map.new((for x <- ERC20.addr, y <- ERC20.addr, do: {x, y}), fn(pair) -> {pair, 0} end), 14 | pending_transactions: MapSet.new([]), 15 | next_tx_id: 0, 16 | last_tx: %{ "id" => 0, "tag" => "None", "fail" => false } 17 | } 18 | oracle_host = String.to_atom(Enum.at(args, 0)) 19 | Node.connect(oracle_host) 20 | oracle_pid = find_oracle() 21 | IO.puts(inspect(oracle_pid)) 22 | main(oracle_pid, initial_state, 0) 23 | end 24 | 25 | def find_oracle() do 26 | o = :global.whereis_name("oracle") 27 | if o == :undefined do 28 | find_oracle() 29 | else 30 | o 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /tla_specifications/old/die_hard.tla: -------------------------------------------------------------------------------- 1 | EXTENDS Integers 2 | 3 | VARIABLES small, big 4 | 5 | TypeOK == /\ small \in 0..3 6 | /\ big \in 0..5 7 | 8 | Init == /\ big = 0 9 | /\ small = 0 10 | 11 | FillSmall == /\ small' = 3 12 | /\ big' = big 13 | 14 | FillBig == /\ big' = 5 15 | /\ small' = small 16 | 17 | EmptySmall == /\ small' = 0 18 | /\ big' = big 19 | 20 | EmptyBig == /\ big' = 0 21 | /\ small' = small 22 | 23 | SmallToBig == IF big + small =< 5 24 | THEN /\ big' = big + small 25 | /\ small' = 0 26 | ELSE /\ big' = 5 27 | /\ small' = small - (5 - big) 28 | 29 | BigToSmall == IF big + small =< 3 30 | THEN /\ big' = 0 31 | /\ small' = big + small 32 | ELSE /\ big' = small - (3 - big) 33 | /\ small' = 3 34 | 35 | Next == \/ FillSmall 36 | \/ FillBig 37 | \/ EmptySmall 38 | \/ EmptyBig 39 | \/ SmallToBig 40 | \/ BigToSmall 41 | -------------------------------------------------------------------------------- /config-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "processes": [ 3 | { 4 | "process_id": "node0", 5 | "actions": [ 6 | "InitiateProbe()", 7 | "\\E i \\in {1, 2} : SendMsg(0, i)", 8 | "Deactivate(0)" 9 | ] 10 | }, 11 | { 12 | "process_id": "node1", 13 | "actions": [ 14 | "PassToken(1)", 15 | "\\E i \\in {0, 2} : SendMsg(1, i)", 16 | "Deactivate(1)" 17 | ] 18 | }, 19 | { 20 | "process_id": "node2", 21 | "actions": [ 22 | "PassToken(2)", 23 | "\\E i \\in {0, 1} : SendMsg(2, i)", 24 | "Deactivate(2)" 25 | ] 26 | } 27 | ], 28 | "shared_variables": ["tcolor", "tpos", "active"], 29 | "constants": [ 30 | { 31 | "name": "N", 32 | "value": "3" 33 | } 34 | ], 35 | "init": "Init", 36 | "next": "Next", 37 | "module_name": "EWD840", 38 | "input_format": "json", 39 | "input_file": "EWD840.json", 40 | "state_graph": "states.json", 41 | "blackbox_tests": [{"name": "Termination", "trace": "trace.out"}], 42 | "destination_folder": "../../elixir" 43 | } 44 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores_with_problem/TrafficSemaphores.tla: -------------------------------------------------------------------------------- 1 | ------------------- MODULE TrafficSemaphores ----------------------- 2 | EXTENDS Integers, FiniteSets 3 | VARIABLE colors, next_to_open 4 | CONSTANT SEMAPHORES 5 | 6 | TurnGreen(s) == /\ \A s2 \in SEMAPHORES : colors[s2] = "red" 7 | /\colors' = [colors EXCEPT ![s] = "green"] 8 | /\UNCHANGED << next_to_open >> 9 | 10 | TurnYellow(s) == /\colors[s] = "green" 11 | /\colors' = [colors EXCEPT ![s] = "yellow"] 12 | /\UNCHANGED << next_to_open >> 13 | 14 | TurnRed(s) == /\colors[s] = "yellow" 15 | /\colors' = [colors EXCEPT ![s] = "red"] 16 | /\UNCHANGED << next_to_open >> 17 | 18 | Init == colors = [s \in SEMAPHORES |-> "red"] /\next_to_open = 0 19 | 20 | Next == \E s \in SEMAPHORES : TurnGreen(s) \/ TurnYellow(s) \/ TurnRed(s) 21 | ------------------------------------------------------------ 22 | ============================================================ 23 | \* Modification History 24 | \* Created Tue May 25 14:59:50 2021 by Gabriela Moreira Mafra 25 | -------------------------------------------------------------------------------- /tla_specifications/ewd840/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "processes": [ 3 | { 4 | "process_id": "node0", 5 | "actions": [ 6 | "InitiateProbe()", 7 | "\\E i \\in {1, 2} : SendMsg(0, i)", 8 | "Deactivate(0)" 9 | ] 10 | }, 11 | { 12 | "process_id": "node1", 13 | "actions": [ 14 | "PassToken(1)", 15 | "\\E i \\in {0, 2} : SendMsg(1, i)", 16 | "Deactivate(1)" 17 | ] 18 | }, 19 | { 20 | "process_id": "node2", 21 | "actions": [ 22 | "PassToken(2)", 23 | "\\E i \\in {0, 1} : SendMsg(2, i)", 24 | "Deactivate(2)" 25 | ] 26 | } 27 | ], 28 | "shared_variables": ["tcolor", "tpos", "active"], 29 | "constants": [ 30 | { 31 | "name": "N", 32 | "value": "3" 33 | } 34 | ], 35 | "init": "Init", 36 | "next": "Next", 37 | "module_name": "EWD840", 38 | "input_format": "json", 39 | "input_file": "EWD840.json", 40 | "state_graph": "states.json", 41 | "blackbox_tests": [{"name": "Termination", "trace": "trace.out"}], 42 | "destination_folder": "../../elixir" 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Gabriela Moreira Mafra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores/TrafficSemaphores.tla: -------------------------------------------------------------------------------- 1 | ------------------- MODULE TrafficSemaphores ----------------------- 2 | EXTENDS Integers, FiniteSets 3 | VARIABLE colors, next_to_open 4 | CONSTANT SEMAPHORES 5 | 6 | TurnGreen(s) == /\ \A s2 \in SEMAPHORES : colors[s2] = "red" 7 | /\colors' = [colors EXCEPT ![s] = "green"] 8 | /\next_to_open = s 9 | /\next_to_open' = (s + 1) % Cardinality(SEMAPHORES) 10 | 11 | TurnYellow(s) == /\colors[s] = "green" 12 | /\colors' = [colors EXCEPT ![s] = "yellow"] 13 | /\UNCHANGED << next_to_open >> 14 | 15 | TurnRed(s) == /\colors[s] = "yellow" 16 | /\colors' = [colors EXCEPT ![s] = "red"] 17 | /\UNCHANGED << next_to_open >> 18 | 19 | Init == colors = [s \in SEMAPHORES |-> "red"] /\next_to_open = 0 20 | 21 | Next == \E s \in SEMAPHORES : TurnGreen(s) \/ TurnYellow(s) \/ TurnRed(s) 22 | ------------------------------------------------------------ 23 | ============================================================ 24 | \* Modification History 25 | \* Created Tue May 25 14:59:50 2021 by Gabriela Moreira Mafra 26 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores_with_problem/states.dot: -------------------------------------------------------------------------------- 1 | strict digraph DiskGraph { 2 | nodesep=0.35; 3 | subgraph cluster_graph { 4 | color="white"; 5 | -8560279655203159469 [label="colors = (0 :> \"red\" @@ 1 :> \"red\")",style = filled] 6 | -8560279655203159469 -> -226189302072578771 [label="",color="black",fontcolor="black"]; 7 | -226189302072578771 [label="colors = (0 :> \"green\" @@ 1 :> \"red\")"]; 8 | -8560279655203159469 -> -1324068011965573801 [label="",color="black",fontcolor="black"]; 9 | -1324068011965573801 [label="colors = (0 :> \"red\" @@ 1 :> \"green\")"]; 10 | -226189302072578771 -> -1382036830410843572 [label="",color="black",fontcolor="black"]; 11 | -1382036830410843572 [label="colors = (0 :> \"yellow\" @@ 1 :> \"red\")"]; 12 | -1324068011965573801 -> -5960835137435681151 [label="",color="black",fontcolor="black"]; 13 | -5960835137435681151 [label="colors = (0 :> \"red\" @@ 1 :> \"yellow\")"]; 14 | -1382036830410843572 -> -8560279655203159469 [label="",color="black",fontcolor="black"]; 15 | -5960835137435681151 -> -8560279655203159469 [label="",color="black",fontcolor="black"]; 16 | {rank = same; -8560279655203159469;} 17 | {rank = same; -1324068011965573801;-226189302072578771;} 18 | {rank = same; -1382036830410843572;-5960835137435681151;} 19 | } 20 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TLA+ Transmutation 2 | Elixir code and test generation from TLA+ specifications 3 | 4 | ## Dependencies 5 | 6 | > For nix users, there are nix files for both a Haskell and an Elixir environment. 7 | 8 | * GHC 8.0.2 9 | 10 | #### For running the generated code 11 | * Elixir 12 | * Erlang 13 | 14 | [Instructions for installation with asdf package manager](https://elixirgirls.com/install-guides/linux.html) 15 | 16 | ## Build 17 | 18 | ``` sh 19 | make compile 20 | ``` 21 | 22 | ## Run 23 | 24 | The best parsing implementation takes JSON files previously parsed from TLA with [Apalache](https://github.com/informalsystems/apalache): 25 | 26 | ``` sh 27 | apalache-mc parse --output=file.json file.tla 28 | ``` 29 | 30 | All compliled files (`CodeGenerator`, `WhiteboxTestGenerator` and `BlackboxTestGenerator`) take a single argument which is a JSON-formatted configuration file similar to [config-sample.json](./config-sample.json) 31 | 32 | Folders inside [tla_specifications](./tla_specifications) are examples containing the required files for running the three executables. You can `cd` into any of them and run: 33 | 34 | ``` sh 35 | ../../CodeGenerator config.json && ../../BlackboxTestGenerator config.json && ../../WhiteboxTestGenerator config.json 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/ew_d840_node0.ex: -------------------------------------------------------------------------------- 1 | defmodule EWD840_node0 do 2 | require Oracle 3 | 4 | import EWD840 5 | 6 | def next(variables) do 7 | Enum.filter( 8 | List.flatten([ 9 | %{ action: "InitiateProbe()", condition: initiate_probe_condition(variables), transition: fn (variables) -> initiate_probe(variables) end }, 10 | Enum.map(MapSet.new([1, 2]), fn (i) -> [ 11 | %{ action: "SendMsg(Lit (Num 0), #{inspect i})", condition: send_msg_condition(variables, 0, i), transition: fn (variables) -> send_msg(variables, 0, i) end } 12 | ] end 13 | ), 14 | %{ action: "Deactivate(Lit (Num 0))", condition: deactivate_condition(variables, 0), transition: fn (variables) -> deactivate(variables, 0) end } 15 | ]), 16 | fn(action) -> action[:condition] end 17 | ) 18 | end 19 | 20 | def main(oracle, private_variables, step) do 21 | shared_state = wait_lock(oracle) 22 | variables = Map.merge(private_variables, shared_state) 23 | 24 | actions = next(variables) 25 | 26 | next_variables = decide_action(oracle, variables, actions, step) 27 | send(oracle, {:notify, self(), variables, next_variables}) 28 | Process.sleep(2000) 29 | 30 | main(oracle, next_variables, step + 1) 31 | end 32 | end 33 | 34 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/ew_d840_node1.ex: -------------------------------------------------------------------------------- 1 | defmodule EWD840_node1 do 2 | require Oracle 3 | 4 | import EWD840 5 | 6 | def next(variables) do 7 | Enum.filter( 8 | List.flatten([ 9 | %{ action: "PassToken(Lit (Num 1))", condition: pass_token_condition(variables, 1), transition: fn (variables) -> pass_token(variables, 1) end }, 10 | Enum.map(MapSet.new([0, 2]), fn (i) -> [ 11 | %{ action: "SendMsg(Lit (Num 1), #{inspect i})", condition: send_msg_condition(variables, 1, i), transition: fn (variables) -> send_msg(variables, 1, i) end } 12 | ] end 13 | ), 14 | %{ action: "Deactivate(Lit (Num 1))", condition: deactivate_condition(variables, 1), transition: fn (variables) -> deactivate(variables, 1) end } 15 | ]), 16 | fn(action) -> action[:condition] end 17 | ) 18 | end 19 | 20 | def main(oracle, private_variables, step) do 21 | shared_state = wait_lock(oracle) 22 | variables = Map.merge(private_variables, shared_state) 23 | 24 | actions = next(variables) 25 | 26 | next_variables = decide_action(oracle, variables, actions, step) 27 | send(oracle, {:notify, self(), variables, next_variables}) 28 | Process.sleep(2000) 29 | 30 | main(oracle, next_variables, step + 1) 31 | end 32 | end 33 | 34 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/ew_d840_node2.ex: -------------------------------------------------------------------------------- 1 | defmodule EWD840_node2 do 2 | require Oracle 3 | 4 | import EWD840 5 | 6 | def next(variables) do 7 | Enum.filter( 8 | List.flatten([ 9 | %{ action: "PassToken(Lit (Num 2))", condition: pass_token_condition(variables, 2), transition: fn (variables) -> pass_token(variables, 2) end }, 10 | Enum.map(MapSet.new([0, 1]), fn (i) -> [ 11 | %{ action: "SendMsg(Lit (Num 2), #{inspect i})", condition: send_msg_condition(variables, 2, i), transition: fn (variables) -> send_msg(variables, 2, i) end } 12 | ] end 13 | ), 14 | %{ action: "Deactivate(Lit (Num 2))", condition: deactivate_condition(variables, 2), transition: fn (variables) -> deactivate(variables, 2) end } 15 | ]), 16 | fn(action) -> action[:condition] end 17 | ) 18 | end 19 | 20 | def main(oracle, private_variables, step) do 21 | shared_state = wait_lock(oracle) 22 | variables = Map.merge(private_variables, shared_state) 23 | 24 | actions = next(variables) 25 | 26 | next_variables = decide_action(oracle, variables, actions, step) 27 | send(oracle, {:notify, self(), variables, next_variables}) 28 | Process.sleep(2000) 29 | 30 | main(oracle, next_variables, step + 1) 31 | end 32 | end 33 | 34 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/traffic_semaphores_main.ex: -------------------------------------------------------------------------------- 1 | defmodule TrafficSemaphores_main do 2 | require Oracle 3 | 4 | import TrafficSemaphores 5 | 6 | def next(variables) do 7 | Enum.filter( 8 | List.flatten([ 9 | Enum.map(TrafficSemaphores.semaphores, fn (s) -> [ 10 | %{ action: "TurnGreen(#{inspect s})", condition: turn_green_condition(variables, s), transition: fn (variables) -> turn_green(variables, s) end }, 11 | %{ action: "TurnYellow(#{inspect s})", condition: turn_yellow_condition(variables, s), transition: fn (variables) -> turn_yellow(variables, s) end }, 12 | %{ action: "TurnRed(#{inspect s})", condition: turn_red_condition(variables, s), transition: fn (variables) -> turn_red(variables, s) end } 13 | ] end 14 | ) 15 | ]), 16 | fn(action) -> action[:condition] end 17 | ) 18 | end 19 | 20 | def main(oracle, private_variables, step) do 21 | shared_state = wait_lock(oracle) 22 | variables = Map.merge(private_variables, shared_state) 23 | 24 | actions = next(variables) 25 | 26 | next_variables = decide_action(oracle, variables, actions, step) 27 | send(oracle, {:notify, self(), variables, next_variables}) 28 | Process.sleep(2000) 29 | 30 | main(oracle, next_variables, step + 1) 31 | end 32 | end 33 | 34 | -------------------------------------------------------------------------------- /CodeGenerator.hs: -------------------------------------------------------------------------------- 1 | import System.Environment 2 | 3 | import ConfigParser 4 | import Control.Applicative 5 | import Elixir 6 | import Head 7 | import Helpers 8 | import JSONParser (parseJson) 9 | import Parser 10 | 11 | filename dest p = dest ++ "/lib/generated_code/" ++ snake p ++ ".ex" 12 | 13 | writeCode dest m (p, code) = 14 | let f = filename dest p 15 | in writeFile f code >> return f 16 | 17 | writeStarter dest (name, code) = 18 | let f = dest ++ "/lib/mix/tasks/" ++ snake name ++ "_starter.ex" 19 | in writeFile f code >> return f 20 | 21 | convert name init next dest config (m, ds) = do 22 | files <- mapM (writeCode dest name) (generate (Spec m init next ds) config) 23 | starter <- mapM (writeStarter dest . generateStarter (Spec m init next ds)) (processNames config) 24 | return (starter, files) 25 | 26 | main = do 27 | (configFile:_) <- getArgs 28 | config <- parseConfig configFile 29 | case config of 30 | Left e -> putStrLn e 31 | Right (Config _ _ _ i n _ mode file _ _ dest) -> do 32 | ls <- 33 | if mode == "tla" 34 | then parseTla file 35 | else parseJson file 36 | case liftA2 (convert file i n dest) config ls of 37 | Left e -> putStrLn e 38 | Right f -> do 39 | a <- f 40 | putStrLn ("Elixirs files written to " ++ show a) 41 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # { nixpkgs ? import {} }: 2 | # let 3 | # sources = { 4 | # haskellNix = builtins.fetchTarball "https://github.com/input-output-hk/haskell.nix/archive/master.tar.gz"; 5 | # }; 6 | # haskellNix = import sources.haskellNix {}; 7 | 8 | # # Import nixpkgs and pass the haskell.nix provided nixpkgsArgs 9 | # pkgs = import haskellNix.sources.nixpkgs haskellNix.nixpkgsArgs; 10 | # in pkgs.haskell-nix.project { 11 | # src = pkgs.haskell-nix.haskellLib.cleanGit { 12 | # name = "tla-transmutation"; 13 | # src = ./.; 14 | # }; 15 | 16 | # compiler-nix-name = "ghc8102"; 17 | # } 18 | { nixpkgs ? import {} }: 19 | let 20 | inherit (nixpkgs) pkgs; 21 | inherit (pkgs) haskellPackages; 22 | 23 | haskellDeps = ps: with ps; [ 24 | base 25 | aeson 26 | casing 27 | extra 28 | ]; 29 | 30 | ghc = haskellPackages.ghcWithPackages haskellDeps; 31 | 32 | nixPackages = [ 33 | ghc 34 | nixpkgs.haskellPackages.haskell-language-server 35 | nixpkgs.haskellPackages.hlint 36 | nixpkgs.haskellPackages.apply-refact 37 | nixpkgs.haskellPackages.hoogle 38 | nixpkgs.haskellPackages.hindent 39 | nixpkgs.haskellPackages.hasktags 40 | nixpkgs.haskellPackages.happy 41 | nixpkgs.haskellPackages.stylish-haskell 42 | ]; 43 | in 44 | pkgs.stdenv.mkDerivation { 45 | name = "env"; 46 | buildInputs = nixPackages; 47 | } 48 | -------------------------------------------------------------------------------- /elixir/lib/oracle_examples/managers_oracle.ex: -------------------------------------------------------------------------------- 1 | defmodule ManagersOracle do 2 | def start do 3 | :global.register_name("oracle", self()) 4 | 5 | spawn(ManagersOracle, :read, []) 6 | listen() 7 | end 8 | 9 | def listen do 10 | receive do 11 | {:choose, p, _, as} -> send(p, {:ok, managers_choice(as)}) 12 | end 13 | 14 | listen() 15 | end 16 | 17 | def managers_choice(actions) do 18 | enumerated_actions = actions |> Enum.with_index() |> Enum.map(fn {x, i} -> "#{i} => #{x}" end) 19 | 20 | send_choices(enumerated_actions, "r1") 21 | send_choices(enumerated_actions, "r2") 22 | show_transaction_manager_choices(enumerated_actions) 23 | 24 | receive do 25 | {:choice, i} -> 26 | IO.puts("From #{inspect(actions)}, oracle chose #{Enum.at(actions, i)}") 27 | i 28 | end 29 | end 30 | 31 | def send_choices(as, id) do 32 | choices = Enum.filter(as, fn a -> a =~ id and a =~ "GR" end) 33 | 34 | manager_pid = :global.whereis_name(id) 35 | send(manager_pid, {self(), choices}) 36 | end 37 | 38 | def show_transaction_manager_choices(as) do 39 | transaction_manager_choices = Enum.filter(as, fn a -> a =~ "GT" end) 40 | 41 | IO.puts(inspect(transaction_manager_choices)) 42 | end 43 | 44 | def read do 45 | {i, _} = Integer.parse(IO.gets("Escolha do Gerenciador de Transações: ")) 46 | 47 | send(:global.whereis_name("oracle"), {:choice, i}) 48 | 49 | read() 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/er_c20_blockchain.ex: -------------------------------------------------------------------------------- 1 | defmodule ERC20_blockchain do 2 | require Oracle 3 | 4 | import ERC20 5 | 6 | def next(variables) do 7 | Enum.filter( 8 | List.flatten([ 9 | Enum.map(variables[:pending_transactions], fn (tx) -> [ 10 | %{ action: "CommitTransfer(#{inspect tx})", condition: commit_transfer_condition(variables, tx), transition: fn (variables) -> commit_transfer(variables, tx) end } 11 | ] end 12 | ), 13 | Enum.map(variables[:pending_transactions], fn (tx) -> [ 14 | %{ action: "CommitTransferFrom(#{inspect tx})", condition: commit_transfer_from_condition(variables, tx), transition: fn (variables) -> commit_transfer_from(variables, tx) end } 15 | ] end 16 | ), 17 | Enum.map(variables[:pending_transactions], fn (tx) -> [ 18 | %{ action: "CommitApprove(#{inspect tx})", condition: commit_approve_condition(variables, tx), transition: fn (variables) -> commit_approve(variables, tx) end } 19 | ] end 20 | ) 21 | ]), 22 | fn(action) -> action[:condition] end 23 | ) 24 | end 25 | 26 | def main(oracle, private_variables, step) do 27 | shared_state = wait_lock(oracle) 28 | variables = Map.merge(private_variables, shared_state) 29 | 30 | actions = next(variables) 31 | 32 | next_variables = decide_action(oracle, variables, actions, step) 33 | send(oracle, {:notify, self(), variables, next_variables}) 34 | Process.sleep(2000) 35 | 36 | main(oracle, next_variables, step + 1) 37 | end 38 | end 39 | 40 | -------------------------------------------------------------------------------- /two_phase_commit_example/lib/managers_oracle.ex: -------------------------------------------------------------------------------- 1 | defmodule ManagersOracle do 2 | def start do 3 | :global.register_name("oracle", self()) 4 | 5 | spawn(ManagersOracle, :read, []) 6 | listen() 7 | end 8 | 9 | def listen do 10 | receive do 11 | {p, as} -> send p, {:ok, managers_choice(as)} 12 | end 13 | 14 | listen() 15 | end 16 | 17 | def managers_choice(actions) do 18 | enumerated_actions = actions |> Enum.with_index |> Enum.map(fn({x, i}) -> "#{i} => #{x}" end) 19 | 20 | send_choices(enumerated_actions, "r1") 21 | send_choices(enumerated_actions, "r2") 22 | show_transaction_manager_choices(enumerated_actions) 23 | 24 | receive do 25 | {:choice, i} -> IO.puts "From #{inspect actions}, oracle chose #{Enum.at(actions, i)}"; i 26 | end 27 | end 28 | 29 | def send_choices(as, id) do 30 | choices = Enum.filter(as, fn (a) -> a =~ id and a =~ "RM" end) 31 | 32 | send get_pid(id), {self(), choices} 33 | end 34 | 35 | def show_transaction_manager_choices(as) do 36 | transaction_manager_choices = Enum.filter(as, fn (a) -> a =~ "TM" end) 37 | 38 | IO.puts Enum.join(transaction_manager_choices, "\n") 39 | IO.puts "" 40 | end 41 | 42 | def read do 43 | {i, _} = Integer.parse(IO.gets("Escolha do Gerenciador de Transações: ")) 44 | 45 | send :global.whereis_name("oracle"), {:choice, i} 46 | 47 | read() 48 | end 49 | 50 | def get_pid(id) do 51 | pid = :global.whereis_name(id) 52 | 53 | if pid == :undefined do 54 | get_pid(id) 55 | else 56 | pid 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /tla_specifications/ewd840/trace.out: -------------------------------------------------------------------------------- 1 | State 1: 2 | /\ tpos = 0 3 | /\ active = (0 :> TRUE @@ 1 :> TRUE @@ 2 :> TRUE) 4 | /\ tcolor = "black" 5 | /\ color = (0 :> "white" @@ 1 :> "white" @@ 2 :> "white") 6 | 7 | State 2: 8 | /\ tpos = 2 9 | /\ active = (0 :> TRUE @@ 1 :> TRUE @@ 2 :> TRUE) 10 | /\ tcolor = "white" 11 | /\ color = (0 :> "white" @@ 1 :> "white" @@ 2 :> "white") 12 | 13 | State 3: 14 | /\ tpos = 2 15 | /\ active = (0 :> FALSE @@ 1 :> TRUE @@ 2 :> TRUE) 16 | /\ tcolor = "white" 17 | /\ color = (0 :> "white" @@ 1 :> "white" @@ 2 :> "white") 18 | 19 | State 4: 20 | /\ tpos = 2 21 | /\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> TRUE) 22 | /\ tcolor = "white" 23 | /\ color = (0 :> "white" @@ 1 :> "white" @@ 2 :> "white") 24 | 25 | State 5: 26 | /\ tpos = 2 27 | /\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE) 28 | /\ tcolor = "white" 29 | /\ color = (0 :> "white" @@ 1 :> "white" @@ 2 :> "white") 30 | 31 | State 6: 32 | /\ tpos = 1 33 | /\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE) 34 | /\ tcolor = "white" 35 | /\ color = (0 :> "white" @@ 1 :> "white" @@ 2 :> "white") 36 | 37 | State 7: 38 | /\ tpos = 0 39 | /\ active = (0 :> FALSE @@ 1 :> FALSE @@ 2 :> FALSE) 40 | /\ tcolor = "white" 41 | /\ color = (0 :> "white" @@ 1 :> "white" @@ 2 :> "white") 42 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/termination.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.Termination do 2 | @moduledoc "Runs blackblox testing using the oracle" 3 | @shortdoc "Runs trace checking for a witness" 4 | use Mix.Task 5 | 6 | @impl Mix.Task 7 | def run(_) do 8 | trace = [ 9 | %{ 10 | tpos: 0, 11 | active: %{ 0 => true, 1 => true, 2 => true }, 12 | tcolor: "black", 13 | color: %{ 0 => "white", 1 => "white", 2 => "white" } 14 | }, 15 | %{ 16 | tpos: 2, 17 | active: %{ 0 => true, 1 => true, 2 => true }, 18 | tcolor: "white", 19 | color: %{ 0 => "white", 1 => "white", 2 => "white" } 20 | }, 21 | %{ 22 | tpos: 2, 23 | active: %{ 0 => false, 1 => true, 2 => true }, 24 | tcolor: "white", 25 | color: %{ 0 => "white", 1 => "white", 2 => "white" } 26 | }, 27 | %{ 28 | tpos: 2, 29 | active: %{ 0 => false, 1 => false, 2 => true }, 30 | tcolor: "white", 31 | color: %{ 0 => "white", 1 => "white", 2 => "white" } 32 | }, 33 | %{ 34 | tpos: 2, 35 | active: %{ 0 => false, 1 => false, 2 => false }, 36 | tcolor: "white", 37 | color: %{ 0 => "white", 1 => "white", 2 => "white" } 38 | }, 39 | %{ 40 | tpos: 1, 41 | active: %{ 0 => false, 1 => false, 2 => false }, 42 | tcolor: "white", 43 | color: %{ 0 => "white", 1 => "white", 2 => "white" } 44 | }, 45 | %{ 46 | tpos: 0, 47 | active: %{ 0 => false, 1 => false, 2 => false }, 48 | tcolor: "white", 49 | color: %{ 0 => "white", 1 => "white", 2 => "white" } 50 | } 51 | ] 52 | 53 | modules = [ 54 | EWD840_node0, 55 | EWD840_node1, 56 | EWD840_node2 57 | ] 58 | pids = Enum.map(modules, fn m -> spawn(m, :main, [self(), Enum.at(trace, 0), 0]) end) 59 | TraceCheckerOracle.start(trace, 0, nil, pids) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /BlackboxTestGenerator.hs: -------------------------------------------------------------------------------- 1 | import Data.List 2 | import Elixir 3 | import Helpers 4 | import Parser 5 | import System.Environment 6 | import ConfigParser 7 | 8 | testFile testTrace modules testName = 9 | unlines 10 | [ "defmodule Mix.Tasks." ++ testName ++ " do" 11 | , " @moduledoc \"Runs blackblox testing using the oracle\"" 12 | , " @shortdoc \"Runs trace checking for a witness\"" 13 | , " use Mix.Task" 14 | , "" 15 | , " @impl Mix.Task" 16 | , " def run(_) do" 17 | , " trace = [" 18 | , intercalate ",\n" testTrace 19 | , " ]" 20 | , "" 21 | , " modules = [" 22 | , " " ++ intercalate ",\n " modules 23 | , " ]" 24 | , " pids = Enum.map(modules, fn m -> spawn(m, :main, [self(), Enum.at(trace, 0), 0]) end)" 25 | , " TraceCheckerOracle.start(trace, 0, nil, pids)" 26 | , " end" 27 | , "end" 28 | ] 29 | 30 | generateTestFromTrace moduleName dest ps (Test n t) = do 31 | f <- readFile t 32 | case parseTrace f of 33 | Right ss -> 34 | let content = testFile (map (initialState [] . toValue) ss) (map ((moduleName ++ "_") ++) ps) n 35 | outFile = dest ++ "/lib/mix/tasks/" ++ snake n ++ ".ex" 36 | in writeFile outFile content 37 | Left e -> print e 38 | 39 | -- generateBlackboxTests :: [String] -> DistributionConfig -> Either String IO() 40 | generateBlackboxTests ps (Config _ _ _ _ _ name _ _ _ tests dest) = mapM (generateTestFromTrace name dest ps) tests 41 | 42 | main :: IO [()] 43 | main = do 44 | (configFile:_) <- getArgs 45 | config <- parseConfig configFile 46 | case fmap (\c -> generateBlackboxTests (processNames c) c) config of 47 | Left err -> error err 48 | Right c -> c 49 | -------------------------------------------------------------------------------- /Snippets.hs: -------------------------------------------------------------------------------- 1 | module Snippets where 2 | 3 | decideAction = 4 | unlines 5 | [ "" 6 | , "def decide_action(oracle, variables, actions, step) do" 7 | , " different_states = Enum.uniq(Enum.map(actions, fn(action) -> action[:transition].(variables) end))" 8 | , "" 9 | , " cond do" 10 | , " Enum.count(different_states) == 1 ->" 11 | , " Enum.at(different_states, 0)" 12 | , " true ->" 13 | , " send oracle, {:choose, self(), different_states}" 14 | , "" 15 | , " receive do" 16 | , " {:ok, n} -> Enum.at(different_states, n)" 17 | , " {:cancel} -> variables" 18 | , " {:stop} -> exit(0)" 19 | , " end" 20 | , " end" 21 | , "end" 22 | ] 23 | 24 | mainFunction = 25 | unlines 26 | [ "def main(oracle, private_variables, step) do" 27 | , " shared_state = wait_lock(oracle)" 28 | , " variables = Map.merge(private_variables, shared_state)" 29 | , "" 30 | , " actions = next(variables)" 31 | , "" 32 | , " next_variables = decide_action(oracle, variables, actions, step)" 33 | , " send(oracle, {:notify, self(), variables, next_variables})" 34 | , " Process.sleep(2000)" 35 | , "" 36 | , " main(oracle, next_variables, step + 1)" 37 | , "end" 38 | ] 39 | 40 | waitLockFunction = 41 | unlines 42 | [ "def wait_lock(oracle) do" 43 | , " send(oracle, {:lock, self()})" 44 | , " receive do" 45 | , " {:lock_acquired, state} -> IO.puts(\"Lock acquired\"); {map, _} = Map.split(state, shared_variables); map" 46 | , " {:already_locked, _} -> IO.puts(\"Lock refused\"); Process.sleep(1000); wait_lock(oracle)" 47 | , " end" 48 | , "end" 49 | ] 50 | 51 | logState = "IO.puts (inspect variables)\n\n" 52 | 53 | oracleDelaration = "require Oracle\n\n" 54 | -------------------------------------------------------------------------------- /tla_specifications/old/JarrosDeAgua.tla: -------------------------------------------------------------------------------- 1 | ---------------------------- MODULE JarrosDeAgua ---------------------------- 2 | EXTENDS Integers 3 | 4 | VARIABLES jarro_pequeno, jarro_grande 5 | 6 | (* TypeOK == /\ jarro_pequeno \in 0..3 *) 7 | (* /\ jarro_grande \in 0..5 *) 8 | 9 | Init == /\ jarro_grande = 0 10 | /\ jarro_pequeno = 0 11 | 12 | EnchePequeno == /\ jarro_pequeno' = 3 13 | /\ jarro_grande' = jarro_grande 14 | 15 | EncheGrande == /\ jarro_grande' = 5 16 | /\ jarro_pequeno' = jarro_pequeno 17 | 18 | EsvaziaPequeno == /\ jarro_pequeno' = 0 19 | /\ jarro_grande' = jarro_grande 20 | 21 | EsvaziaGrande == /\ jarro_grande' = 0 22 | /\ jarro_pequeno' = jarro_pequeno 23 | 24 | PequenoParaGrande == IF jarro_grande + jarro_pequeno =< 5 25 | THEN /\ jarro_grande' = jarro_grande + jarro_pequeno 26 | /\ jarro_pequeno' = 0 27 | ELSE /\ jarro_grande' = 5 28 | /\ jarro_pequeno' = jarro_pequeno - (5 - jarro_grande) 29 | 30 | GrandeParaPequeno == IF jarro_grande + jarro_pequeno =< 3 31 | THEN /\ jarro_grande' = 0 32 | /\ jarro_pequeno' = jarro_grande + jarro_pequeno 33 | ELSE /\ jarro_grande' = jarro_pequeno - (3 - jarro_grande) 34 | /\ jarro_pequeno' = 3 35 | 36 | Inv == jarro_grande /= 4 37 | 38 | Next == \/ EnchePequeno 39 | \/ EncheGrande 40 | \/ EsvaziaPequeno 41 | \/ EsvaziaGrande 42 | \/ PequenoParaGrande 43 | \/ GrandeParaPequeno 44 | ============================================================================= 45 | -------------------------------------------------------------------------------- /Math.hs: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------- 2 | -- Copyright (c) 2014, Enzo Haussecker. All rights reserved. -- 3 | --------------------------------------------------------------- 4 | {-# OPTIONS -Wall #-} 5 | 6 | -- | Parse and evaluate mathematical expressions. 7 | module Math where 8 | 9 | import Control.Applicative ((<|>)) 10 | 11 | import Data.Char 12 | import Head 13 | 14 | import qualified Text.ParserCombinators.Parsec as P 15 | import qualified Text.ParserCombinators.Parsec.Expr as P 16 | 17 | build :: P.Parser Value 18 | build = do 19 | P.try $ P.buildExpressionParser table (P.try $ factor) <|> factor 20 | 21 | table :: [[P.Operator Char st Value]] 22 | table = 23 | [ [prefix "-" Neg] 24 | , [binary "*" Mul P.AssocLeft, binary "/ " Div P.AssocLeft] 25 | , [binary "+" Add P.AssocLeft, binary "-" Sub P.AssocLeft] 26 | , [binary "%" Mod P.AssocLeft] 27 | ] 28 | where 29 | binary s f a = P.Infix (P.try (P.string s) >> ws >> return f) a 30 | prefix s f = P.Prefix (P.try (P.string s) >> ws >> return f) 31 | 32 | ws = 33 | P.many $ do 34 | P.many1 (P.oneOf " \n") 35 | return () 36 | 37 | factor :: P.Parser Value 38 | factor = 39 | do _ <- P.char '(' 40 | expr <- build 41 | ws 42 | _ <- P.char ')' 43 | ws 44 | return (expr) 45 | <|> atom 46 | 47 | atom :: P.Parser Value 48 | atom = 49 | do P.try $ do 50 | n <- number 51 | ws 52 | return (n) 53 | <|> do 54 | var <- P.many1 (P.oneOf (['a' .. 'z'] ++ ['A' .. 'Z'] ++ ['_'] ++ ['0' .. '9'])) 55 | ws 56 | return $! Ref var 57 | 58 | number :: P.Parser Value 59 | number = do 60 | n <- numberLiteral 61 | return (Lit n) 62 | 63 | numberLiteral :: P.Parser Lit 64 | numberLiteral = do 65 | digits <- P.many1 P.digit 66 | let n = foldl (\x d -> 10 * x + toInteger (digitToInt d)) 0 digits 67 | ws 68 | return (Num n) 69 | -------------------------------------------------------------------------------- /elixir/lib/oracle_examples/trace_checker_oracle.ex: -------------------------------------------------------------------------------- 1 | defmodule TraceCheckerOracle do 2 | def start(trace, step, lock, pids) do 3 | startWithValues(trace, step, lock, pids, []) 4 | end 5 | 6 | def startWithValues(trace, step, lock, pids, refused) do 7 | if step >= length(trace) - 1 do 8 | Enum.map(pids, fn pid -> send(pid, :stop) end) 9 | IO.puts("SUCCESS") 10 | exit(0) 11 | end 12 | 13 | if Enum.sort(Enum.uniq(pids)) == Enum.sort(Enum.uniq(refused)) do 14 | IO.puts("FAILURE") 15 | exit(1) 16 | end 17 | 18 | current_variables = Enum.at(trace, step) 19 | 20 | ref = Process.monitor(if lock != nil, do: lock, else: self()) 21 | 22 | receive do 23 | {:lock, p} -> 24 | IO.inspect(lock) 25 | 26 | if lock != nil do 27 | send(p, {:already_locked, %{}}) 28 | else 29 | send(p, {:lock_acquired, current_variables}) 30 | startWithValues(trace, step, p, pids, refused) 31 | end 32 | 33 | {:notify, p, current_state, next_state} -> 34 | if current_state != next_state do 35 | result = TraceRunner.check(trace, step, current_state, next_state) 36 | IO.puts("Step: #{step + 1}") 37 | IO.inspect(result) 38 | IO.inspect(next_state) 39 | startWithValues(trace, step + 1, nil, pids, []) 40 | else 41 | startWithValues(trace, step, nil, pids, [p | refused]) 42 | end 43 | 44 | {:choose, p, as} -> 45 | choice = TraceRunner.choose(trace, step, as) 46 | 47 | if choice != nil do 48 | send(p, {:ok, choice}) 49 | else 50 | 51 | send(p, {:cancel}) 52 | end 53 | 54 | {:DOWN, ^ref, _, _, _} -> 55 | IO.puts("Process #{inspect(ref)} is down") 56 | Process.sleep(2000) 57 | exit(1) 58 | end 59 | 60 | startWithValues(trace, step, lock, pids, refused) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/er_c20_bob.ex: -------------------------------------------------------------------------------- 1 | defmodule ERC20_bob do 2 | require Oracle 3 | 4 | import ERC20 5 | 6 | def next(variables) do 7 | Enum.filter( 8 | List.flatten([ 9 | Enum.map(ERC20.addr, fn (toAddr) -> [ 10 | Enum.map(ERC20.amounts, fn (value) -> [ 11 | %{ action: "SubmitTransfer(Bob_OF_ADDR, #{inspect toAddr}, #{inspect value})", condition: submit_transfer_condition(variables, "Bob_OF_ADDR", toAddr, value), transition: fn (variables) -> submit_transfer(variables, "Bob_OF_ADDR", toAddr, value) end } 12 | ] end 13 | ) 14 | ] end 15 | ), 16 | Enum.map(ERC20.addr, fn (fromAddr) -> [ 17 | Enum.map(ERC20.addr, fn (toAddr) -> [ 18 | Enum.map(ERC20.amounts, fn (value) -> [ 19 | %{ action: "SubmitTransferFrom(Bob_OF_ADDR, #{inspect fromAddr}, #{inspect toAddr}, #{inspect value})", condition: submit_transfer_from_condition(variables, "Bob_OF_ADDR", fromAddr, toAddr, value), transition: fn (variables) -> submit_transfer_from(variables, "Bob_OF_ADDR", fromAddr, toAddr, value) end } 20 | ] end 21 | ) 22 | ] end 23 | ) 24 | ] end 25 | ), 26 | Enum.map(ERC20.addr, fn (spender) -> [ 27 | Enum.map(ERC20.amounts, fn (value) -> [ 28 | %{ action: "SubmitApprove(Bob_OF_ADDR, #{inspect spender}, #{inspect value})", condition: submit_approve_condition(variables, "Bob_OF_ADDR", spender, value), transition: fn (variables) -> submit_approve(variables, "Bob_OF_ADDR", spender, value) end } 29 | ] end 30 | ) 31 | ] end 32 | ) 33 | ]), 34 | fn(action) -> action[:condition] end 35 | ) 36 | end 37 | 38 | def main(oracle, private_variables, step) do 39 | shared_state = wait_lock(oracle) 40 | variables = Map.merge(private_variables, shared_state) 41 | 42 | actions = next(variables) 43 | 44 | next_variables = decide_action(oracle, variables, actions, step) 45 | send(oracle, {:notify, self(), variables, next_variables}) 46 | Process.sleep(2000) 47 | 48 | main(oracle, next_variables, step + 1) 49 | end 50 | end 51 | 52 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/er_c20_eve.ex: -------------------------------------------------------------------------------- 1 | defmodule ERC20_eve do 2 | require Oracle 3 | 4 | import ERC20 5 | 6 | def next(variables) do 7 | Enum.filter( 8 | List.flatten([ 9 | Enum.map(ERC20.addr, fn (toAddr) -> [ 10 | Enum.map(ERC20.amounts, fn (value) -> [ 11 | %{ action: "SubmitTransfer(Eve_OF_ADDR, #{inspect toAddr}, #{inspect value})", condition: submit_transfer_condition(variables, "Eve_OF_ADDR", toAddr, value), transition: fn (variables) -> submit_transfer(variables, "Eve_OF_ADDR", toAddr, value) end } 12 | ] end 13 | ) 14 | ] end 15 | ), 16 | Enum.map(ERC20.addr, fn (fromAddr) -> [ 17 | Enum.map(ERC20.addr, fn (toAddr) -> [ 18 | Enum.map(ERC20.amounts, fn (value) -> [ 19 | %{ action: "SubmitTransferFrom(Eve_OF_ADDR, #{inspect fromAddr}, #{inspect toAddr}, #{inspect value})", condition: submit_transfer_from_condition(variables, "Eve_OF_ADDR", fromAddr, toAddr, value), transition: fn (variables) -> submit_transfer_from(variables, "Eve_OF_ADDR", fromAddr, toAddr, value) end } 20 | ] end 21 | ) 22 | ] end 23 | ) 24 | ] end 25 | ), 26 | Enum.map(ERC20.addr, fn (spender) -> [ 27 | Enum.map(ERC20.amounts, fn (value) -> [ 28 | %{ action: "SubmitApprove(Eve_OF_ADDR, #{inspect spender}, #{inspect value})", condition: submit_approve_condition(variables, "Eve_OF_ADDR", spender, value), transition: fn (variables) -> submit_approve(variables, "Eve_OF_ADDR", spender, value) end } 29 | ] end 30 | ) 31 | ] end 32 | ) 33 | ]), 34 | fn(action) -> action[:condition] end 35 | ) 36 | end 37 | 38 | def main(oracle, private_variables, step) do 39 | shared_state = wait_lock(oracle) 40 | variables = Map.merge(private_variables, shared_state) 41 | 42 | actions = next(variables) 43 | 44 | next_variables = decide_action(oracle, variables, actions, step) 45 | send(oracle, {:notify, self(), variables, next_variables}) 46 | Process.sleep(2000) 47 | 48 | main(oracle, next_variables, step + 1) 49 | end 50 | end 51 | 52 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/er_c20_alice.ex: -------------------------------------------------------------------------------- 1 | defmodule ERC20_alice do 2 | require Oracle 3 | 4 | import ERC20 5 | 6 | def next(variables) do 7 | Enum.filter( 8 | List.flatten([ 9 | Enum.map(ERC20.addr, fn (toAddr) -> [ 10 | Enum.map(ERC20.amounts, fn (value) -> [ 11 | %{ action: "SubmitTransfer(Alice_OF_ADDR, #{inspect toAddr}, #{inspect value})", condition: submit_transfer_condition(variables, "Alice_OF_ADDR", toAddr, value), transition: fn (variables) -> submit_transfer(variables, "Alice_OF_ADDR", toAddr, value) end } 12 | ] end 13 | ) 14 | ] end 15 | ), 16 | Enum.map(ERC20.addr, fn (fromAddr) -> [ 17 | Enum.map(ERC20.addr, fn (toAddr) -> [ 18 | Enum.map(ERC20.amounts, fn (value) -> [ 19 | %{ action: "SubmitTransferFrom(Alice_OF_ADDR, #{inspect fromAddr}, #{inspect toAddr}, #{inspect value})", condition: submit_transfer_from_condition(variables, "Alice_OF_ADDR", fromAddr, toAddr, value), transition: fn (variables) -> submit_transfer_from(variables, "Alice_OF_ADDR", fromAddr, toAddr, value) end } 20 | ] end 21 | ) 22 | ] end 23 | ) 24 | ] end 25 | ), 26 | Enum.map(ERC20.addr, fn (spender) -> [ 27 | Enum.map(ERC20.amounts, fn (value) -> [ 28 | %{ action: "SubmitApprove(Alice_OF_ADDR, #{inspect spender}, #{inspect value})", condition: submit_approve_condition(variables, "Alice_OF_ADDR", spender, value), transition: fn (variables) -> submit_approve(variables, "Alice_OF_ADDR", spender, value) end } 29 | ] end 30 | ) 31 | ] end 32 | ) 33 | ]), 34 | fn(action) -> action[:condition] end 35 | ) 36 | end 37 | 38 | def main(oracle, private_variables, step) do 39 | shared_state = wait_lock(oracle) 40 | variables = Map.merge(private_variables, shared_state) 41 | 42 | actions = next(variables) 43 | 44 | next_variables = decide_action(oracle, variables, actions, step) 45 | send(oracle, {:notify, self(), variables, next_variables}) 46 | Process.sleep(2000) 47 | 48 | main(oracle, next_variables, step + 1) 49 | end 50 | end 51 | 52 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/traffic_semaphores.ex: -------------------------------------------------------------------------------- 1 | defmodule TrafficSemaphores do 2 | def shared_variables do 3 | [ 4 | :colors, 5 | :next_to_open, 6 | ] 7 | end 8 | require Oracle 9 | @semaphores MapSet.new([0, 1]) 10 | def semaphores, do: @semaphores 11 | 12 | 13 | def turn_green_condition(variables, s) do 14 | Enum.all?([Enum.all?(@semaphores, fn(s2) -> variables[:colors][s2] == "red" end), variables[:next_to_open] == s]) 15 | end 16 | 17 | def turn_green(variables, s) do 18 | %{ 19 | colors: variables[:colors]|>Map.put(s, "green"), 20 | next_to_open: rem((s + 1), (Enum.count(@semaphores))) 21 | } 22 | end 23 | 24 | 25 | def turn_yellow_condition(variables, s) do 26 | variables[:colors][s] == "green" 27 | end 28 | 29 | def turn_yellow(variables, s) do 30 | %{ 31 | colors: variables[:colors]|>Map.put(s, "yellow"), 32 | next_to_open: variables[:next_to_open] 33 | } 34 | end 35 | 36 | 37 | def turn_red_condition(variables, s) do 38 | variables[:colors][s] == "yellow" 39 | end 40 | 41 | def turn_red(variables, s) do 42 | %{ 43 | colors: variables[:colors]|>Map.put(s, "red"), 44 | next_to_open: variables[:next_to_open] 45 | } 46 | end 47 | 48 | 49 | # "Spec": OperEx "AND" [OperEx "OPER_APP" [NameEx "Init"],OperEx "GLOBALLY" [OperEx "STUTTER" [OperEx "OPER_APP" [NameEx "Next"],OperEx "TUPLE" [NameEx "colors",NameEx "next_to_open"]]]] 50 | 51 | def decide_action(oracle, variables, actions, step) do 52 | different_states = Enum.uniq(Enum.map(actions, fn(action) -> action[:transition].(variables) end)) 53 | 54 | cond do 55 | Enum.count(different_states) == 1 -> 56 | Enum.at(different_states, 0) 57 | true -> 58 | send oracle, {:choose, self(), different_states} 59 | 60 | receive do 61 | {:ok, n} -> Enum.at(different_states, n) 62 | {:cancel} -> variables 63 | {:stop} -> exit(0) 64 | end 65 | end 66 | end 67 | 68 | def wait_lock(oracle) do 69 | send(oracle, {:lock, self()}) 70 | receive do 71 | {:lock_acquired, state} -> IO.puts("Lock acquired"); {map, _} = Map.split(state, shared_variables); map 72 | {:already_locked, _} -> IO.puts("Lock refused"); Process.sleep(1000); wait_lock(oracle) 73 | end 74 | end 75 | end 76 | 77 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "processes": [ 3 | { 4 | "process_id": "alice", 5 | "actions": [ 6 | "\\E toAddr \\in ADDR: \\E value \\in AMOUNTS : SubmitTransfer(\"Alice_OF_ADDR\", toAddr, value)", 7 | "\\E fromAddr \\in ADDR: \\E toAddr \\in ADDR: \\E value \\in AMOUNTS : SubmitTransferFrom(\"Alice_OF_ADDR\", fromAddr, toAddr, value)", 8 | "\\E spender \\in ADDR: \\E value \\in AMOUNTS : SubmitApprove(\"Alice_OF_ADDR\", spender, value)" 9 | ] 10 | }, 11 | { 12 | "process_id": "bob", 13 | "actions": [ 14 | "\\E toAddr \\in ADDR: \\E value \\in AMOUNTS : SubmitTransfer(\"Bob_OF_ADDR\", toAddr, value)", 15 | "\\E fromAddr \\in ADDR: \\E toAddr \\in ADDR: \\E value \\in AMOUNTS : SubmitTransferFrom(\"Bob_OF_ADDR\", fromAddr, toAddr, value)", 16 | "\\E spender \\in ADDR: \\E value \\in AMOUNTS : SubmitApprove(\"Bob_OF_ADDR\", spender, value)" 17 | ] 18 | }, 19 | { 20 | "process_id": "eve", 21 | "actions": [ 22 | "\\E toAddr \\in ADDR: \\E value \\in AMOUNTS : SubmitTransfer(\"Eve_OF_ADDR\", toAddr, value)", 23 | "\\E fromAddr \\in ADDR: \\E toAddr \\in ADDR: \\E value \\in AMOUNTS : SubmitTransferFrom(\"Eve_OF_ADDR\", fromAddr, toAddr, value)", 24 | "\\E spender \\in ADDR: \\E value \\in AMOUNTS : SubmitApprove(\"Eve_OF_ADDR\", spender, value)" 25 | ] 26 | }, 27 | { 28 | "process_id": "blockchain", 29 | "actions": [ 30 | "\\E tx \\in pendingTransactions: CommitTransfer(tx)", 31 | "\\E tx \\in pendingTransactions: CommitTransferFrom(tx)", 32 | "\\E tx \\in pendingTransactions: CommitApprove(tx)" 33 | ] 34 | } 35 | ], 36 | "shared_variables": ["pendingTransactions", "lastTx", "nextTxId", "balanceOf", "allowance"], 37 | "constants": [ 38 | { 39 | "name": "ADDR", 40 | "value": "{\"Alice_OF_ADDR\", \"Bob_OF_ADDR\", \"Eve_OF_ADDR\"}" 41 | }, 42 | { 43 | "name": "AMOUNTS", 44 | "value": "{0, 1, 2, 3, 98, 100, 102}" 45 | } 46 | ], 47 | "init": "Init", 48 | "next": "Next", 49 | "module_name": "ERC20", 50 | "input_format": "json", 51 | "input_file": "ERC20.json", 52 | "state_graph": "states.json", 53 | "blackbox_tests": [{"name": "ExploitTrace", "trace": "trace.out"}], 54 | "destination_folder": "../../elixir" 55 | } 56 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores/states.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DiskGraph", 3 | "directed": true, 4 | "strict": true, 5 | "nodesep": "0.35", 6 | "_subgraph_cnt": 7, 7 | "objects": [ 8 | { 9 | "_gvid": 7, 10 | "name": "7210969400895743542", 11 | "label": "/\\\\ colors = (0 :> \"red\" @@ 1 :> \"red\")\\n/\\\\ next_to_open = 0", 12 | "style": "filled" 13 | }, 14 | { 15 | "_gvid": 8, 16 | "name": "8666215057606062623", 17 | "label": "/\\\\ colors = (0 :> \"green\" @@ 1 :> \"red\")\\n/\\\\ next_to_open = 1" 18 | }, 19 | { 20 | "_gvid": 9, 21 | "name": "-1145142259021417419", 22 | "label": "/\\\\ colors = (0 :> \"yellow\" @@ 1 :> \"red\")\\n/\\\\ next_to_open = 1" 23 | }, 24 | { 25 | "_gvid": 10, 26 | "name": "7220866383477917723", 27 | "label": "/\\\\ colors = (0 :> \"red\" @@ 1 :> \"red\")\\n/\\\\ next_to_open = 1" 28 | }, 29 | { 30 | "_gvid": 11, 31 | "name": "6003703145199322104", 32 | "label": "/\\\\ colors = (0 :> \"red\" @@ 1 :> \"green\")\\n/\\\\ next_to_open = 0" 33 | }, 34 | { 35 | "_gvid": 12, 36 | "name": "5389667823581670897", 37 | "label": "/\\\\ colors = (0 :> \"red\" @@ 1 :> \"yellow\")\\n/\\\\ next_to_open = 0" 38 | } 39 | ], 40 | "edges": [ 41 | { 42 | "_gvid": 0, 43 | "tail": 7, 44 | "head": 8, 45 | "color": "black", 46 | "fontcolor": "black", 47 | "label": "" 48 | }, 49 | { 50 | "_gvid": 1, 51 | "tail": 8, 52 | "head": 9, 53 | "color": "black", 54 | "fontcolor": "black", 55 | "label": "" 56 | }, 57 | { 58 | "_gvid": 2, 59 | "tail": 9, 60 | "head": 10, 61 | "color": "black", 62 | "fontcolor": "black", 63 | "label": "" 64 | }, 65 | { 66 | "_gvid": 3, 67 | "tail": 10, 68 | "head": 11, 69 | "color": "black", 70 | "fontcolor": "black", 71 | "label": "" 72 | }, 73 | { 74 | "_gvid": 4, 75 | "tail": 11, 76 | "head": 12, 77 | "color": "black", 78 | "fontcolor": "black", 79 | "label": "" 80 | }, 81 | { 82 | "_gvid": 5, 83 | "tail": 12, 84 | "head": 7, 85 | "color": "black", 86 | "fontcolor": "black", 87 | "label": "" 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /tla_specifications/traffic_semaphores_with_problem/states.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DiskGraph", 3 | "directed": true, 4 | "strict": true, 5 | "nodesep": "0.35", 6 | "_subgraph_cnt": 7, 7 | "objects": [ 8 | { 9 | "_gvid": 7, 10 | "name": "7210969400895743542", 11 | "label": "/\\\\ colors = (0 :> \"red\" @@ 1 :> \"red\")\\n/\\\\ next_to_open = 0", 12 | "style": "filled" 13 | }, 14 | { 15 | "_gvid": 8, 16 | "name": "8666215057606062623", 17 | "label": "/\\\\ colors = (0 :> \"green\" @@ 1 :> \"red\")\\n/\\\\ next_to_open = 1" 18 | }, 19 | { 20 | "_gvid": 9, 21 | "name": "-1145142259021417419", 22 | "label": "/\\\\ colors = (0 :> \"yellow\" @@ 1 :> \"red\")\\n/\\\\ next_to_open = 1" 23 | }, 24 | { 25 | "_gvid": 10, 26 | "name": "7220866383477917723", 27 | "label": "/\\\\ colors = (0 :> \"red\" @@ 1 :> \"red\")\\n/\\\\ next_to_open = 1" 28 | }, 29 | { 30 | "_gvid": 11, 31 | "name": "6003703145199322104", 32 | "label": "/\\\\ colors = (0 :> \"red\" @@ 1 :> \"green\")\\n/\\\\ next_to_open = 0" 33 | }, 34 | { 35 | "_gvid": 12, 36 | "name": "5389667823581670897", 37 | "label": "/\\\\ colors = (0 :> \"red\" @@ 1 :> \"yellow\")\\n/\\\\ next_to_open = 0" 38 | } 39 | ], 40 | "edges": [ 41 | { 42 | "_gvid": 0, 43 | "tail": 7, 44 | "head": 8, 45 | "color": "black", 46 | "fontcolor": "black", 47 | "label": "" 48 | }, 49 | { 50 | "_gvid": 1, 51 | "tail": 8, 52 | "head": 9, 53 | "color": "black", 54 | "fontcolor": "black", 55 | "label": "" 56 | }, 57 | { 58 | "_gvid": 2, 59 | "tail": 9, 60 | "head": 10, 61 | "color": "black", 62 | "fontcolor": "black", 63 | "label": "" 64 | }, 65 | { 66 | "_gvid": 3, 67 | "tail": 10, 68 | "head": 11, 69 | "color": "black", 70 | "fontcolor": "black", 71 | "label": "" 72 | }, 73 | { 74 | "_gvid": 4, 75 | "tail": 11, 76 | "head": 12, 77 | "color": "black", 78 | "fontcolor": "black", 79 | "label": "" 80 | }, 81 | { 82 | "_gvid": 5, 83 | "tail": 12, 84 | "head": 7, 85 | "color": "black", 86 | "fontcolor": "black", 87 | "label": "" 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # https://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # 15 | # The location of a snapshot can be provided as a file or url. Stack assumes 16 | # a snapshot provided as a file might change, whereas a url resource does not. 17 | # 18 | # resolver: ./custom-snapshot.yaml 19 | # resolver: https://example.com/snapshots/2018-01-01.yaml 20 | resolver: 21 | url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/17/11.yaml 22 | 23 | # User packages to be built. 24 | # Various formats can be used as shown in the example below. 25 | # 26 | # packages: 27 | # - some-directory 28 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 29 | # subdirs: 30 | # - auto-update 31 | # - wai 32 | packages: 33 | - . 34 | # Dependency packages to be pulled from upstream that are not in the resolver. 35 | # These entries can reference officially published versions as well as 36 | # forks / in-progress versions pinned to a git hash. For example: 37 | # 38 | # extra-deps: 39 | # - acme-missiles-0.3 40 | # - git: https://github.com/commercialhaskell/stack.git 41 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 42 | # 43 | # extra-deps: [] 44 | 45 | # Override default flag values for local packages and extra-deps 46 | # flags: {} 47 | 48 | # Extra package databases containing global packages 49 | # extra-package-dbs: [] 50 | 51 | # Control whether we use the GHC we find on the path 52 | # system-ghc: true 53 | # 54 | # Require a specific version of stack, using version ranges 55 | # require-stack-version: -any # Default 56 | # require-stack-version: ">=2.5" 57 | # 58 | # Override the architecture used by stack, especially useful on Windows 59 | # arch: i386 60 | # arch: x86_64 61 | # 62 | # Extra directories used by stack for building 63 | # extra-include-dirs: [/path/to/dir] 64 | # extra-lib-dirs: [/path/to/dir] 65 | # 66 | # Allow a newer minor version of GHC than the snapshot specifies 67 | # compiler-check: newer-minor 68 | -------------------------------------------------------------------------------- /ConfigParser.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | module ConfigParser where 5 | 6 | import qualified Head as H 7 | import Parser 8 | 9 | import Data.Aeson 10 | import qualified Data.ByteString.Lazy as B 11 | import GHC.Generics 12 | 13 | jsonFile :: FilePath 14 | jsonFile = "config-sample.json" 15 | 16 | data DistributionConfig = 17 | Config [ProcessConfig] [String] [ConstantConfig] String String String String String String [BlackboxTest] String 18 | deriving (Show, Generic) 19 | 20 | data ProcessConfig = 21 | PConfig String [H.Action] 22 | deriving (Show, Generic) 23 | 24 | data ConstantConfig = 25 | Constant String H.Value 26 | deriving (Show, Generic) 27 | 28 | data BlackboxTest = 29 | Test String String 30 | deriving (Show, Generic) 31 | 32 | instance FromJSON DistributionConfig where 33 | parseJSON = 34 | withObject "DistribuitionConfig" $ \obj -> do 35 | ps <- obj .: "processes" 36 | vs <- obj .: "shared_variables" 37 | cs <- obj .: "constants" 38 | i <- obj .: "init" 39 | n <- obj .: "next" 40 | m <- obj .: "module_name" 41 | f <- obj .: "input_format" 42 | file <- obj .: "input_file" 43 | g <- obj .: "state_graph" 44 | bs <- obj .: "blackbox_tests" 45 | dest <- obj .: "destination_folder" 46 | return (Config ps vs cs i n m f file g bs dest) 47 | 48 | instance FromJSON ProcessConfig where 49 | parseJSON = 50 | withObject "ProcessConfig" $ \obj -> do 51 | i <- obj .: "process_id" 52 | as <- obj .: "actions" 53 | case mapM parseState as of 54 | Left e -> fail ("Invalid action in:" ++ show as ++ ". Error: " ++ show e) 55 | Right cs -> return (PConfig i cs) 56 | 57 | instance FromJSON ConstantConfig where 58 | parseJSON = 59 | withObject "ConstantConfig" $ \obj -> do 60 | n <- obj .: "name" 61 | v <- obj .: "value" 62 | case parseValue v of 63 | Left e -> fail ("Invalid value in:" ++ show v ++ ". Error: " ++ show e) 64 | Right val -> return (Constant n val) 65 | 66 | instance FromJSON BlackboxTest where 67 | parseJSON = 68 | withObject "BlackboxTest" $ \obj -> do 69 | n <- obj .: "name" 70 | t <- obj .: "trace" 71 | return (Test n t) 72 | 73 | parseConfig :: FilePath -> IO (Either String DistributionConfig) 74 | parseConfig file = do 75 | content <- B.readFile file 76 | return (eitherDecode content) 77 | -- main :: IO () 78 | -- main = parseJson jsonFile >>= print 79 | 80 | processNames :: DistributionConfig -> [String] 81 | processNames (Config ps _ _ _ _ _ _ _ _ _ _) = map (\(PConfig n _) -> n) ps 82 | -------------------------------------------------------------------------------- /Head.hs: -------------------------------------------------------------------------------- 1 | module Head where 2 | 3 | type Identifier = String 4 | 5 | type Parameter = Identifier 6 | 7 | type Constant = Identifier 8 | 9 | type Variable = Identifier 10 | 11 | type Documentation = [String] 12 | 13 | type ElixirCode = String 14 | 15 | type Init = Definition 16 | 17 | type Next = Definition 18 | 19 | type Context = [(Identifier, String)] 20 | 21 | data Spec = 22 | Spec Module Identifier Identifier [Definition] 23 | deriving (Show, Eq) 24 | 25 | data Module = 26 | Module Identifier Documentation 27 | deriving (Show, Eq) 28 | 29 | data Definition 30 | = ActionDefinition Identifier [Parameter] Documentation Action 31 | | ValueDefinition Identifier [Parameter] Value 32 | | Constants [Identifier] 33 | | Variables [Identifier] 34 | | Comment String 35 | deriving (Show, Eq) 36 | 37 | data Lit 38 | = Str String 39 | | Boolean Bool 40 | | Num Integer 41 | | FullSet String 42 | | Tuple [Lit] 43 | deriving (Show, Eq) 44 | 45 | data Key 46 | = Key Lit 47 | | All Identifier Value 48 | deriving (Show, Eq) 49 | 50 | data CaseMatch 51 | = Match Value Value 52 | | DefaultMatch Value 53 | deriving (Show, Eq) 54 | 55 | data Action 56 | = Condition Value 57 | | Primed Identifier Value 58 | | Unchanged [Identifier] 59 | | ActionNot Action 60 | | ActionAnd [Action] 61 | | ActionOr [Action] 62 | | ActionCall Identifier [Value] 63 | | ActionIf Value Action Action 64 | | ActionLet [Definition] Action 65 | | Exists Identifier Value Action 66 | | ForAll Identifier Value Action 67 | deriving (Show, Eq) 68 | 69 | data Value 70 | = Equality Value Value 71 | | Inequality Value Value 72 | | Gt Value Value 73 | | Lt Value Value 74 | | Gte Value Value 75 | | Lte Value Value 76 | | RecordBelonging Value Value 77 | | RecordNotBelonging Value Value 78 | | And [Value] 79 | | Or [Value] 80 | | Not Value 81 | | If Value Value Value 82 | | ConditionCall Identifier [Value] 83 | | PExists Identifier Value Value 84 | | PForAll Identifier Value Value 85 | | Let [Definition] Value 86 | | Set [Value] 87 | | TupleVal [Value] 88 | | FunSet Value Value 89 | | FunGen Identifier Value Value 90 | | SetTimes Value Value 91 | | SetIn Value Value 92 | | SetMinus Value Value 93 | | Union Value Value 94 | | Filtered Identifier Value Value 95 | | Cardinality Value 96 | | Fold Value Value Value 97 | | Record [(Key, Value)] 98 | | RecordSet [(Key, Value)] 99 | | Except Identifier [(Value, Value)] 100 | | Case [CaseMatch] 101 | | Domain Value 102 | | Index Value Value 103 | | Range Value Value 104 | | Ref String 105 | | Neg Value 106 | | Add Value Value 107 | | Sub Value Value 108 | | Mul Value Value 109 | | Div Value Value 110 | | Mod Value Value 111 | | Lit Lit 112 | deriving (Show, Eq) 113 | -------------------------------------------------------------------------------- /elixir/test/generated_code/traffic_semaphores_test.exs: -------------------------------------------------------------------------------- 1 | defmodule TrafficSemaphoresTest do 2 | use ExUnit.Case 3 | doctest TrafficSemaphores 4 | test "fromState 7" do 5 | variables = %{ 6 | colors: %{ 0 => "red", 1 => "red" }, 7 | next_to_open: 0 8 | } 9 | 10 | expectedStates = [%{ 11 | colors: %{ 0 => "green", 1 => "red" }, 12 | next_to_open: 1 13 | }] 14 | 15 | actions = List.flatten([TrafficSemaphores_main.next(variables)]) 16 | states = Enum.map(actions, fn action -> action[:transition].(variables) end) 17 | 18 | assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 19 | end 20 | 21 | test "fromState 8" do 22 | variables = %{ 23 | colors: %{ 0 => "green", 1 => "red" }, 24 | next_to_open: 1 25 | } 26 | 27 | expectedStates = [%{ 28 | colors: %{ 0 => "yellow", 1 => "red" }, 29 | next_to_open: 1 30 | }] 31 | 32 | actions = List.flatten([TrafficSemaphores_main.next(variables)]) 33 | states = Enum.map(actions, fn action -> action[:transition].(variables) end) 34 | 35 | assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 36 | end 37 | 38 | test "fromState 9" do 39 | variables = %{ 40 | colors: %{ 0 => "yellow", 1 => "red" }, 41 | next_to_open: 1 42 | } 43 | 44 | expectedStates = [%{ 45 | colors: %{ 0 => "red", 1 => "red" }, 46 | next_to_open: 1 47 | }] 48 | 49 | actions = List.flatten([TrafficSemaphores_main.next(variables)]) 50 | states = Enum.map(actions, fn action -> action[:transition].(variables) end) 51 | 52 | assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 53 | end 54 | 55 | test "fromState 10" do 56 | variables = %{ 57 | colors: %{ 0 => "red", 1 => "red" }, 58 | next_to_open: 1 59 | } 60 | 61 | expectedStates = [%{ 62 | colors: %{ 0 => "red", 1 => "green" }, 63 | next_to_open: 0 64 | }] 65 | 66 | actions = List.flatten([TrafficSemaphores_main.next(variables)]) 67 | states = Enum.map(actions, fn action -> action[:transition].(variables) end) 68 | 69 | assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 70 | end 71 | 72 | test "fromState 11" do 73 | variables = %{ 74 | colors: %{ 0 => "red", 1 => "green" }, 75 | next_to_open: 0 76 | } 77 | 78 | expectedStates = [%{ 79 | colors: %{ 0 => "red", 1 => "yellow" }, 80 | next_to_open: 0 81 | }] 82 | 83 | actions = List.flatten([TrafficSemaphores_main.next(variables)]) 84 | states = Enum.map(actions, fn action -> action[:transition].(variables) end) 85 | 86 | assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 87 | end 88 | 89 | test "fromState 12" do 90 | variables = %{ 91 | colors: %{ 0 => "red", 1 => "yellow" }, 92 | next_to_open: 0 93 | } 94 | 95 | expectedStates = [%{ 96 | colors: %{ 0 => "red", 1 => "red" }, 97 | next_to_open: 0 98 | }] 99 | 100 | actions = List.flatten([TrafficSemaphores_main.next(variables)]) 101 | states = Enum.map(actions, fn action -> action[:transition].(variables) end) 102 | 103 | assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 104 | end 105 | 106 | end -------------------------------------------------------------------------------- /WhiteboxTestGenerator.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveGeneric #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | 4 | import Data.Aeson 5 | import qualified Data.ByteString.Lazy as B 6 | import Data.List 7 | import GHC.Generics 8 | import System.Environment 9 | 10 | import Head as H 11 | import Elixir 12 | import Helpers 13 | import Parser 14 | import ConfigParser 15 | 16 | getJSON :: FilePath -> IO B.ByteString 17 | getJSON = B.readFile 18 | 19 | data Graph = 20 | Graph 21 | { nodes :: [Node] 22 | , edges :: [Edge] 23 | } 24 | deriving (Show, Generic) 25 | 26 | data Node = 27 | Node 28 | { nodeId :: Integer 29 | , label :: String 30 | } 31 | deriving (Show, Generic) 32 | 33 | data Edge = 34 | Edge 35 | { nodeFrom :: Integer 36 | , nodeTo :: Integer 37 | } 38 | deriving (Show, Generic) 39 | 40 | instance FromJSON Graph where 41 | parseJSON (Object v) = Graph <$> v .: "objects" <*> v .: "edges" 42 | 43 | instance FromJSON Node where 44 | parseJSON (Object v) = Node <$> v .: "_gvid" <*> v .: "label" 45 | 46 | instance FromJSON Edge where 47 | parseJSON (Object v) = Edge <$> v .: "tail" <*> v .: "head" 48 | 49 | genTests :: String -> [String] -> Graph -> Either String String 50 | genTests m ms Graph {nodes = ns, edges = es} = 51 | case traverse (testForNode (map ((m ++ "_") ++) ms) (Graph ns es)) ns of 52 | Right ts -> Right (header m ++ intercalate "\n" ts ++ "\nend") 53 | Left e -> Left e 54 | 55 | testForNode :: [String] -> Graph -> Node -> Either String String 56 | testForNode ms g Node {nodeId = i, label = l} = do 57 | vs <- toMap Node {nodeId = i, label = l} 58 | ss <- statesFromId g i >>= traverse toMap 59 | return 60 | (unlines 61 | [ "test \"fromState " ++ show i ++ "\" do" 62 | , " variables = " ++ vs 63 | , "" 64 | , " expectedStates = [" ++ intercalate ",\n" ss ++ "]" 65 | , "" 66 | , " actions = List.flatten([" ++ intercalate ", " (map (++ ".next(variables)") ms) ++ "])" 67 | , " states = Enum.map(actions, fn action -> action[:transition].(variables) end)" 68 | , "" 69 | , " assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates))" 70 | , "end" 71 | ]) 72 | 73 | statesFromId :: Graph -> Integer -> Either String [Node] 74 | statesFromId Graph {nodes = ns, edges = es} i = 75 | let edgesFromId = filter (\Edge {nodeFrom = f, nodeTo = _} -> f == i) es 76 | nodesIdsFromId = map (\Edge {nodeFrom = _, nodeTo = t} -> t) edgesFromId 77 | in traverse (findNode ns) nodesIdsFromId 78 | 79 | findNode :: [Node] -> Integer -> Either String Node 80 | findNode ns n = 81 | case find (\Node {nodeId = i, label = _} -> n == i) ns of 82 | Just node -> Right node 83 | Nothing -> Left ("Node with id " ++ show n ++ " could not be found") 84 | 85 | toMap :: Node -> Either String String 86 | toMap Node {nodeId = _, label = l} = 87 | case parseState (unescape l) of 88 | Right a -> Right (initialState [] (toValue a)) 89 | Left e -> Left (show e) 90 | 91 | 92 | unescape :: String -> String 93 | unescape [] = [] 94 | unescape [s] = [s] 95 | unescape (c1:c2:cs) = 96 | if c1 == '\\' && (c2 == '\\' || c2 == 'n') 97 | then (if c2 == 'n' 98 | then unescape cs 99 | else unescape (c2 : cs)) 100 | else c1 : unescape (c2 : cs) 101 | 102 | header :: String -> String 103 | header m = unlines ["defmodule " ++ m ++ "Test do", " use ExUnit.Case", " doctest " ++ m] 104 | 105 | generateWhiteboxTests ps (Config _ _ _ _ _ name _ _ file _ dest) = do 106 | d <- (eitherDecode <$> getJSON file) :: IO (Either String Graph) 107 | case d of 108 | Left err -> putStrLn err 109 | Right graph -> case genTests name ps graph of 110 | Left err -> putStrLn err 111 | Right s -> 112 | let f = dest ++ "/test/generated_code/" ++ snake name ++ "_test.exs" 113 | in writeFile f s 114 | 115 | 116 | main :: IO () 117 | main = do 118 | (configFile:_) <- getArgs 119 | config <- parseConfig configFile 120 | case fmap (\c -> generateWhiteboxTests (processNames c) c) config of 121 | Left err -> error err 122 | Right c -> c 123 | -------------------------------------------------------------------------------- /tla_specifications/old/TransactionCommit.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE TransactionCommit ----------------------------------------------- 2 | (***************************************************************************) 3 | (* This specification is explained in "Transaction Commit", Lecture 5 of *) 4 | (* the TLA+ Video Course. *) 5 | (***************************************************************************) 6 | CONSTANT RM 7 | VARIABLE rmState 8 | ----------------------------------------------------------------------------- 9 | TCTypeOK == 10 | (*************************************************************************) 11 | (* The type-correctness invariant *) 12 | (*************************************************************************) 13 | rmState \in [RM -> {"working", "prepared", "committed", "aborted"}] 14 | 15 | TCInit == rmState = [r \in RM |-> "working"] 16 | (*************************************************************************) 17 | (* The initial predicate. *) 18 | (*************************************************************************) 19 | 20 | canCommit == \A r \in RM : rmState[r] \in {"prepared", "committed"} 21 | (*************************************************************************) 22 | (* True iff all RMs are in the "prepared" or "committed" state. *) 23 | (*************************************************************************) 24 | 25 | notCommitted == \A r \in RM : rmState[r] # "committed" 26 | (*************************************************************************) 27 | (* True iff no resource manager has decided to commit. *) 28 | (*************************************************************************) 29 | ----------------------------------------------------------------------------- 30 | (***************************************************************************) 31 | (* We now define the actions that may be performed by the RMs, and then *) 32 | (* define the complete next-state action of the specification to be the *) 33 | (* disjunction of the possible RM actions. *) 34 | (***************************************************************************) 35 | Prepare(r) == /\ rmState[r] = "working" 36 | /\ rmState' = [rmState EXCEPT ![r] = "prepared"] 37 | 38 | Decide(r) == \/ /\ rmState[r] = "prepared" 39 | /\ canCommit 40 | /\ rmState' = [rmState EXCEPT ![r] = "committed"] 41 | \/ /\ rmState[r] \in {"working", "prepared"} 42 | /\ notCommitted 43 | /\ rmState' = [rmState EXCEPT ![r] = "aborted"] 44 | 45 | TCNext == \E r \in RM : Prepare(r) \/ Decide(r) 46 | (*************************************************************************) 47 | (* The next-state action. *) 48 | (*************************************************************************) 49 | ----------------------------------------------------------------------------- 50 | TCConsistent == 51 | (*************************************************************************) 52 | (* A state predicate asserting that two RMs have not arrived at *) 53 | (* conflicting decisions. It is an invariant of the specification. *) 54 | (*************************************************************************) 55 | \A r1, r2 \in RM : ~ /\ rmState[r1] = "aborted" 56 | /\ rmState[r2] = "committed" 57 | ----------------------------------------------------------------------------- 58 | (***************************************************************************) 59 | (* The following part of the spec is not discussed in Video Lecture 5. It *) 60 | (* will be explained in Video Lecture 8. *) 61 | (***************************************************************************) 62 | TCSpec == TCInit /\ [][TCNext]_rmState 63 | (*************************************************************************) 64 | (* The complete specification of the protocol written as a temporal *) 65 | (* formula. *) 66 | (*************************************************************************) 67 | 68 | THEOREM TCSpec => [](TCTypeOK /\ TCConsistent) 69 | (*************************************************************************) 70 | (* This theorem asserts the truth of the temporal formula whose meaning *) 71 | (* is that the state predicate TCTypeOK /\ TCInvariant is an invariant *) 72 | (* of the specification TCSpec. Invariance of this conjunction is *) 73 | (* equivalent to invariance of both of the formulas TCTypeOK and *) 74 | (* TCConsistent. *) 75 | (*************************************************************************) 76 | ================================================================================= 77 | -------------------------------------------------------------------------------- /tla_specifications/old/ewd998/AsyncTerminationDetection.tla: -------------------------------------------------------------------------------- 1 | ---------------------- MODULE AsyncTerminationDetection --------------------- 2 | (***************************************************************************) 3 | (* An abstract specification of the termination detection problem in a *) 4 | (* ring with asynchronous communication. *) 5 | (***************************************************************************) 6 | EXTENDS Naturals 7 | CONSTANT 8 | \* @type: Int; 9 | N 10 | ASSUME NAssumption == N \in Nat \ {0} 11 | 12 | Node == 0 .. N-1 13 | 14 | VARIABLES 15 | \* @type: Int -> Bool; 16 | active, \* activation status of nodes 17 | \* @type: Int -> Int; 18 | pending, \* number of messages pending at a node 19 | \* @type: Bool; 20 | terminationDetected \* has termination been detected? 21 | 22 | TypeOK == 23 | /\ active \in [Node -> BOOLEAN] 24 | /\ pending \in [Node -> Nat] 25 | /\ terminationDetected \in BOOLEAN 26 | 27 | terminated == \A n \in Node : ~ active[n] /\ pending[n] = 0 28 | 29 | (***************************************************************************) 30 | (* Initial condition: the nodes can be active or inactive, no pending *) 31 | (* messages. Termination may (but need not) be detected immediately if all *) 32 | (* nodes are inactive. *) 33 | (***************************************************************************) 34 | Init == 35 | /\ active \in [Node -> BOOLEAN] 36 | /\ pending = [n \in Node |-> 0] 37 | /\ terminationDetected \in {FALSE, terminated} 38 | 39 | Terminate(i) == \* node i terminates 40 | /\ active[i] 41 | /\ active' = [active EXCEPT ![i] = FALSE] 42 | /\ pending' = pending 43 | (* possibly (but not necessarily) detect termination if all nodes are inactive *) 44 | /\ terminationDetected' \in {terminationDetected, terminated'} 45 | 46 | SendMsg(i,j) == \* node i sends a message to node j 47 | /\ active[i] 48 | /\ pending' = [pending EXCEPT ![j] = @ + 1] 49 | /\ UNCHANGED <> 50 | 51 | RcvMsg(i) == \* node i receives a pending message 52 | /\ pending[i] > 0 53 | /\ active' = [active EXCEPT ![i] = TRUE] 54 | /\ pending' = [pending EXCEPT ![i] = @ - 1] 55 | /\ UNCHANGED terminationDetected 56 | 57 | DetectTermination == 58 | /\ terminated 59 | /\ terminationDetected' = TRUE 60 | /\ UNCHANGED <> 61 | 62 | Next == 63 | \/ \E i \in Node : RcvMsg(i) \/ Terminate(i) 64 | \/ \E i,j \in Node : SendMsg(i,j) 65 | \/ DetectTermination 66 | 67 | vars == <> 68 | Spec == /\ Init /\ [][Next]_vars 69 | /\ WF_vars(DetectTermination) 70 | \* reasonable but not necessary for detecting termination 71 | \* /\ \A i \in Node : WF_vars(Wakeup(i)) 72 | 73 | \* a temporary solution for Apalache, until it translates [][Next]_vars 74 | NextOrUnchanged == 75 | Next \/ UNCHANGED vars 76 | 77 | (***************************************************************************) 78 | (* Restrict TLC model checking to a finite fragment of the state space. *) 79 | (***************************************************************************) 80 | StateConstraint == \A n \in Node : pending[n] <= 3 81 | 82 | (***************************************************************************) 83 | (* Correctness properties. *) 84 | (***************************************************************************) 85 | Safe == terminationDetected => terminated 86 | 87 | Quiescence == [](terminated => []terminated) 88 | 89 | Live == terminated ~> terminationDetected 90 | 91 | (***************************************************************************) 92 | (* An inductive invariant to be checked with Apalache. *) 93 | (***************************************************************************) 94 | IndInv == 95 | /\ TypeOK 96 | /\ Safe 97 | 98 | \* By proving QuiescenceAsActionInv, we show that Quiescence holds true 99 | QuiescenceAsActionInv == 100 | terminated => terminated' 101 | 102 | \* @typeAlias: STATE = 103 | \* [ active: Int -> Bool, pending: Int -> Int, terminationDetected: Bool ]; 104 | \* @type: Seq(STATE) => Bool; 105 | QuiescenceAsTraceInv(hist) == 106 | LET terminatedAt(i) == 107 | \A n \in Node: ~hist[i].active[n] /\ hist[i].pending[n] = 0 108 | IN 109 | \A i \in DOMAIN hist: 110 | terminatedAt(i) => 111 | \A j \in DOMAIN hist: j >= i => terminatedAt(j) 112 | 113 | (***************************************************************************) 114 | (* Use Apalache to verify Quiescence by checking the action formula *) 115 | (* StableActionInvariant for a model with initial-state predicate TypeOK *) 116 | (* and next-state relation Next. *) 117 | (***************************************************************************) 118 | StableActionInvariant == terminated => terminated' 119 | ============================================================================= 120 | \* Modification History 121 | \* Last modified Tue Apr 12 15:04:08 CEST 2022 by merz 122 | \* Last modified Wed Jun 02 14:21:31 PDT 2021 by markus 123 | \* Created Sun Jan 10 15:19:20 CET 2021 by merz 124 | -------------------------------------------------------------------------------- /tla_specifications/old/TwoPhaseCommit/TCommit.tla: -------------------------------------------------------------------------------- 1 | \* This code originally comes from Leslie Lamport's video series 2 | \* http://lamport.azurewebsites.net/video/videos.html 3 | ------------------------------ MODULE TCommit ------------------------------ 4 | (***************************************************************************) 5 | (* This specification is explained in "Transaction Commit", Lecture 5 of *) 6 | (* the TLA+ Video Course. *) 7 | (***************************************************************************) 8 | CONSTANT RM \* The set of participating resource managers 9 | 10 | VARIABLE rmState \* rmState[rm] is the state of resource manager r. 11 | ----------------------------------------------------------------------------- 12 | TCTypeOK == 13 | (*************************************************************************) 14 | (* The type-correctness invariant *) 15 | (*************************************************************************) 16 | rmState \in [RM -> {"working", "prepared", "committed", "aborted"}] 17 | 18 | TCInit == rmState = [r \in RM |-> "working"] 19 | (*************************************************************************) 20 | (* The initial predicate. *) 21 | (*************************************************************************) 22 | 23 | canCommit == \A r \in RM : rmState[r] \in {"prepared", "committed"} 24 | (*************************************************************************) 25 | (* True iff all RMs are in the "prepared" or "committed" state. *) 26 | (*************************************************************************) 27 | 28 | notCommitted == \A r \in RM : rmState[r] # "committed" 29 | (*************************************************************************) 30 | (* True iff no resource manager has decided to commit. *) 31 | (*************************************************************************) 32 | ----------------------------------------------------------------------------- 33 | (***************************************************************************) 34 | (* We now define the actions that may be performed by the RMs, and then *) 35 | (* define the complete next-state action of the specification to be the *) 36 | (* disjunction of the possible RM actions. *) 37 | (***************************************************************************) 38 | Prepare(r) == /\ rmState[r] = "working" 39 | /\ rmState' = [rmState EXCEPT ![r] = "prepared"] 40 | 41 | Decide(r) == \/ /\ rmState[r] = "prepared" 42 | /\ canCommit 43 | /\ rmState' = [rmState EXCEPT ![r] = "committed"] 44 | \/ /\ rmState[r] \in {"working", "prepared"} 45 | /\ notCommitted 46 | /\ rmState' = [rmState EXCEPT ![r] = "aborted"] 47 | 48 | TCNext == \E r \in RM : Prepare(r) \/ Decide(r) 49 | (*************************************************************************) 50 | (* The next-state action. *) 51 | (*************************************************************************) 52 | ----------------------------------------------------------------------------- 53 | TCConsistent == 54 | (*************************************************************************) 55 | (* A state predicate asserting that two RMs have not arrived at *) 56 | (* conflicting decisions. It is an invariant of the specification. *) 57 | (*************************************************************************) 58 | \A r1, r2 \in RM : ~ /\ rmState[r1] = "aborted" 59 | /\ rmState[r2] = "committed" 60 | ----------------------------------------------------------------------------- 61 | (***************************************************************************) 62 | (* The following part of the spec is not discussed in Video Lecture 5. It *) 63 | (* will be explained in Video Lecture 8. *) 64 | (***************************************************************************) 65 | TCSpec == TCInit /\ [][TCNext]_rmState 66 | (*************************************************************************) 67 | (* The complete specification of the protocol written as a temporal *) 68 | (* formula. *) 69 | (*************************************************************************) 70 | 71 | THEOREM TCSpec => [](TCTypeOK /\ TCConsistent) 72 | (*************************************************************************) 73 | (* This theorem asserts the truth of the temporal formula whose meaning *) 74 | (* is that the state predicate TCTypeOK /\ TCInvariant is an invariant *) 75 | (* of the specification TCSpec. Invariance of this conjunction is *) 76 | (* equivalent to invariance of both of the formulas TCTypeOK and *) 77 | (* TCConsistent. *) 78 | (*************************************************************************) 79 | 80 | ============================================================================= 81 | \* Modification History 82 | \* Last modified Thu Sep 14 22:04:45 EDT 2017 by jay1512 83 | \* Created Thu Sep 14 22:04:25 EDT 2017 by jay1512 84 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/ew_d840.ex: -------------------------------------------------------------------------------- 1 | defmodule EWD840 do 2 | def shared_variables do 3 | [ 4 | :tcolor, 5 | :tpos, 6 | :active, 7 | ] 8 | end 9 | require Oracle 10 | @n 3 11 | def n, do: @n 12 | 13 | 14 | def max_n(variables) do 15 | 20 16 | end 17 | 18 | 19 | def color(variables) do 20 | MapSet.new(["white", "black"]) 21 | end 22 | 23 | 24 | def const_init4(variables) do 25 | MapSet.member?(MapSet.new([4]), @n) 26 | end 27 | 28 | 29 | def const_init10(variables) do 30 | MapSet.member?(MapSet.new([10]), @n) 31 | end 32 | 33 | 34 | def const_init_all20(variables) do 35 | MapSet.member?(2..50, @n) 36 | end 37 | 38 | 39 | def nodes(variables) do 40 | MapSet.new(Enum.filter(0..max_n(variables), fn(i) -> i < @n end)) 41 | end 42 | 43 | 44 | def initiate_probe_condition(variables) do 45 | Enum.all?([variables[:tpos] == 0, variables[:tcolor] == "black" or variables[:color][0] == "black"]) 46 | end 47 | 48 | def initiate_probe(variables) do 49 | %{ 50 | tpos: @n - 1, 51 | tcolor: "white", 52 | active: variables[:active], 53 | color: variables[:color]|>Map.put(0, "white") 54 | } 55 | end 56 | 57 | 58 | def pass_token_condition(variables, i) do 59 | Enum.all?([variables[:tpos] == i, not variables[:active][i] or variables[:color][i] == "black" or variables[:tcolor] == "black"]) 60 | end 61 | 62 | def pass_token(variables, i) do 63 | %{ 64 | tpos: i - 1, 65 | tcolor: (if variables[:color][i] == "black", do: "black", else: variables[:tcolor]), 66 | active: variables[:active], 67 | color: variables[:color]|>Map.put(i, "white") 68 | } 69 | end 70 | 71 | 72 | def send_msg_condition(variables, i, j) do 73 | variables[:active][i] 74 | end 75 | 76 | def send_msg(variables, i, j) do 77 | %{ 78 | active: variables[:active]|>Map.put(j, true), 79 | color: variables[:color]|>Map.put(i, (if j > i, do: "black", else: variables[:color][i])), 80 | tpos: variables[:tpos], 81 | tcolor: variables[:tcolor] 82 | } 83 | end 84 | 85 | 86 | def deactivate_condition(variables, i) do 87 | variables[:active][i] 88 | end 89 | 90 | def deactivate(variables, i) do 91 | %{ 92 | active: variables[:active]|>Map.put(i, false), 93 | color: variables[:color], 94 | tpos: variables[:tpos], 95 | tcolor: variables[:tcolor] 96 | } 97 | end 98 | 99 | 100 | def vars(variables) do 101 | {variables[:active], variables[:color], variables[:tpos], variables[:tcolor]} 102 | end 103 | 104 | 105 | def token_always_black(variables) do 106 | variables[:tcolor] == "black" 107 | end 108 | 109 | 110 | def termination_detected(variables) do 111 | variables[:tpos] == 0 and variables[:tcolor] == "white" and variables[:color][0] == "white" and not variables[:active][0] 112 | end 113 | 114 | 115 | # "NeverChangeColor": OperEx "GLOBALLY" [OperEx "STUTTER" [OperEx "UNCHANGED" [NameEx "color"],OperEx "OPER_APP" [NameEx "vars"]]] 116 | # "TerminationDetection": OperEx "IMPLIES" [OperEx "OPER_APP" [NameEx "terminationDetected"],OperEx "FORALL3" [NameEx "i",OperEx "OPER_APP" [NameEx "Nodes"],OperEx "NOT" [OperEx "FUN_APP" [NameEx "active",NameEx "i"]]]] 117 | # "Liveness": OperEx "LEADS_TO" [OperEx "FORALL3" [NameEx "i",OperEx "OPER_APP" [NameEx "Nodes"],OperEx "NOT" [OperEx "FUN_APP" [NameEx "active",NameEx "i"]]],OperEx "OPER_APP" [NameEx "terminationDetected"]] 118 | # "FalseLiveness": OperEx "LEADS_TO" [OperEx "FORALL3" [NameEx "i",OperEx "OPER_APP" [NameEx "Nodes"],OperEx "GLOBALLY" [OperEx "EVENTUALLY" [OperEx "NOT" [OperEx "FUN_APP" [NameEx "active",NameEx "i"]]]]],OperEx "OPER_APP" [NameEx "terminationDetected"]] 119 | # "Inv": OperEx "OR" [OperEx "LABEL" [OperEx "FORALL3" [NameEx "i",OperEx "OPER_APP" [NameEx "Nodes"],OperEx "IMPLIES" [OperEx "LT" [NameEx "tpos",NameEx "i"],OperEx "NOT" [OperEx "FUN_APP" [NameEx "active",NameEx "i"]]]],ValEx (TlaStr "P0")],OperEx "LABEL" [OperEx "EXISTS3" [NameEx "j",OperEx "OPER_APP" [NameEx "Nodes"],OperEx "IMPLIES" [OperEx "AND" [OperEx "LE" [ValEx (TlaInt 0),NameEx "j"],OperEx "LE" [NameEx "j",NameEx "tpos"]],OperEx "EQ" [OperEx "FUN_APP" [NameEx "color",NameEx "j"],ValEx (TlaStr "black")]]],ValEx (TlaStr "P1")],OperEx "LABEL" [OperEx "EQ" [NameEx "tcolor",ValEx (TlaStr "black")],ValEx (TlaStr "P2")]] 120 | # "SpecWFNext": OperEx "AND" [OperEx "AND" [OperEx "OPER_APP" [NameEx "Init"],OperEx "GLOBALLY" [OperEx "STUTTER" [OperEx "OPER_APP" [NameEx "Next"],OperEx "OPER_APP" [NameEx "vars"]]]],OperEx "WEAK_FAIRNESS" [OperEx "OPER_APP" [NameEx "vars"],OperEx "OPER_APP" [NameEx "Next"]]] 121 | # "CheckInductiveSpec": OperEx "AND" [OperEx "OPER_APP" [NameEx "Inv"],OperEx "GLOBALLY" [OperEx "STUTTER" [OperEx "OPER_APP" [NameEx "Next"],OperEx "OPER_APP" [NameEx "vars"]]]] 122 | 123 | def decide_action(oracle, variables, actions, step) do 124 | different_states = Enum.uniq(Enum.map(actions, fn(action) -> action[:transition].(variables) end)) 125 | 126 | cond do 127 | Enum.count(different_states) == 1 -> 128 | Enum.at(different_states, 0) 129 | true -> 130 | send oracle, {:choose, self(), different_states} 131 | 132 | receive do 133 | {:ok, n} -> Enum.at(different_states, n) 134 | {:cancel} -> variables 135 | {:stop} -> exit(0) 136 | end 137 | end 138 | end 139 | 140 | def wait_lock(oracle) do 141 | send(oracle, {:lock, self()}) 142 | receive do 143 | {:lock_acquired, state} -> IO.puts("Lock acquired"); {map, _} = Map.split(state, shared_variables); map 144 | {:already_locked, _} -> IO.puts("Lock refused"); Process.sleep(1000); wait_lock(oracle) 145 | end 146 | end 147 | end 148 | 149 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/Apalache.tla: -------------------------------------------------------------------------------- 1 | --------------------------- MODULE Apalache ----------------------------------- 2 | (* 3 | * This is a standard module for use with the Apalache model checker. 4 | * The meaning of the operators is explained in the comments. 5 | * Many of the operators serve as additional annotations of their arguments. 6 | * As we like to preserve compatibility with TLC and TLAPS, we define the 7 | * operator bodies by erasure. The actual interpretation of the operators is 8 | * encoded inside Apalache. For the moment, these operators are mirrored in 9 | * the class at.forsyte.apalache.tla.lir.oper.ApalacheOper. 10 | * 11 | * Igor Konnov, Jure Kukovec, Informal Systems 2020-2022 12 | *) 13 | 14 | (** 15 | * An assignment of an expression e to a state variable x. Typically, one 16 | * uses the non-primed version of x in the initializing predicate Init and 17 | * the primed version of x (that is, x') in the transition predicate Next. 18 | * Although TLA+ does not have a concept of a variable assignment, we find 19 | * this concept extremely useful for symbolic model checking. In pure TLA+, 20 | * one would simply write x = e, or x \in {e}. 21 | * 22 | * Apalache automatically converts some expressions of the form 23 | * x = e or x \in {e} into assignments. However, if you like to annotate 24 | * assignments by hand, you can use this operator. 25 | * 26 | * For a further discussion on that matter, see: 27 | * https://github.com/informalsystems/apalache/blob/unstable/docs/src/idiomatic/001assignments.md 28 | *) 29 | __x := __e == __x = __e 30 | 31 | (** 32 | * A generator of a data structure. Given a positive integer `bound`, and 33 | * assuming that the type of the operator application is known, we 34 | * recursively generate a TLA+ data structure as a tree, whose width is 35 | * bound by the number `bound`. 36 | * 37 | * The body of this operator is redefined by Apalache. 38 | *) 39 | Gen(__size) == {} 40 | 41 | (** 42 | * Non-deterministically pick a value out of the set `S`, if `S` is non-empty. 43 | * If `S` is empty, return some value of the proper type. This can be 44 | * understood as a non-deterministic version of CHOOSE x \in S: TRUE. 45 | * 46 | * @type: Set(a) => a; 47 | *) 48 | Guess(__S) == 49 | \* Since this is not supported by TLC, 50 | \* we fall back to the deterministic version for TLC. 51 | \* Apalache redefines the operator `Guess` as explained above. 52 | CHOOSE __x \in __S: TRUE 53 | 54 | (** 55 | * Convert a set of pairs S to a function F. Note that if S contains at least 56 | * two pairs <> and <> such that x = u and y /= v, 57 | * then F is not uniquely defined. We use CHOOSE to resolve this ambiguity. 58 | * Apalache implements a more efficient encoding of this operator 59 | * than the default one. 60 | * 61 | * @type: Set(<>) => (a -> b); 62 | *) 63 | SetAsFun(__S) == 64 | LET __Dom == { __x: <<__x, __y>> \in __S } 65 | __Rng == { __y: <<__x, __y>> \in __S } 66 | IN 67 | [ __x \in __Dom |-> CHOOSE __y \in __Rng: <<__x, __y>> \in __S ] 68 | 69 | (** 70 | * A sequence constructor that avoids using a function constructor. 71 | * Since Apalache is typed, this operator is more efficient than 72 | * FunAsSeq([ i \in 1..N |-> F(i) ]). Apalache requires N to be 73 | * a constant expression. 74 | * 75 | * @type: (Int, (Int -> a)) => Seq(a); 76 | *) 77 | LOCAL INSTANCE Integers 78 | MkSeq(__N, __F(_)) == 79 | \* This is the TLC implementation. Apalache does it differently. 80 | [ __i \in (1..__N) |-> __F(__i) ] 81 | 82 | \* required by our default definition of FoldSeq and FunAsSeq 83 | LOCAL INSTANCE Sequences 84 | 85 | (** 86 | * As TLA+ is untyped, one can use function- and sequence-specific operators 87 | * interchangeably. However, to maintain correctness w.r.t. our type-system, 88 | * an explicit cast is needed when using functions as sequences. 89 | * FunAsSeq reinterprets a function over integers as a sequence. 90 | * 91 | * The parameters have the following meaning: 92 | * 93 | * - fn is the function from 1..len that should be interpreted as a sequence. 94 | * - len is the length of the sequence, len = Cardinality(DOMAIN fn), 95 | * len may be a variable, a computable expression, etc. 96 | * - capacity is a static upper bound on the length, that is, len <= capacity. 97 | * 98 | * @type: ((Int -> a), Int, Int) => Seq(a); 99 | *) 100 | FunAsSeq(__fn, __len, __capacity) == 101 | LET __FunAsSeq_elem_ctor(__i) == __fn[__i] IN 102 | SubSeq(MkSeq(__capacity, __FunAsSeq_elem_ctor), 1, __len) 103 | 104 | (** 105 | * Annotating an expression \E x \in S: P as Skolemizable. That is, it can 106 | * be replaced with an expression c \in S /\ P(c) for a fresh constant c. 107 | * Not every exisential can be replaced with a constant, this should be done 108 | * with care. Apalache detects Skolemizable expressions by static analysis. 109 | *) 110 | Skolem(__e) == __e 111 | 112 | (** 113 | * A hint to the model checker to expand a set S, instead of dealing 114 | * with it symbolically. Apalache finds out which sets have to be expanded 115 | * by static analysis. 116 | *) 117 | Expand(__S) == __S 118 | 119 | (** 120 | * A hint to the model checker to replace its argument Cardinality(S) >= k 121 | * with a series of existential quantifiers for a constant k. 122 | * Similar to Skolem, this has to be done carefully. Apalache automatically 123 | * places this hint by static analysis. 124 | *) 125 | ConstCardinality(__cardExpr) == __cardExpr 126 | 127 | (** 128 | * The folding operator, used to implement computation over a set. 129 | * Apalache implements a more efficient encoding than the one below. 130 | * (from the community modules). 131 | * 132 | * @type: ((a, b) => a, a, Set(b)) => a; 133 | *) 134 | RECURSIVE ApaFoldSet(_, _, _) 135 | ApaFoldSet(__Op(_,_), __v, __S) == 136 | IF __S = {} 137 | THEN __v 138 | ELSE LET __w == CHOOSE __x \in __S: TRUE IN 139 | LET __T == __S \ {__w} IN 140 | ApaFoldSet(__Op, __Op(__v,__w), __T) 141 | 142 | (** 143 | * The folding operator, used to implement computation over a sequence. 144 | * Apalache implements a more efficient encoding than the one below. 145 | * (from the community modules). 146 | * 147 | * @type: ((a, b) => a, a, Seq(b)) => a; 148 | *) 149 | RECURSIVE ApaFoldSeqLeft(_, _, _) 150 | ApaFoldSeqLeft(__Op(_,_), __v, __seq) == 151 | IF __seq = <<>> 152 | THEN __v 153 | ELSE ApaFoldSeqLeft(__Op, __Op(__v, Head(__seq)), Tail(__seq)) 154 | 155 | =============================================================================== 156 | -------------------------------------------------------------------------------- /Helpers.hs: -------------------------------------------------------------------------------- 1 | module Helpers where 2 | 3 | import Data.List 4 | import Data.List.Extra 5 | import qualified Text.Casing as Casing -- cabal install casing 6 | 7 | import DocHandler 8 | import Head 9 | import Snippets 10 | 11 | -- (MOD) helpers 12 | moduleHeader name (Module m doc) shared imp = 13 | "defmodule " ++ 14 | name ++ 15 | " do\n" ++ 16 | ident 17 | (moduleDoc doc ++ 18 | sharedVariablesDeclaration shared ++ 19 | oracleDelaration ++ 20 | if imp 21 | then "import " ++ m ++ "\n\n" 22 | else "") 23 | 24 | sharedVariablesDeclaration :: [String] -> String 25 | sharedVariablesDeclaration [] = "" 26 | sharedVariablesDeclaration shared = 27 | unlines ([ "def shared_variables do", " [" ] ++ map (\v -> " :" ++ snake v ++ ",") shared ++ [" ]", "end"]) 28 | 29 | moduleContext (Module m _) = [(m, "module")] 30 | 31 | -- (CALL) helpers 32 | call g i [] = snake i 33 | call g i ps = let divider = if (i, "local") `elem` g then "." else "" 34 | in snake i ++ divider ++ "(" ++ intercalate ", " ps ++ ")" 35 | 36 | -- (IF) helpers 37 | ifExpr c t e = "(if " ++ c ++ ", do: " ++ t ++ ", else: " ++ e ++ ")" 38 | 39 | -- (REC-LIT) helpers 40 | isLiteral ((Key _), _) = True 41 | isLiteral _ = False 42 | 43 | -- (INFO-*) helpers 44 | actionName (ActionCall i ps) = i ++ "(" ++ intercalate ", " (map interpolate ps) ++ ")" 45 | actionName a = escape (show a) 46 | 47 | escape cs = foldr (++) "" (map escape' cs) 48 | 49 | escape' :: Char -> String 50 | escape' c 51 | | c `elem` regexChars = '\\' : [c] 52 | | otherwise = [c] 53 | where 54 | regexChars = "\\+()^$.{}]|\"" 55 | 56 | -- Others 57 | enclose s = "(" ++ s ++ ")" 58 | 59 | cFold :: [ElixirCode] -> ElixirCode 60 | cFold [] = "true" 61 | cFold [c] = c 62 | cFold cs = let (preassignments, conditions) = partition preassignment cs 63 | in intercalate "" (map (++ "\n") preassignments) ++ "Enum.all?([" ++ intercalate ", " conditions ++ "])" 64 | 65 | aFold :: [ElixirCode] -> ElixirCode 66 | aFold [] = "%{}" 67 | aFold as = 68 | let (preassignments, as') = partition preassignment as 69 | (postAssignments, actions) = partition postassignment as' 70 | kvs = intercalate ",\n" (map keyValue actions) 71 | initialVariables = 72 | case actions of 73 | [] -> [] 74 | _ -> ["%{\n" ++ ident kvs ++ "\n}"] 75 | in intercalate "" (map (++ "\n") preassignments) ++ mapMerge (initialVariables ++ postAssignments) 76 | 77 | orFold :: [ElixirCode] -> ElixirCode 78 | orFold [] = "true" 79 | orFold [c] = c 80 | orFold cs = "Enum.any?([" ++ intercalate ", " cs ++ "])" 81 | 82 | isUnchanged (Unchanged _) = True 83 | isUnchanged _ = False 84 | 85 | allUnchanged xs = dropWhile isUnchanged xs == [] 86 | 87 | keyValue a = drop 3 (dropEnd 2 a) 88 | 89 | mapMerge [m] = m 90 | mapMerge (m:ms) = "Map.merge(\n " ++ m ++ ",\n" ++ ident (mapMerge ms) ++ ")\n" 91 | 92 | preassignment as = any (isPrefixOf " = ") (tails as) 93 | postassignment as = 94 | (head as) == '(' || 95 | take 2 as == "if" || dropWhile (/= ':') as == [] || take 4 as == "Enum" || take 3 as == "Map" || take 4 as == "List" 96 | 97 | interpolate (Lit (Str i)) = i 98 | interpolate (Ref i) = "#{inspect " ++ i ++ "}" 99 | interpolate i = show i 100 | 101 | declaration i ps = "def " ++ snake i ++ "(" ++ intercalate ", " ("variables" : ps) ++ ") do\n" 102 | 103 | identAndSeparate sep ls = (intercalate (sep ++ "\n") (map ((++) " ") ls)) 104 | 105 | unzipAndFold :: [([a], [b])] -> ([a], [b]) 106 | unzipAndFold = foldr (\x (a, b) -> (fst x ++ a, snd x ++ b)) ([], []) 107 | 108 | snake i = Casing.toQuietSnake (Casing.fromAny i) 109 | 110 | pascal i = Casing.toPascal (Casing.fromAny i) 111 | 112 | ident block = intercalate "\n" (map tabIfline (lines block)) 113 | 114 | mapAndJoin f ls = intercalate "\n" (map f ls) 115 | 116 | tabIfline [] = [] 117 | tabIfline xs = " " ++ xs 118 | 119 | isNamed i (ActionDefinition id _ _ _) = i == id 120 | isNamed i (ValueDefinition id _ _) = i == id 121 | isNamed _ _ = False 122 | 123 | name (ActionDefinition i _ _ _) = i 124 | name (ValueDefinition i _ _) = i 125 | name _ = "" 126 | 127 | moduleName (Module i _) = i 128 | 129 | partitionCondition (Condition c) = [c] 130 | partitionCondition (ActionCall c ps) = [ConditionCall c ps] 131 | partitionCondition (ActionOr cs) = 132 | let ics = foldr (++) [] (map partitionCondition cs) 133 | in if ics /= [] 134 | then [Or ics] 135 | else [] 136 | partitionCondition (ActionAnd cs) = 137 | let ics = foldr (++) [] (map partitionCondition cs) 138 | in if ics /= [] 139 | then [And ics] 140 | else [] 141 | partitionCondition (Exists i v (ActionOr cs)) = 142 | let ics = foldr (++) [] (map partitionCondition cs) 143 | in if ics /= [] 144 | then [Or ics] 145 | else [] 146 | partitionCondition _ = [] 147 | 148 | specialDef :: String -> String -> Definition -> Bool 149 | specialDef _ _ (Constants _) = True 150 | specialDef _ _ (Variables _) = True 151 | specialDef i n d = (isNamed i d) || (isNamed n d) 152 | 153 | findConstants ds = 154 | concat 155 | (map 156 | (\d -> 157 | case d of 158 | Constants cs -> cs 159 | _ -> []) 160 | ds) 161 | 162 | findVariables ds = 163 | concat 164 | (map 165 | (\d -> 166 | case d of 167 | Variables cs -> cs 168 | _ -> []) 169 | ds) 170 | 171 | findIdentifier i ds = 172 | case find (isNamed i) ds of 173 | Just a -> a 174 | Nothing -> error ("Definition not found: " ++ show i ++ " in " ++ show ds) 175 | 176 | starterTask mName name init = 177 | unlines 178 | [ "defmodule Mix.Tasks." ++ pascal (mName ++ "_" ++ name ++ "Starter") ++ " do" 179 | , " @moduledoc \"Printed when the user requests `mix help echo`\"" 180 | , " @shortdoc \"Echoes arguments\"" 181 | , " use Mix.Task" 182 | , " import " ++ mName 183 | , " import " ++ mName ++ "_" ++ name 184 | , "" 185 | , " @impl Mix.Task" 186 | , " def run(args) do" 187 | , " variables = %{}" 188 | , " initial_state = " ++ init 189 | , " oracle_host = String.to_atom(Enum.at(args, 0))" 190 | , " Node.connect(oracle_host)" 191 | , " oracle_pid = find_oracle()" 192 | , " IO.puts(inspect(oracle_pid))" 193 | , " main(oracle_pid, initial_state, 0)" 194 | , " end" 195 | , "" 196 | , " def find_oracle() do" 197 | , " o = :global.whereis_name(\"oracle\")" 198 | , " if o == :undefined do" 199 | , " find_oracle()" 200 | , " else" 201 | , " o" 202 | , " end" 203 | , " end" 204 | , "end" 205 | ] 206 | 207 | tracerStarterTask name trace = 208 | unlines 209 | [ "defmodule Mix.Tasks.Startmodel do" 210 | , " @moduledoc \"Printed when the user requests `mix help echo`\"" 211 | , " @shortdoc \"Echoes arguments\"" 212 | , " use Mix.Task" 213 | , "" 214 | , " @impl Mix.Task" 215 | , " def run(_) do" 216 | , " trace = [" 217 | ] ++ 218 | trace ++ 219 | unlines 220 | [ " ]" 221 | , "" 222 | , " oracle = spawn(TraceCheckerOracle, :start, [trace])" 223 | , " " ++ name ++ ".main(oracle, Enum.at(trace, 0), 0)" 224 | , " end" 225 | , "end" 226 | ] 227 | 228 | toValue :: Action -> Value 229 | toValue (ActionAnd as) = And (map toValue as) 230 | toValue (Condition v) = v 231 | toValue (ActionCall i ps) = ConditionCall i ps 232 | toValue a = error("Not a value: " ++ show a) 233 | -------------------------------------------------------------------------------- /tla_specifications/old/ewd998/Functions.tla: -------------------------------------------------------------------------------- 1 | ------------------------------ MODULE Functions ----------------------------- 2 | (***************************************************************************) 3 | (* `^{\large\bf \vspace{12pt} *) 4 | (* Notions about functions including injection, surjection, and bijection.*) 5 | (* Originally contributed by Tom Rodeheffer, MSR. *) 6 | (* \vspace{12pt}}^' *) 7 | (***************************************************************************) 8 | 9 | LOCAL INSTANCE Folds 10 | 11 | (***************************************************************************) 12 | (* Restriction of a function to a set (should be a subset of the domain). *) 13 | (***************************************************************************) 14 | Restrict(f,S) == [ x \in S |-> f[x] ] 15 | 16 | (***************************************************************************) 17 | (* Range of a function. *) 18 | (* Note: The image of a set under function f can be defined as *) 19 | (* Range(Restrict(f,S)). *) 20 | (***************************************************************************) 21 | Range(f) == { f[x] : x \in DOMAIN f } 22 | 23 | 24 | (***************************************************************************) 25 | (* The inverse of a function. *) 26 | (* Example: *) 27 | (* LET f == ("a" :> 0 @@ "b" :> 1 @@ "c" :> 2) *) 28 | (* IN Inverse(f, DOMAIN f, Range(f)) = *) 29 | (* (0 :> "a" @@ 1 :> "b" @@ 2 :> "c") *) 30 | (* Example: *) 31 | (* LET f == ("a" :> 0 @@ "b" :> 1 @@ "c" :> 2) *) 32 | (* IN Inverse(f, DOMAIN f, {1,3}) = *) 33 | (* 1 :> "b" @@ 3 :> "a") *) 34 | (***************************************************************************) 35 | Inverse(f,S,T) == [t \in T |-> CHOOSE s \in S : t \in Range(f) => f[s] = t] 36 | 37 | (***************************************************************************) 38 | (* The inverse of a function. *) 39 | (***************************************************************************) 40 | AntiFunction(f) == Inverse(f, DOMAIN f, Range(f)) 41 | 42 | (***************************************************************************) 43 | (* A function is injective iff it maps each element in its domain to a *) 44 | (* distinct element. *) 45 | (* *) 46 | (* This definition is overridden by TLC in the Java class SequencesExt. *) 47 | (* The operator is overridden by the Java method with the same name. *) 48 | (***************************************************************************) 49 | IsInjective(f) == \A a,b \in DOMAIN f : f[a] = f[b] => a = b 50 | 51 | (***************************************************************************) 52 | (* Set of injections between two sets. *) 53 | (***************************************************************************) 54 | Injection(S,T) == { M \in [S -> T] : IsInjective(M) } 55 | 56 | 57 | (***************************************************************************) 58 | (* A map is a surjection iff for each element in the range there is some *) 59 | (* element in the domain that maps to it. *) 60 | (***************************************************************************) 61 | Surjection(S,T) == { M \in [S -> T] : \A t \in T : \E s \in S : M[s] = t } 62 | 63 | 64 | (***************************************************************************) 65 | (* A map is a bijection iff it is both an injection and a surjection. *) 66 | (***************************************************************************) 67 | Bijection(S,T) == Injection(S,T) \cap Surjection(S,T) 68 | 69 | 70 | (***************************************************************************) 71 | (* An injection, surjection, or bijection exists if the corresponding set *) 72 | (* is nonempty. *) 73 | (***************************************************************************) 74 | ExistsInjection(S,T) == Injection(S,T) # {} 75 | ExistsSurjection(S,T) == Surjection(S,T) # {} 76 | ExistsBijection(S,T) == Bijection(S,T) # {} 77 | 78 | -------------------------------------------------------------------------------- 79 | 80 | FoldFunction(op(_,_), base, fun) == 81 | (***************************************************************************) 82 | (* Applies the binary function op on all elements of seq in arbitrary *) 83 | (* order starting with op(f[k], base). The resulting function is: *) 84 | (* op(f[i],op(f[j], ..., op(f[k],base) ...)) *) 85 | (* *) 86 | (* op must be associative and commutative, because we can not assume a *) 87 | (* particular ordering of i, j, and k *) 88 | (* *) 89 | (* Example: *) 90 | (* FoldFunction(LAMBDA x,y: {x} \cup y, {}, <<1,2,1>>) = {1,2} *) 91 | (***************************************************************************) 92 | MapThenFoldSet(op, base, LAMBDA i : fun[i], LAMBDA s: CHOOSE x \in s : TRUE, DOMAIN fun) 93 | 94 | 95 | FoldFunctionOnSet(op(_,_), base, fun, indices) == 96 | (***************************************************************************) 97 | (* Applies the binary function op on the given indices of seq in arbitrary *) 98 | (* order starting with op(f[k], base). The resulting function is: *) 99 | (* op(f[i],op(f[j], ..., op(f[k],base) ...)) *) 100 | (* *) 101 | (* op must be associative and commutative, because we can not assume a *) 102 | (* particular ordering of i, j, and k *) 103 | (* *) 104 | (* indices must be a subset of DOMAIN(fun) *) 105 | (* *) 106 | (* Example: *) 107 | (* FoldFunctionOnSet(LAMBDA x,y: {x} \cup y, {}, <<1,2>>, {}) = {} *) 108 | (***************************************************************************) 109 | MapThenFoldSet(op, base, LAMBDA i : fun[i], LAMBDA s: CHOOSE x \in s : TRUE, indices) 110 | 111 | ============================================================================= 112 | \* Modification History 113 | \* Last modified Mon Apr 05 03:25:53 CEST 2021 by marty 114 | \* Last modified Sun Dec 27 09:38:06 CET 2020 by merz 115 | \* Last modified Wed Jun 05 12:14:19 CEST 2013 by bhargav 116 | \* Last modified Fri May 03 12:55:35 PDT 2013 by tomr 117 | \* Created Thu Apr 11 10:30:48 PDT 2013 by tomr 118 | -------------------------------------------------------------------------------- /elixir/lib/generated_code/er_c20.ex: -------------------------------------------------------------------------------- 1 | defmodule ERC20 do 2 | def shared_variables do 3 | [ 4 | :pending_transactions, 5 | :last_tx, 6 | :next_tx_id, 7 | :balance_of, 8 | :allowance, 9 | ] 10 | end 11 | require Oracle 12 | @amounts MapSet.new([0, 1, 2, 3, 98, 100, 102]) 13 | def amounts, do: @amounts 14 | 15 | 16 | @addr MapSet.new(["Alice_OF_ADDR", "Bob_OF_ADDR", "Eve_OF_ADDR"]) 17 | def addr, do: @addr 18 | 19 | 20 | def submit_transfer_condition(variables, _sender, _toAddr, _value) do 21 | new_tx = fn(variables) -> %{ "id" => variables[:next_tx_id], "tag" => "transfer", "fail" => false, "sender" => _sender, "toAddr" => _toAddr, "value" => _value }end 22 | Enum.all?([true]) 23 | end 24 | 25 | def submit_transfer(variables, _sender, _toAddr, _value) do 26 | new_tx = fn(variables) -> %{ "id" => variables[:next_tx_id], "tag" => "transfer", "fail" => false, "sender" => _sender, "toAddr" => _toAddr, "value" => _value }end 27 | %{ 28 | pending_transactions: MapSet.put(variables[:pending_transactions], new_tx.(variables)), 29 | last_tx: %{ "id" => 0, "tag" => "None", "fail" => false }, 30 | next_tx_id: variables[:next_tx_id] + 1, 31 | balance_of: variables[:balance_of], 32 | allowance: variables[:allowance] 33 | } 34 | end 35 | 36 | 37 | def commit_transfer_condition(variables, _tx) do 38 | fail = fn(variables) -> _tx["value"] < 0 or _tx["value"] > variables[:balance_of][_tx["sender"]] or _tx["sender"] == _tx["toAddr"]end 39 | Enum.all?([_tx["tag"] == "transfer", (if fail.(variables), do: false, else: true)]) 40 | end 41 | 42 | def commit_transfer(variables, _tx) do 43 | fail = fn(variables) -> _tx["value"] < 0 or _tx["value"] > variables[:balance_of][_tx["sender"]] or _tx["sender"] == _tx["toAddr"]end 44 | Map.merge( 45 | %{ 46 | pending_transactions: MapSet.difference(variables[:pending_transactions], MapSet.new([_tx])), 47 | last_tx: _tx|>Map.put("fail", fail.(variables)) 48 | }, 49 | (if fail.(variables), do: %{ 50 | balance_of: variables[:balance_of], 51 | allowance: variables[:allowance], 52 | next_tx_id: variables[:next_tx_id] 53 | }, else: %{ 54 | balance_of: variables[:balance_of]|>Map.put(_tx["sender"], (variables[:balance_of][_tx["sender"]]) - (_tx["value"]))|>Map.put(_tx["toAddr"], (variables[:balance_of][_tx["toAddr"]]) + (_tx["value"])), 55 | allowance: variables[:allowance], 56 | next_tx_id: variables[:next_tx_id] 57 | })) 58 | end 59 | 60 | 61 | def submit_transfer_from_condition(variables, _sender, _fromAddr, _toAddr, _value) do 62 | new_tx = fn(variables) -> %{ "id" => variables[:next_tx_id], "tag" => "transferFrom", "fail" => false, "sender" => _sender, "fromAddr" => _fromAddr, "toAddr" => _toAddr, "value" => _value }end 63 | Enum.all?([true]) 64 | end 65 | 66 | def submit_transfer_from(variables, _sender, _fromAddr, _toAddr, _value) do 67 | new_tx = fn(variables) -> %{ "id" => variables[:next_tx_id], "tag" => "transferFrom", "fail" => false, "sender" => _sender, "fromAddr" => _fromAddr, "toAddr" => _toAddr, "value" => _value }end 68 | %{ 69 | pending_transactions: MapSet.put(variables[:pending_transactions], new_tx.(variables)), 70 | last_tx: %{ "id" => 0, "tag" => "None", "fail" => false }, 71 | next_tx_id: variables[:next_tx_id] + 1, 72 | balance_of: variables[:balance_of], 73 | allowance: variables[:allowance] 74 | } 75 | end 76 | 77 | 78 | def commit_transfer_from_condition(variables, _tx) do 79 | fail = fn(variables) -> _tx["value"] < 0 or _tx["value"] > variables[:balance_of][_tx["fromAddr"]] or _tx["value"] > variables[:allowance][{_tx["fromAddr"], _tx["sender"]}] or _tx["fromAddr"] == _tx["toAddr"]end 80 | Enum.all?([_tx["tag"] == "transferFrom", (if fail.(variables), do: false, else: true)]) 81 | end 82 | 83 | def commit_transfer_from(variables, _tx) do 84 | fail = fn(variables) -> _tx["value"] < 0 or _tx["value"] > variables[:balance_of][_tx["fromAddr"]] or _tx["value"] > variables[:allowance][{_tx["fromAddr"], _tx["sender"]}] or _tx["fromAddr"] == _tx["toAddr"]end 85 | Map.merge( 86 | %{ 87 | pending_transactions: MapSet.difference(variables[:pending_transactions], MapSet.new([_tx])), 88 | last_tx: _tx|>Map.put("fail", fail.(variables)) 89 | }, 90 | (if fail.(variables), do: %{ 91 | balance_of: variables[:balance_of], 92 | allowance: variables[:allowance], 93 | next_tx_id: variables[:next_tx_id] 94 | }, else: %{ 95 | balance_of: variables[:balance_of]|>Map.put(_tx["fromAddr"], (variables[:balance_of][_tx["fromAddr"]]) - (_tx["value"]))|>Map.put(_tx["toAddr"], (variables[:balance_of][_tx["toAddr"]]) + (_tx["value"])), 96 | allowance: variables[:allowance]|>Map.put({_tx["fromAddr"], _tx["sender"]}, (variables[:allowance][{_tx["fromAddr"], _tx["sender"]}]) - (_tx["value"])), 97 | next_tx_id: variables[:next_tx_id] 98 | })) 99 | end 100 | 101 | 102 | def submit_approve_condition(variables, _sender, _spender, _value) do 103 | new_tx = fn(variables) -> %{ "id" => variables[:next_tx_id], "tag" => "approve", "fail" => false, "sender" => _sender, "spender" => _spender, "value" => _value }end 104 | Enum.all?([true]) 105 | end 106 | 107 | def submit_approve(variables, _sender, _spender, _value) do 108 | new_tx = fn(variables) -> %{ "id" => variables[:next_tx_id], "tag" => "approve", "fail" => false, "sender" => _sender, "spender" => _spender, "value" => _value }end 109 | %{ 110 | pending_transactions: MapSet.put(variables[:pending_transactions], new_tx.(variables)), 111 | last_tx: %{ "id" => 0, "tag" => "None", "fail" => false }, 112 | next_tx_id: variables[:next_tx_id] + 1, 113 | balance_of: variables[:balance_of], 114 | allowance: variables[:allowance] 115 | } 116 | end 117 | 118 | 119 | def commit_approve_condition(variables, _tx) do 120 | fail = fn(variables) -> _tx["value"] < 0 or _tx["sender"] == _tx["spender"]end 121 | Enum.all?([_tx["tag"] == "approve", (if fail.(variables), do: false, else: true)]) 122 | end 123 | 124 | def commit_approve(variables, _tx) do 125 | fail = fn(variables) -> _tx["value"] < 0 or _tx["sender"] == _tx["spender"]end 126 | Map.merge( 127 | %{ 128 | pending_transactions: MapSet.difference(variables[:pending_transactions], MapSet.new([_tx])), 129 | balance_of: variables[:balance_of], 130 | next_tx_id: variables[:next_tx_id], 131 | last_tx: _tx|>Map.put("fail", fail.(variables)) 132 | }, 133 | (if fail.(variables), do: %{ 134 | allowance: variables[:allowance] 135 | }, else: %{ 136 | allowance: variables[:allowance]|>Map.put({_tx["sender"], _tx["spender"]}, _tx["value"]) 137 | })) 138 | end 139 | 140 | 141 | 142 | def decide_action(oracle, variables, actions, step) do 143 | different_states = Enum.uniq(Enum.map(actions, fn(action) -> action[:transition].(variables) end)) 144 | 145 | cond do 146 | Enum.count(different_states) == 1 -> 147 | Enum.at(different_states, 0) 148 | true -> 149 | send oracle, {:choose, self(), different_states} 150 | 151 | receive do 152 | {:ok, n} -> Enum.at(different_states, n) 153 | {:cancel} -> variables 154 | {:stop} -> exit(0) 155 | end 156 | end 157 | end 158 | 159 | def wait_lock(oracle) do 160 | send(oracle, {:lock, self()}) 161 | receive do 162 | {:lock_acquired, state} -> IO.puts("Lock acquired"); {map, _} = Map.split(state, shared_variables); map 163 | {:already_locked, _} -> IO.puts("Lock refused"); Process.sleep(1000); wait_lock(oracle) 164 | end 165 | end 166 | end 167 | 168 | -------------------------------------------------------------------------------- /tla_specifications/old/ewd998/EWD998.tla: -------------------------------------------------------------------------------- 1 | ------------------------------- MODULE EWD998 ------------------------------- 2 | (***************************************************************************) 3 | (* TLA+ specification of an algorithm for distributed termination *) 4 | (* detection on a ring, due to Shmuel Safra, published as EWD 998: *) 5 | (* Shmuel Safra's version of termination detection. *) 6 | (* https://www.cs.utexas.edu/users/EWD/ewd09xx/EWD998.PDF *) 7 | (***************************************************************************) 8 | EXTENDS Integers, FiniteSets, Functions, SequencesExt 9 | 10 | CONSTANT 11 | \* @type: Int; 12 | N 13 | ASSUME NAssumption == N \in Nat \ {0} \* At least one node. 14 | 15 | Node == 0 .. N-1 16 | Color == {"white", "black"} 17 | Token == [pos : Node, q : Int, color : Color] 18 | 19 | VARIABLES 20 | \* @type: Int -> Bool; 21 | active, \* activation status of nodes 22 | \* @type: Int -> Str; 23 | color, \* color of nodes 24 | \* @type: Int -> Int; 25 | counter, \* nb of sent messages - nb of rcvd messages per node 26 | \* @type: Int -> Int; 27 | pending, \* nb of messages in transit to node 28 | \* @type: [ pos: Int, q: Int, color: Str ]; 29 | token \* token structure 30 | 31 | vars == <> 32 | 33 | TypeOK == 34 | /\ active \in [Node -> BOOLEAN] 35 | /\ color \in [Node -> Color] 36 | /\ counter \in [Node -> Int] 37 | /\ pending \in [Node -> Nat] 38 | /\ token \in Token 39 | ------------------------------------------------------------------------------ 40 | 41 | Init == 42 | (* EWD840 but nodes *) 43 | /\ active \in [Node -> BOOLEAN] 44 | /\ color \in [Node -> Color] 45 | (* Rule 0 *) 46 | /\ counter = [i \in Node |-> 0] \* c properly initialized 47 | /\ pending = [i \in Node |-> 0] 48 | /\ token \in [ pos: Node, q: {0}, color: {"black"} ] 49 | 50 | InitiateProbe == 51 | (* Rules 1 + 5 + 6 *) 52 | /\ token.pos = 0 53 | /\ \* previous round not conclusive if: 54 | \/ token.color = "black" 55 | \/ color[0] = "black" 56 | \/ counter[0] + token.q > 0 57 | /\ token' = [pos |-> N-1, q |-> 0, color |-> "white"] 58 | /\ color' = [ color EXCEPT ![0] = "white" ] 59 | \* The state of the nodes remains unchanged by token-related actions. 60 | /\ UNCHANGED <> 61 | 62 | PassToken(i) == 63 | (* Rules 2 + 4 + 7 *) 64 | /\ ~ active[i] \* If machine i is active, keep the token. 65 | /\ token.pos = i 66 | /\ token' = [pos |-> token.pos - 1, 67 | q |-> token.q + counter[i], 68 | color |-> IF color[i] = "black" THEN "black" ELSE token.color] 69 | /\ color' = [ color EXCEPT ![i] = "white" ] 70 | \* The state of the nodes remains unchanged by token-related actions. 71 | /\ UNCHANGED <> 72 | 73 | System == \/ InitiateProbe 74 | \/ \E i \in Node \ {0} : PassToken(i) 75 | 76 | ----------------------------------------------------------------------------- 77 | 78 | SendMsg(i) == 79 | \* Only allowed to send msgs if node i is active. 80 | /\ active[i] 81 | (* Rule 0 *) 82 | /\ counter' = [counter EXCEPT ![i] = @ + 1] 83 | \* Non-deterministically choose a receiver node. 84 | /\ \E j \in Node \ {i} : pending' = [pending EXCEPT ![j] = @ + 1] 85 | \* Note that we don't blacken node i as in EWD840 if node i 86 | \* sends a message to node j with j > i 87 | /\ UNCHANGED <> 88 | 89 | RecvMsg(i) == 90 | /\ pending[i] > 0 91 | /\ pending' = [pending EXCEPT ![i] = @ - 1] 92 | (* Rule 0 *) 93 | /\ counter' = [counter EXCEPT ![i] = @ - 1] 94 | (* Rule 3 *) 95 | /\ color' = [ color EXCEPT ![i] = "black" ] 96 | \* Receipt of a message activates i. 97 | /\ active' = [ active EXCEPT ![i] = TRUE ] 98 | /\ UNCHANGED <> 99 | 100 | Deactivate(i) == 101 | /\ active[i] 102 | /\ active' = [active EXCEPT ![i] = FALSE] 103 | /\ UNCHANGED <> 104 | 105 | Environment == \E i \in Node : SendMsg(i) \/ RecvMsg(i) \/ Deactivate(i) 106 | 107 | ----------------------------------------------------------------------------- 108 | 109 | Next == 110 | System \/ Environment 111 | 112 | Spec == Init /\ [][Next]_vars /\ WF_vars(System) 113 | 114 | ----------------------------------------------------------------------------- 115 | 116 | (***************************************************************************) 117 | (* Bound the otherwise infinite state space that TLC has to check. *) 118 | (***************************************************************************) 119 | StateConstraint == 120 | /\ \A i \in Node : counter[i] <= 3 /\ pending[i] <= 3 121 | /\ token.q <= 9 122 | 123 | ----------------------------------------------------------------------------- 124 | 125 | (***************************************************************************) 126 | (* Main safety property: if there is a white token at node 0 and there are *) 127 | (* no in-flight messages then every node is inactive. *) 128 | (***************************************************************************) 129 | terminationDetected == 130 | /\ token.pos = 0 131 | /\ token.color = "white" 132 | /\ token.q + counter[0] = 0 133 | /\ color[0] = "white" 134 | /\ ~ active[0] 135 | /\ pending[0] = 0 136 | 137 | (***************************************************************************) 138 | (* Sum of the values f[x], for x \in S \subseteq DOMAIN f. *) 139 | (***************************************************************************) 140 | \* @type: (Int -> Int, Set(Int)) => Int; 141 | Sum(f, S) == FoldFunctionOnSet(+, 0, f, S) 142 | 143 | (***************************************************************************) 144 | (* The number of messages on their way. "in-flight" *) 145 | (***************************************************************************) 146 | B == Sum(pending, Node) 147 | 148 | (***************************************************************************) 149 | (* The system has terminated if no node is active and there are no *) 150 | (* in-flight messages. *) 151 | (***************************************************************************) 152 | Termination == 153 | /\ \A i \in Node : ~ active[i] 154 | /\ B = 0 155 | 156 | TerminationDetection == 157 | terminationDetected => Termination 158 | 159 | (***************************************************************************) 160 | (* Interval of nodes between a and b: this is just a..b, but the following *) 161 | (* definition helps Apalache to construct a bounded set. *) 162 | (***************************************************************************) 163 | Rng(a,b) == { i \in Node: a <= i /\ i <= b } 164 | 165 | 166 | (***************************************************************************) 167 | (* Safra's inductive invariant *) 168 | (***************************************************************************) 169 | Inv == 170 | /\ P0:: B = Sum(counter, Node) 171 | (* (Ai: t < i < N: machine nr.i is passive) /\ *) 172 | (* (Si: t < i < N: ci.i) = q *) 173 | /\ \/ P1:: /\ \A i \in Rng(token.pos+1, N-1): active[i] = FALSE \* machine nr.i is passive 174 | /\ IF token.pos = N-1 175 | THEN token.q = 0 176 | ELSE token.q = Sum(counter, Rng(token.pos+1,N-1)) 177 | (* (Si: 0 <= i <= t: c.i) + q > 0. *) 178 | \/ P2:: Sum(counter, Rng(0, token.pos)) + token.q > 0 179 | (* Ei: 0 <= i <= t : machine nr.i is black. *) 180 | \/ P3:: \E i \in Rng(0, token.pos) : color[i] = "black" 181 | (* The token is black. *) 182 | \/ P4:: token.color = "black" 183 | 184 | (***************************************************************************) 185 | (* The inductive invariant combined with the type invariant *) 186 | (***************************************************************************) 187 | TypedInv == 188 | /\ TypeOK 189 | /\ Inv 190 | 191 | (***************************************************************************) 192 | (* Liveness property: termination is eventually detected. *) 193 | (***************************************************************************) 194 | Liveness == 195 | Termination ~> terminationDetected 196 | 197 | (***************************************************************************) 198 | (* The algorithm implements the specification of termination detection *) 199 | (* in a ring with asynchronous communication. *) 200 | (* The parameters of module AsyncTerminationDetection are instantiated *) 201 | (* by the symbols of the same name of the present module. *) 202 | (***************************************************************************) 203 | TD == INSTANCE AsyncTerminationDetection 204 | 205 | THEOREM Spec => TD!Spec 206 | ============================================================================= 207 | 208 | Checked with TLC in 01/2021 with two cores on a fairly modern desktop 209 | and the given state constraint StateConstraint above: 210 | 211 | | N | Diameter | Distinct States | States | Time | 212 | | --- | --- | --- | --- | --- | 213 | | 3 | 60 | 1.3m | 10.1m | 42 s | 214 | | 4 | 105 | 219m | 2.3b | 50 m | 215 | -------------------------------------------------------------------------------- /elixir/lib/mix/tasks/exploit_trace.ex: -------------------------------------------------------------------------------- 1 | defmodule Mix.Tasks.ExploitTrace do 2 | @moduledoc "Runs blackblox testing using the oracle" 3 | @shortdoc "Runs trace checking for a witness" 4 | use Mix.Task 5 | 6 | @impl Mix.Task 7 | def run(_) do 8 | trace = [ 9 | %{ 10 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 11 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 12 | last_tx: %{ "fail" => false, "id" => 0, "tag" => "None" }, 13 | next_tx_id: 0, 14 | pending_transactions: MapSet.new([]) 15 | }, 16 | %{ 17 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 18 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 19 | last_tx: %{ "fail" => false, "id" => 0, "tag" => "None" }, 20 | next_tx_id: 1, 21 | pending_transactions: MapSet.new([%{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }]) 22 | }, 23 | %{ 24 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 25 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 26 | last_tx: %{ "fail" => false, "id" => 0, "tag" => "None" }, 27 | next_tx_id: 2, 28 | pending_transactions: MapSet.new([%{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 1, "sender" => "Alice_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }]) 29 | }, 30 | %{ 31 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 32 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 33 | last_tx: %{ "fail" => false, "id" => 0, "tag" => "None" }, 34 | next_tx_id: 3, 35 | pending_transactions: MapSet.new([%{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 1, "sender" => "Alice_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 2, "sender" => "Bob_OF_ADDR", "tag" => "transfer", "toAddr" => "Alice_OF_ADDR", "value" => 0 }]) 36 | }, 37 | %{ 38 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 1, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 39 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 40 | last_tx: %{ "fail" => false, "id" => 1, "sender" => "Alice_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, 41 | next_tx_id: 3, 42 | pending_transactions: MapSet.new([%{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 2, "sender" => "Bob_OF_ADDR", "tag" => "transfer", "toAddr" => "Alice_OF_ADDR", "value" => 0 }]) 43 | }, 44 | %{ 45 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 1, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 46 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 47 | last_tx: %{ "fail" => false, "id" => 0, "tag" => "None" }, 48 | next_tx_id: 4, 49 | pending_transactions: MapSet.new([%{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 2, "sender" => "Bob_OF_ADDR", "tag" => "transfer", "toAddr" => "Alice_OF_ADDR", "value" => 0 }, %{ "fail" => false, "id" => 3, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 3 }]) 50 | }, 51 | %{ 52 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 1, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 53 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 54 | last_tx: %{ "fail" => false, "id" => 2, "sender" => "Bob_OF_ADDR", "tag" => "transfer", "toAddr" => "Alice_OF_ADDR", "value" => 0 }, 55 | next_tx_id: 4, 56 | pending_transactions: MapSet.new([%{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 3, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 3 }]) 57 | }, 58 | %{ 59 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 1, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 60 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 61 | last_tx: %{ "fail" => false, "id" => 0, "tag" => "None" }, 62 | next_tx_id: 5, 63 | pending_transactions: MapSet.new([%{ "fail" => false, "fromAddr" => "Eve_OF_ADDR", "id" => 4, "sender" => "Bob_OF_ADDR", "tag" => "transferFrom", "toAddr" => "Alice_OF_ADDR", "value" => 2 }, %{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 3, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 3 }]) 64 | }, 65 | %{ 66 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 1, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 67 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 68 | last_tx: %{ "fail" => false, "id" => 0, "tag" => "None" }, 69 | next_tx_id: 6, 70 | pending_transactions: MapSet.new([%{ "fail" => false, "fromAddr" => "Eve_OF_ADDR", "id" => 4, "sender" => "Bob_OF_ADDR", "tag" => "transferFrom", "toAddr" => "Alice_OF_ADDR", "value" => 2 }, %{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 3, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 3 }, %{ "fail" => false, "id" => 5, "sender" => "Alice_OF_ADDR", "tag" => "transfer", "toAddr" => "Eve_OF_ADDR", "value" => 0 }]) 71 | }, 72 | %{ 73 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 1, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 3, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 74 | balance_of: %{ "Alice_OF_ADDR" => 100, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 100 }, 75 | last_tx: %{ "fail" => false, "id" => 3, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 3 }, 76 | next_tx_id: 6, 77 | pending_transactions: MapSet.new([%{ "fail" => false, "fromAddr" => "Eve_OF_ADDR", "id" => 4, "sender" => "Bob_OF_ADDR", "tag" => "transferFrom", "toAddr" => "Alice_OF_ADDR", "value" => 2 }, %{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 5, "sender" => "Alice_OF_ADDR", "tag" => "transfer", "toAddr" => "Eve_OF_ADDR", "value" => 0 }]) 78 | }, 79 | %{ 80 | allowance: %{ {"Bob_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Alice_OF_ADDR", "Bob_OF_ADDR"} => 1, {"Alice_OF_ADDR", "Eve_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Bob_OF_ADDR"} => 0, {"Bob_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Alice_OF_ADDR"} => 0, {"Eve_OF_ADDR", "Bob_OF_ADDR"} => 1, {"Eve_OF_ADDR", "Eve_OF_ADDR"} => 0 }, 81 | balance_of: %{ "Alice_OF_ADDR" => 102, "Bob_OF_ADDR" => 100, "Eve_OF_ADDR" => 98 }, 82 | last_tx: %{ "fail" => false, "fromAddr" => "Eve_OF_ADDR", "id" => 4, "sender" => "Bob_OF_ADDR", "tag" => "transferFrom", "toAddr" => "Alice_OF_ADDR", "value" => 2 }, 83 | next_tx_id: 6, 84 | pending_transactions: MapSet.new([%{ "fail" => false, "id" => 0, "sender" => "Eve_OF_ADDR", "spender" => "Bob_OF_ADDR", "tag" => "approve", "value" => 1 }, %{ "fail" => false, "id" => 5, "sender" => "Alice_OF_ADDR", "tag" => "transfer", "toAddr" => "Eve_OF_ADDR", "value" => 0 }]) 85 | } 86 | ] 87 | 88 | modules = [ 89 | ERC20_alice, 90 | ERC20_bob, 91 | ERC20_eve, 92 | ERC20_blockchain 93 | ] 94 | pids = Enum.map(modules, fn m -> spawn(m, :main, [self(), Enum.at(trace, 0), 0]) end) 95 | TraceCheckerOracle.start(trace, 0, nil, pids) 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /tla_specifications/old/TwoPhaseCommit/TwoPhaseCommit.tla: -------------------------------------------------------------------------------- 1 | ---- MODULE TwoPhaseCommit---------------------------------------------------- 2 | (***************************************************************************) 3 | (* This specification is discussed in "Two-Phase Commit", Lecture 6 of the *) 4 | (* TLA+ Video Course. It describes the Two-Phase Commit protocol, in *) 5 | (* which a transaction manager (TM) coordinates the resource managers *) 6 | (* (RMs) to implement the Transaction Commit specification of module *) 7 | (* TCommit. In this specification, RMs spontaneously issue Prepared *) 8 | (* messages. We ignore the Prepare messages that the TM can send to the *) 9 | (* RMs. *) 10 | (* *) 11 | (* For simplicity, we also eliminate Abort messages sent by an RM when it *) 12 | (* decides to abort. Such a message would cause the TM to abort the *) 13 | (* transaction, an event represented here by the TM spontaneously deciding *) 14 | (* to abort. *) 15 | (***************************************************************************) 16 | CONSTANT RM 17 | 18 | VARIABLES 19 | rmState, 20 | tmState, 21 | tmPrepared, 22 | msgs 23 | (***********************************************************************) 24 | (* In the protocol, processes communicate with one another by sending *) 25 | (* messages. For simplicity, we represent message passing with the *) 26 | (* variable msgs whose value is the set of all messages that have been *) 27 | (* sent. A message is sent by adding it to the set msgs. An action *) 28 | (* that, in an implementation, would be enabled by the receipt of a *) 29 | (* certain message is here enabled by the presence of that message in *) 30 | (* msgs. For simplicity, messages are never removed from msgs. This *) 31 | (* allows a single message to be received by multiple receivers. *) 32 | (* Receipt of the same message twice is therefore allowed; but in this *) 33 | (* particular protocol, that's not a problem. *) 34 | (***********************************************************************) 35 | 36 | Messages == 37 | (*************************************************************************) 38 | (* The set of all possible messages. Messages of type "Prepared" are *) 39 | (* sent from the RM indicated by the message's rm field to the TM. *) 40 | (* Messages of type "Commit" and "Abort" are broadcast by the TM, to be *) 41 | (* received by all RMs. The set msgs contains just a single copy of *) 42 | (* such a message. *) 43 | (*************************************************************************) 44 | [type : {"Prepared"}, rm : RM] \cup [type : {"Commit", "Abort"}] 45 | 46 | TPTypeOK == 47 | (*************************************************************************) 48 | (* The type-correctness invariant *) 49 | (*************************************************************************) 50 | /\ rmState \in [RM -> {"working", "prepared", "committed", "aborted"}] 51 | /\ tmState \in {"init", "done"} 52 | /\ tmPrepared \subseteq RM 53 | /\ msgs \subseteq Messages 54 | 55 | TPInit == 56 | (*************************************************************************) 57 | (* The initial predicate. *) 58 | (*************************************************************************) 59 | /\ rmState = [r \in RM |-> "working"] 60 | /\ tmState = "init" 61 | /\ tmPrepared = {} 62 | /\ msgs = {} 63 | ----------------------------------------------------------------------------- 64 | (***************************************************************************) 65 | (* We now define the actions that may be performed by the processes, first *) 66 | (* the TM's actions, then the RMs' actions. *) 67 | (***************************************************************************) 68 | TMRcvPrepared(r) == 69 | (*************************************************************************) 70 | (* The TM receives a "Prepared" message from resource manager r. We *) 71 | (* could add the additional enabling condition r \notin tmPrepared, *) 72 | (* which disables the action if the TM has already received this *) 73 | (* message. But there is no need, because in that case the action has *) 74 | (* no effect; it leaves the state unchanged. *) 75 | (*************************************************************************) 76 | /\ tmState = "init" 77 | /\ [type |-> "Prepared", rm |-> r] \in msgs 78 | /\ tmPrepared' = tmPrepared \cup {r} 79 | /\ UNCHANGED <> 80 | 81 | TMCommit == 82 | (*************************************************************************) 83 | (* The TM commits the transaction; enabled iff the TM is in its initial *) 84 | (* state and every RM has sent a "Prepared" message. *) 85 | (*************************************************************************) 86 | /\ tmState = "init" 87 | /\ tmPrepared = RM 88 | /\ tmState' = "done" 89 | /\ msgs' = msgs \cup {[type |-> "Commit"]} 90 | /\ UNCHANGED <> 91 | 92 | TMAbort == 93 | (*************************************************************************) 94 | (* The TM spontaneously aborts the transaction. *) 95 | (*************************************************************************) 96 | /\ tmState = "init" 97 | /\ tmState' = "done" 98 | /\ msgs' = msgs \cup {[type |-> "Abort"]} 99 | /\ UNCHANGED <> 100 | 101 | RMPrepare(r) == 102 | (*************************************************************************) 103 | (* Resource manager r prepares. *) 104 | (*************************************************************************) 105 | /\ rmState[r] = "working" 106 | /\ rmState' = [rmState EXCEPT ![r] = "prepared"] 107 | /\ msgs' = msgs \cup {[type |-> "Prepared", rm |-> r]} 108 | /\ UNCHANGED <> 109 | 110 | RMChooseToAbort(r) == 111 | (*************************************************************************) 112 | (* Resource manager r spontaneously decides to abort. As noted above, r *) 113 | (* does not send any message in our simplified spec. *) 114 | (*************************************************************************) 115 | /\ rmState[r] = "working" 116 | /\ rmState' = [rmState EXCEPT ![r] = "aborted"] 117 | /\ UNCHANGED <> 118 | 119 | RMRcvCommitMsg(r) == 120 | (*************************************************************************) 121 | (* Resource manager r is told by the TM to commit. *) 122 | (*************************************************************************) 123 | /\ [type |-> "Commit"] \in msgs 124 | /\ rmState' = [rmState EXCEPT ![r] = "committed"] 125 | /\ UNCHANGED <> 126 | 127 | RMRcvAbortMsg(r) == 128 | (*************************************************************************) 129 | (* Resource manager r is told by the TM to abort. *) 130 | (*************************************************************************) 131 | /\ [type |-> "Abort"] \in msgs 132 | /\ rmState' = [rmState EXCEPT ![r] = "aborted"] 133 | /\ UNCHANGED <> 134 | 135 | TPNext == 136 | \/ TMCommit \/ TMAbort 137 | \/ \E r \in RM : 138 | TMRcvPrepared(r) \/ RMPrepare(r) \/ RMChooseToAbort(r) 139 | \/ RMRcvCommitMsg(r) \/ RMRcvAbortMsg(r) 140 | ----------------------------------------------------------------------------- 141 | (***************************************************************************) 142 | (* The material below this point is not discussed in Video Lecture 6. It *) 143 | (* will be explained in Video Lecture 8. *) 144 | (***************************************************************************) 145 | 146 | TPSpec == TPInit /\ [][TPNext]_<> 147 | (*************************************************************************) 148 | (* The complete spec of the Two-Phase Commit protocol. *) 149 | (*************************************************************************) 150 | 151 | THEOREM TPSpec => []TPTypeOK 152 | (*************************************************************************) 153 | (* This theorem asserts that the type-correctness predicate TPTypeOK is *) 154 | (* an invariant of the specification. *) 155 | (*************************************************************************) 156 | ----------------------------------------------------------------------------- 157 | (***************************************************************************) 158 | (* We now assert that the Two-Phase Commit protocol implements the *) 159 | (* Transaction Commit protocol of module TCommit. The following statement *) 160 | (* imports all the definitions from module TCommit into the current *) 161 | (* module. *) 162 | (***************************************************************************) 163 | INSTANCE TCommit 164 | 165 | THEOREM TPSpec => TCSpec 166 | (*************************************************************************) 167 | (* This theorem asserts that the specification TPSpec of the Two-Phase *) 168 | (* Commit protocol implements the specification TCSpec of the *) 169 | (* Transaction Commit protocol. *) 170 | (*************************************************************************) 171 | (***************************************************************************) 172 | (* The two theorems in this module have been checked with TLC for six *) 173 | (* RMs, a configuration with 50816 reachable states, in a little over a *) 174 | (* minute on a 1 GHz PC. *) 175 | (***************************************************************************) 176 | ================================================================================ 177 | -------------------------------------------------------------------------------- /tla_specifications/ERC20/ERC20.tla: -------------------------------------------------------------------------------- 1 | ----------------------------- MODULE ERC20 ------------------------------------ 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 | * This TLA+ specification is designed for model checking with Apalache. 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 | 17 | EXTENDS Integers, Apalache 18 | 19 | CONSTANTS 20 | \* Set of all addresses. 21 | \* 22 | \* @type: Set(ADDR); 23 | ADDR, 24 | \* Set of the amounts to use. 25 | \* 26 | \* @type: Set(Int); 27 | AMOUNTS 28 | 29 | VARIABLES 30 | \* Token balance for every account. This is exactly as `balanceOf` in ERC20. 31 | \* 32 | \* @type: ADDR -> Int; 33 | balanceOf, 34 | \* Allowance to transfer tokens 35 | \* from owner (1st element) by spender (2nd element). 36 | \* This is exactly as `allowance` of ERC20. 37 | \* 38 | \* @type: <> -> Int; 39 | allowance 40 | 41 | \* Variables that model Ethereum transactions, not the ERC20 state machine. 42 | VARIABLES 43 | \* Pending transactions to be executed against the contract. 44 | \* Note that we have a set of transactions instead of a sequence, 45 | \* as the order of transactions on Ethereum is not predefined. 46 | \* To make it possible to submit two 'equal' transactions, 47 | \* we introduce a unique transaction id. 48 | \* 49 | \* @type: Set(TX); 50 | pendingTransactions, 51 | \* The last executed transaction. 52 | \* 53 | \* @type: TX; 54 | lastTx, 55 | \* A serial number to assign unique ids to transactions 56 | \* @type: Int; 57 | nextTxId 58 | 59 | \* Initialize an ERC20 token. 60 | Init == 61 | /\ balanceOf = [a \in ADDR |-> 100] 62 | \* no account is allowed to withdraw from another account 63 | /\ allowance = [ pair \in ADDR \X ADDR |-> 0 ] 64 | \* no pending transactions 65 | /\ pendingTransactions = {} 66 | /\ nextTxId = 0 67 | /\ lastTx = [ id |-> 0, tag |-> "None", fail |-> FALSE ] 68 | 69 | (* EIP-20: 70 | The following action submits a transaction to the blockchain. 71 | 72 | Transfers _value amount of tokens to address _toAddr, and MUST fire the Transfer 73 | event. The function SHOULD throw if the message caller’s account balance does 74 | not have enough tokens to spend. 75 | 76 | Note Transfers of 0 values MUST be treated as normal transfers and fire the 77 | Transfer event. 78 | *) 79 | SubmitTransfer(_sender, _toAddr, _value) == 80 | LET newTx == [ id |-> nextTxId, tag |-> "transfer", fail |-> FALSE, 81 | sender |-> _sender, toAddr |-> _toAddr, value |-> _value ] 82 | IN 83 | /\ pendingTransactions' = pendingTransactions \union { newTx } 84 | /\ lastTx' = [ id |-> 0, tag |-> "None", fail |-> FALSE ] 85 | /\ nextTxId' = nextTxId + 1 86 | /\ UNCHANGED <> 87 | 88 | (* 89 | Process a Transfer transaction that was submitted with SubmitTransfer. 90 | *) 91 | CommitTransfer(_tx) == 92 | /\ _tx.tag = "transfer" 93 | /\ pendingTransactions' = pendingTransactions \ { _tx } 94 | /\ LET fail == 95 | \/ _tx.value < 0 96 | \/ _tx.value > balanceOf[_tx.sender] 97 | \/ _tx.sender = _tx.toAddr 98 | IN 99 | /\ lastTx' = [ _tx EXCEPT !.fail = fail ] 100 | /\ IF fail 101 | THEN UNCHANGED <> 102 | ELSE \* transaction succeeds 103 | \* update the balances of the 'sender' and 'toAddr' addresses 104 | /\ balanceOf' = [ 105 | balanceOf EXCEPT ![_tx.sender] = @ - _tx.value, 106 | ![_tx.toAddr] = @ + _tx.value 107 | ] 108 | /\ UNCHANGED <> 109 | 110 | (* EIP-20: 111 | Transfers _value amount of tokens from address _fromAddr to address _toAddr, and 112 | MUST fire the Transfer event. 113 | 114 | The transferFrom method is used for a withdraw workflow, allowing contracts to 115 | transfer tokens on your behalf. This can be used for example to allow a 116 | contract to transfer tokens on your behalf and/or to charge fees in 117 | sub-currencies. The function SHOULD throw unless the _fromAddr account has 118 | deliberately authorized the sender of the message via some mechanism. 119 | 120 | Note Transfers of 0 values MUST be treated as normal transfers and fire the 121 | Transfer event. 122 | *) 123 | SubmitTransferFrom(_sender, _fromAddr, _toAddr, _value) == 124 | LET newTx == [ 125 | id |-> nextTxId, 126 | tag |-> "transferFrom", fail |-> FALSE, sender |-> _sender, 127 | fromAddr |-> _fromAddr, toAddr |-> _toAddr, value |-> _value 128 | ] 129 | IN 130 | /\ pendingTransactions' = pendingTransactions \union { newTx } 131 | /\ lastTx' = [ id |-> 0, tag |-> "None", fail |-> FALSE ] 132 | /\ nextTxId' = nextTxId + 1 133 | /\ UNCHANGED <> 134 | 135 | (* 136 | Process a TranferFrom transaction that was submitted with SubmitTransferFrom. 137 | *) 138 | CommitTransferFrom(_tx) == 139 | /\ _tx.tag = "transferFrom" 140 | /\ pendingTransactions' = pendingTransactions \ { _tx } 141 | /\ LET fail == 142 | \/ _tx.value < 0 143 | \/ _tx.value > balanceOf[_tx.fromAddr] 144 | \/ _tx.value > allowance[_tx.fromAddr, _tx.sender] 145 | \/ _tx.fromAddr = _tx.toAddr 146 | IN 147 | /\ lastTx' = [ _tx EXCEPT !.fail = fail ] 148 | /\ IF fail 149 | THEN UNCHANGED <> 150 | ELSE \* transaction succeeds 151 | \* update the balances of the 'fromAddr' and 'toAddr' addresses 152 | /\ balanceOf' = [ balanceOf EXCEPT 153 | ![_tx.fromAddr] = @ - _tx.value, 154 | ![_tx.toAddr] = @ + _tx.value 155 | ] 156 | \* decrease the allowance for the sender 157 | /\ allowance' = [ allowance EXCEPT 158 | ![_tx.fromAddr, _tx.sender] = @ - _tx.value 159 | ] 160 | /\ UNCHANGED nextTxId 161 | 162 | (* EIP-20: 163 | Allows _spender to withdraw from your account multiple times, up to the _value 164 | amount. If this function is called again it overwrites the current allowance 165 | with _value. 166 | *) 167 | SubmitApprove(_sender, _spender, _value) == 168 | LET newTx == [ 169 | id |-> nextTxId, 170 | tag |-> "approve", fail |-> FALSE, 171 | sender |-> _sender, spender |-> _spender, value |-> _value 172 | ] 173 | IN 174 | /\ pendingTransactions' = pendingTransactions \union { newTx } 175 | /\ lastTx' = [ id |-> 0, tag |-> "None", fail |-> FALSE ] 176 | /\ nextTxId' = nextTxId + 1 177 | /\ UNCHANGED <> 178 | 179 | \* Process an Approve transaction that was submitted with SubmitApprove. 180 | CommitApprove(_tx) == 181 | /\ _tx.tag = "approve" 182 | /\ pendingTransactions' = pendingTransactions \ { _tx } 183 | /\ UNCHANGED <> 184 | /\ LET fail == _tx.value < 0 \/ _tx.sender = _tx.spender IN 185 | /\ lastTx' = [ _tx EXCEPT !.fail = fail ] 186 | /\ IF fail 187 | THEN \* transaction fails 188 | UNCHANGED allowance 189 | ELSE \* transaction succeeds 190 | \* set the allowance for the pair <> to value 191 | allowance' = 192 | [ allowance EXCEPT ![_tx.sender, _tx.spender] = _tx.value ] 193 | 194 | \* The transition relation, which chooses one of the actions 195 | Next == 196 | \/ \E sender, toAddr \in ADDR: 197 | \E value \in AMOUNTS: 198 | SubmitTransfer(sender, toAddr, value) 199 | \/ \E sender, fromAddr, toAddr \in ADDR: 200 | \E value \in AMOUNTS: 201 | SubmitTransferFrom(sender, fromAddr, toAddr, value) 202 | \/ \E sender, spender \in ADDR: 203 | \E value \in AMOUNTS: 204 | SubmitApprove(sender, spender, value) 205 | \/ \E tx \in pendingTransactions: 206 | \/ CommitTransfer(tx) 207 | \/ CommitTransferFrom(tx) 208 | \/ CommitApprove(tx) 209 | 210 | 211 | (* False invariants to debug the spec *) 212 | 213 | \* Claim that no `TransferFrom` transaction is ever processed. 214 | \* This is false. Use this false invariant to see an example of how 215 | \* `TransferFrom` is processed. 216 | NoTransferFrom == 217 | LET Example == 218 | /\ ~lastTx.fail 219 | /\ lastTx.tag = "transferFrom" 220 | /\ lastTx.value > 0 221 | IN 222 | ~Example 223 | 224 | \* Claim that no `Approve` transaction is ever processed. 225 | \* This is false. Use this false invariant to see an example of how 226 | \* `Approve` is processed. 227 | NoApprove == 228 | LET Example == 229 | /\ ~lastTx.fail 230 | /\ lastTx.tag = "approve" 231 | /\ lastTx.value > 0 232 | IN 233 | ~Example 234 | 235 | \* EXPECTED PROPERTIES 236 | 237 | \* No transferFrom should be possible, while there is a pending approval 238 | \* for a smaller amount. This invariant is violated, as explained in: 239 | \* 240 | \* https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit# 241 | NoTransferFromWhileApproveInFlight == 242 | LET BadExample == 243 | /\ lastTx.tag = "transferFrom" 244 | /\ lastTx.value > 0 245 | /\ ~lastTx.fail 246 | /\ \E approval \in pendingTransactions: 247 | /\ approval.tag = "approve" 248 | /\ approval.sender = lastTx.fromAddr 249 | /\ approval.spender = lastTx.sender 250 | /\ lastTx.sender /= lastTx.toAddr 251 | /\ approval.value < lastTx.value 252 | /\ approval.value > 0 253 | IN 254 | ~BadExample 255 | 256 | \* Make sure that the account balances never go negative. 257 | NoNegativeBalances == 258 | \A a \in ADDR: 259 | balanceOf[a] >= 0 260 | 261 | \* A trace invariant: For every pair <>, the sum of transfers 262 | \* via TransferFrom is no greater than the maximum allowance. 263 | \* It is quite hard to formulate this property, as there are scenarios, 264 | \* where this behavior is actually expected. 265 | \* In pure TLA+, we would have to write a temporal property. 266 | \* In Apalache, we are just writing a trace invariant. 267 | \* 268 | \* @type: Seq(STATE) => Bool; 269 | NoTransferAboveApproved(trace) == 270 | \A spender, fromAddr \in ADDR: 271 | LET TransferIndices == { 272 | i \in DOMAIN trace: 273 | LET tx == trace[i].lastTx IN 274 | /\ tx.tag = "transferFrom" 275 | /\ ~tx.fail 276 | /\ tx.fromAddr = fromAddr 277 | /\ tx.sender = spender 278 | /\ tx.value > 0 279 | } 280 | IN 281 | \* the sum of all transfers from 'fromAddr' to 'toAddr' 282 | LET sumTransfers == 283 | LET Add(sum, i) == sum + trace[i].lastTx.value IN 284 | ApaFoldSet(Add, 0, TransferIndices) 285 | IN 286 | \* there exists an approval for the whole transfer sum 287 | LET existsApprovalForSumInPast == 288 | \E i \in DOMAIN trace: 289 | LET tx_i == trace[i].lastTx IN 290 | \* all transfers are made after the approval 291 | /\ \A j \in TransferIndices: j > i 292 | /\ tx_i.tag = "approve" 293 | /\ ~tx_i.fail 294 | \* the sender of this transaction is allowing the spender 295 | \* to spend at most the sum of the made transfers. 296 | /\ tx_i.value >= sumTransfers 297 | /\ spender = tx_i.spender 298 | /\ fromAddr = tx_i.sender 299 | IN 300 | sumTransfers > 0 => existsApprovalForSumInPast 301 | 302 | Full == nextTxId <= 5 303 | =============================================================================== 304 | -------------------------------------------------------------------------------- /two_phase_commit_example/lib/two_phase_commit.ex: -------------------------------------------------------------------------------- 1 | defmodule TwoPhaseCommit do 2 | @moduledoc """ 3 | ************************************************************************* 4 | This specification is discussed in "Two-Phase Commit", Lecture 6 of the 5 | TLA+ Video Course. It describes the Two-Phase Commit protocol, in 6 | which a transaction manager (TM) coordinates the resource managers 7 | (RMs) to implement the Transaction Commit specification of module 8 | TCommit. In this specification, RMs spontaneously issue Prepared 9 | messages. We ignore the Prepare messages that the TM can send to the 10 | RMs. 11 | 12 | For simplicity, we also eliminate Abort messages sent by an RM when it 13 | decides to abort. Such a message would cause the TM to abort the 14 | transaction, an event represented here by the TM spontaneously deciding 15 | to abort. 16 | ************************************************************************* 17 | """ 18 | require ManagersOracle 19 | @oracle spawn(ManagersOracle, :start, []) 20 | 21 | @rm MapSet.new(["r1", "r2"]) 22 | def rm, do: @rm 23 | 24 | 25 | # ********************************************************************* 26 | # In the protocol, processes communicate with one another by sending 27 | # messages. For simplicity, we represent message passing with the 28 | # variable msgs whose value is the set of all messages that have been 29 | # sent. A message is sent by adding it to the set msgs. An action 30 | # that, in an implementation, would be enabled by the receipt of a 31 | # certain message is here enabled by the presence of that message in 32 | # msgs. For simplicity, messages are never removed from msgs. This 33 | # allows a single message to be received by multiple receivers. 34 | # Receipt of the same message twice is therefore allowed; but in this 35 | # particular protocol, that's not a problem. 36 | # ********************************************************************* 37 | # Messages == 38 | # *********************************************************************** 39 | # The set of all possible messages. Messages of type "Prepared" are 40 | # sent from the RM indicated by the message's rm field to the TM. 41 | # Messages of type "Commit" and "Abort" are broadcast by the TM, to be 42 | # received by all RMs. The set msgs contains just a single copy of 43 | # such a message. 44 | # *********************************************************************** 45 | # [type : {"Prepared"}, rm : RM] \cup [type : {"Commit", "Abort"}] 46 | # TPTypeOK == 47 | # *********************************************************************** 48 | # The type-correctness invariant 49 | # *********************************************************************** 50 | # /\ rmState \in [RM -> {"working", "prepared", "committed", "aborted"}] 51 | # /\ tmState \in {"init", "done"} 52 | # /\ tmPrepared \subseteq RM 53 | # /\ msgs \subseteq Messages 54 | # ************************************************************************* 55 | # We now define the actions that may be performed by the processes, first 56 | # the TM's actions, then the RMs' actions. 57 | # ************************************************************************* 58 | @doc """ 59 | *********************************************************************** 60 | The TM receives a "Prepared" message from resource manager r. We 61 | could add the additional enabling condition r \notin tmPrepared, 62 | which disables the action if the TM has already received this 63 | message. But there is no need, because in that case the action has 64 | no effect; it leaves the state unchanged. 65 | *********************************************************************** 66 | """ 67 | def tm_rcv_prepared_condition(variables, r) do 68 | variables[:tm_state] == "init" and Enum.member?(variables[:msgs], %{ type: "Prepared", rm: r }) 69 | end 70 | 71 | def tm_rcv_prepared(variables, r) do 72 | %{ 73 | tm_prepared: MapSet.put(variables[:tm_prepared], r), 74 | rm_state: variables[:rm_state], 75 | tm_state: variables[:tm_state], 76 | msgs: variables[:msgs] 77 | } 78 | end 79 | 80 | 81 | @doc """ 82 | *********************************************************************** 83 | The TM commits the transaction; enabled iff the TM is in its initial 84 | state and every RM has sent a "Prepared" message. 85 | *********************************************************************** 86 | """ 87 | def tm_commit_condition(variables) do 88 | variables[:tm_state] == "init" and variables[:tm_prepared] == @rm 89 | end 90 | 91 | def tm_commit(variables) do 92 | %{ 93 | tm_state: "done", 94 | msgs: MapSet.put(variables[:msgs], %{ type: "Commit" }), 95 | rm_state: variables[:rm_state], 96 | tm_prepared: variables[:tm_prepared] 97 | } 98 | end 99 | 100 | 101 | @doc """ 102 | *********************************************************************** 103 | The TM spontaneously aborts the transaction. 104 | *********************************************************************** 105 | """ 106 | def tm_abort_condition(variables) do 107 | variables[:tm_state] == "init" 108 | end 109 | 110 | def tm_abort(variables) do 111 | %{ 112 | tm_state: "done", 113 | msgs: MapSet.put(variables[:msgs], %{ type: "Abort" }), 114 | rm_state: variables[:rm_state], 115 | tm_prepared: variables[:tm_prepared] 116 | } 117 | end 118 | 119 | 120 | @doc """ 121 | *********************************************************************** 122 | Resource manager r prepares. 123 | *********************************************************************** 124 | """ 125 | def rm_prepare_condition(variables, r) do 126 | variables[:rm_state][r] == "working" 127 | end 128 | 129 | def rm_prepare(variables, r) do 130 | %{ 131 | rm_state: Map.put(variables[:rm_state], r, "prepared"), 132 | msgs: MapSet.put(variables[:msgs], %{ type: "Prepared", rm: r }), 133 | tm_state: variables[:tm_state], 134 | tm_prepared: variables[:tm_prepared] 135 | } 136 | end 137 | 138 | 139 | @doc """ 140 | *********************************************************************** 141 | Resource manager r spontaneously decides to abort. As noted above, r 142 | does not send any message in our simplified spec. 143 | *********************************************************************** 144 | """ 145 | def rm_choose_to_abort_condition(variables, r) do 146 | variables[:rm_state][r] == "working" 147 | end 148 | 149 | def rm_choose_to_abort(variables, r) do 150 | %{ 151 | rm_state: Map.put(variables[:rm_state], r, "aborted"), 152 | tm_state: variables[:tm_state], 153 | tm_prepared: variables[:tm_prepared], 154 | msgs: variables[:msgs] 155 | } 156 | end 157 | 158 | 159 | @doc """ 160 | *********************************************************************** 161 | Resource manager r is told by the TM to commit. 162 | *********************************************************************** 163 | """ 164 | def rm_rcv_commit_msg_condition(variables, r) do 165 | Enum.member?(variables[:msgs], %{ type: "Commit" }) 166 | end 167 | 168 | def rm_rcv_commit_msg(variables, r) do 169 | %{ 170 | rm_state: Map.put(variables[:rm_state], r, "committed"), 171 | tm_state: variables[:tm_state], 172 | tm_prepared: variables[:tm_prepared], 173 | msgs: variables[:msgs] 174 | } 175 | end 176 | 177 | 178 | @doc """ 179 | *********************************************************************** 180 | Resource manager r is told by the TM to abort. 181 | *********************************************************************** 182 | """ 183 | def rm_rcv_abort_msg_condition(variables, r) do 184 | Enum.member?(variables[:msgs], %{ type: "Abort" }) 185 | end 186 | 187 | def rm_rcv_abort_msg(variables, r) do 188 | %{ 189 | rm_state: Map.put(variables[:rm_state], r, "aborted"), 190 | tm_state: variables[:tm_state], 191 | tm_prepared: variables[:tm_prepared], 192 | msgs: variables[:msgs] 193 | } 194 | end 195 | 196 | 197 | # ************************************************************************* 198 | # The material below this point is not discussed in Video Lecture 6. It 199 | # will be explained in Video Lecture 8. 200 | # ************************************************************************* 201 | # TPSpec == TPInit /\ [][TPNext]_<> 202 | # *********************************************************************** 203 | # The complete spec of the Two-Phase Commit protocol. 204 | # *********************************************************************** 205 | # *********************************************************************** 206 | # This theorem asserts that the type-correctness predicate TPTypeOK is 207 | # an invariant of the specification. 208 | # *********************************************************************** 209 | # ************************************************************************* 210 | # We now assert that the Two-Phase Commit protocol implements the 211 | # Transaction Commit protocol of module TCommit. The following statement 212 | # imports all the definitions from module TCommit into the current 213 | # module. 214 | # ************************************************************************* 215 | # *********************************************************************** 216 | # This theorem asserts that the specification TPSpec of the Two-Phase 217 | # Commit protocol implements the specification TCSpec of the 218 | # Transaction Commit protocol. 219 | # *********************************************************************** 220 | # ************************************************************************* 221 | # The two theorems in this module have been checked with TLC for six 222 | # RMs, a configuration with 50816 reachable states, in a little over a 223 | # minute on a 1 GHz PC. 224 | # ************************************************************************* 225 | def main(variables) do 226 | IEx.Helpers.clear 227 | IO.puts (inspect(variables, pretty: true)) 228 | 229 | main( 230 | decide_action( 231 | List.flatten([ 232 | %{ action: "TMCommit()", condition: tm_commit_condition(variables), state: tm_commit(variables) }, 233 | %{ action: "TMAbort()", condition: tm_abort_condition(variables), state: tm_abort(variables) }, 234 | Enum.map(@rm, fn (r) -> [ 235 | %{ action: "TMRcvPrepared(#{r})", condition: tm_rcv_prepared_condition(variables, r), state: tm_rcv_prepared(variables, r) }, 236 | %{ action: "RMPrepare(#{r})", condition: rm_prepare_condition(variables, r), state: rm_prepare(variables, r) }, 237 | %{ action: "RMChooseToAbort(#{r})", condition: rm_choose_to_abort_condition(variables, r), state: rm_choose_to_abort(variables, r) }, 238 | %{ action: "RMRcvCommitMsg(#{r})", condition: rm_rcv_commit_msg_condition(variables, r), state: rm_rcv_commit_msg(variables, r) }, 239 | %{ action: "RMRcvAbortMsg(#{r})", condition: rm_rcv_abort_msg_condition(variables, r), state: rm_rcv_abort_msg(variables, r) } 240 | ] end 241 | ) 242 | ]) 243 | ) 244 | ) 245 | end 246 | 247 | def decide_action(actions) do 248 | possible_actions = Enum.filter(actions, fn(action) -> action[:condition] end) 249 | different_states = Enum.uniq_by(possible_actions, fn(action) -> action[:state] end) 250 | 251 | if Enum.count(different_states) == 1 do 252 | Enum.at(possible_actions, 0)[:state] 253 | else 254 | actions_names = Enum.map(possible_actions, fn(action) -> action[:action] end) 255 | send @oracle, {self(), actions_names} 256 | 257 | n = receive do 258 | {:ok, n} -> n 259 | end 260 | 261 | Enum.at(possible_actions, n)[:state] 262 | end 263 | end 264 | end 265 | 266 | IO.gets("start?") 267 | TwoPhaseCommit.main( 268 | # *********************************************************************** 269 | # The initial predicate. 270 | # *********************************************************************** 271 | %{ 272 | rm_state: TwoPhaseCommit.rm |> Enum.map(fn (r) -> {r, "working"} end) |> Enum.into(%{ }), 273 | tm_state: "init", 274 | tm_prepared: MapSet.new([]), 275 | msgs: MapSet.new([]) 276 | } 277 | ) 278 | -------------------------------------------------------------------------------- /elixir/test/generated_code/jarros_de_agua_test.exs: -------------------------------------------------------------------------------- 1 | # defmodule JarrosDeAguaTest do 2 | # use ExUnit.Case 3 | # doctest JarrosDeAgua 4 | # test "fromState 9" do 5 | # variables = %{ 6 | # jarro_pequeno: 0, 7 | # jarro_grande: 0 8 | # } 9 | 10 | # expectedStates = [%{ 11 | # jarro_pequeno: 0, 12 | # jarro_grande: 0 13 | # }, 14 | # %{ 15 | # jarro_pequeno: 3, 16 | # jarro_grande: 0 17 | # }, 18 | # %{ 19 | # jarro_pequeno: 0, 20 | # jarro_grande: 5 21 | # }] 22 | 23 | # actions = JarrosDeAgua.next(variables) 24 | # states = Enum.map(actions, fn action -> action[:state] end) 25 | 26 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 27 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 28 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 29 | # end 30 | 31 | # test "fromState 10" do 32 | # variables = %{ 33 | # jarro_pequeno: 3, 34 | # jarro_grande: 0 35 | # } 36 | 37 | # expectedStates = [%{ 38 | # jarro_pequeno: 0, 39 | # jarro_grande: 0 40 | # }, 41 | # %{ 42 | # jarro_pequeno: 3, 43 | # jarro_grande: 0 44 | # }, 45 | # %{ 46 | # jarro_pequeno: 3, 47 | # jarro_grande: 5 48 | # }, 49 | # %{ 50 | # jarro_pequeno: 0, 51 | # jarro_grande: 3 52 | # }] 53 | 54 | # actions = JarrosDeAgua.next(variables) 55 | # states = Enum.map(actions, fn action -> action[:state] end) 56 | 57 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 58 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 59 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 60 | # end 61 | 62 | # test "fromState 11" do 63 | # variables = %{ 64 | # jarro_pequeno: 0, 65 | # jarro_grande: 5 66 | # } 67 | 68 | # expectedStates = [%{ 69 | # jarro_pequeno: 0, 70 | # jarro_grande: 0 71 | # }, 72 | # %{ 73 | # jarro_pequeno: 0, 74 | # jarro_grande: 5 75 | # }, 76 | # %{ 77 | # jarro_pequeno: 3, 78 | # jarro_grande: 5 79 | # }, 80 | # %{ 81 | # jarro_pequeno: 3, 82 | # jarro_grande: 2 83 | # }] 84 | 85 | # actions = JarrosDeAgua.next(variables) 86 | # states = Enum.map(actions, fn action -> action[:state] end) 87 | 88 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 89 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 90 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 91 | # end 92 | 93 | # test "fromState 12" do 94 | # variables = %{ 95 | # jarro_pequeno: 3, 96 | # jarro_grande: 5 97 | # } 98 | 99 | # expectedStates = [%{ 100 | # jarro_pequeno: 3, 101 | # jarro_grande: 0 102 | # }, 103 | # %{ 104 | # jarro_pequeno: 0, 105 | # jarro_grande: 5 106 | # }, 107 | # %{ 108 | # jarro_pequeno: 3, 109 | # jarro_grande: 5 110 | # }] 111 | 112 | # actions = JarrosDeAgua.next(variables) 113 | # states = Enum.map(actions, fn action -> action[:state] end) 114 | 115 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 116 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 117 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 118 | # end 119 | 120 | # test "fromState 13" do 121 | # variables = %{ 122 | # jarro_pequeno: 0, 123 | # jarro_grande: 3 124 | # } 125 | 126 | # expectedStates = [%{ 127 | # jarro_pequeno: 0, 128 | # jarro_grande: 0 129 | # }, 130 | # %{ 131 | # jarro_pequeno: 3, 132 | # jarro_grande: 0 133 | # }, 134 | # %{ 135 | # jarro_pequeno: 0, 136 | # jarro_grande: 5 137 | # }, 138 | # %{ 139 | # jarro_pequeno: 0, 140 | # jarro_grande: 3 141 | # }, 142 | # %{ 143 | # jarro_pequeno: 3, 144 | # jarro_grande: 3 145 | # }] 146 | 147 | # actions = JarrosDeAgua.next(variables) 148 | # states = Enum.map(actions, fn action -> action[:state] end) 149 | 150 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 151 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 152 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 153 | # end 154 | 155 | # test "fromState 14" do 156 | # variables = %{ 157 | # jarro_pequeno: 3, 158 | # jarro_grande: 2 159 | # } 160 | 161 | # expectedStates = [%{ 162 | # jarro_pequeno: 3, 163 | # jarro_grande: 0 164 | # }, 165 | # %{ 166 | # jarro_pequeno: 0, 167 | # jarro_grande: 5 168 | # }, 169 | # %{ 170 | # jarro_pequeno: 3, 171 | # jarro_grande: 5 172 | # }, 173 | # %{ 174 | # jarro_pequeno: 3, 175 | # jarro_grande: 2 176 | # }, 177 | # %{ 178 | # jarro_pequeno: 0, 179 | # jarro_grande: 2 180 | # }] 181 | 182 | # actions = JarrosDeAgua.next(variables) 183 | # states = Enum.map(actions, fn action -> action[:state] end) 184 | 185 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 186 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 187 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 188 | # end 189 | 190 | # test "fromState 15" do 191 | # variables = %{ 192 | # jarro_pequeno: 3, 193 | # jarro_grande: 3 194 | # } 195 | 196 | # expectedStates = [%{ 197 | # jarro_pequeno: 3, 198 | # jarro_grande: 0 199 | # }, 200 | # %{ 201 | # jarro_pequeno: 3, 202 | # jarro_grande: 5 203 | # }, 204 | # %{ 205 | # jarro_pequeno: 0, 206 | # jarro_grande: 3 207 | # }, 208 | # %{ 209 | # jarro_pequeno: 3, 210 | # jarro_grande: 3 211 | # }, 212 | # %{ 213 | # jarro_pequeno: 1, 214 | # jarro_grande: 5 215 | # }] 216 | 217 | # actions = JarrosDeAgua.next(variables) 218 | # states = Enum.map(actions, fn action -> action[:state] end) 219 | 220 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 221 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 222 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 223 | # end 224 | 225 | # test "fromState 16" do 226 | # variables = %{ 227 | # jarro_pequeno: 0, 228 | # jarro_grande: 2 229 | # } 230 | 231 | # expectedStates = [%{ 232 | # jarro_pequeno: 0, 233 | # jarro_grande: 0 234 | # }, 235 | # %{ 236 | # jarro_pequeno: 0, 237 | # jarro_grande: 5 238 | # }, 239 | # %{ 240 | # jarro_pequeno: 3, 241 | # jarro_grande: 2 242 | # }, 243 | # %{ 244 | # jarro_pequeno: 0, 245 | # jarro_grande: 2 246 | # }, 247 | # %{ 248 | # jarro_pequeno: 2, 249 | # jarro_grande: 0 250 | # }] 251 | 252 | # actions = JarrosDeAgua.next(variables) 253 | # states = Enum.map(actions, fn action -> action[:state] end) 254 | 255 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 256 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 257 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 258 | # end 259 | 260 | # test "fromState 17" do 261 | # variables = %{ 262 | # jarro_pequeno: 1, 263 | # jarro_grande: 5 264 | # } 265 | 266 | # expectedStates = [%{ 267 | # jarro_pequeno: 0, 268 | # jarro_grande: 5 269 | # }, 270 | # %{ 271 | # jarro_pequeno: 3, 272 | # jarro_grande: 5 273 | # }, 274 | # %{ 275 | # jarro_pequeno: 3, 276 | # jarro_grande: 3 277 | # }, 278 | # %{ 279 | # jarro_pequeno: 1, 280 | # jarro_grande: 5 281 | # }, 282 | # %{ 283 | # jarro_pequeno: 1, 284 | # jarro_grande: 0 285 | # }] 286 | 287 | # actions = JarrosDeAgua.next(variables) 288 | # states = Enum.map(actions, fn action -> action[:state] end) 289 | 290 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 291 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 292 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 293 | # end 294 | 295 | # test "fromState 18" do 296 | # variables = %{ 297 | # jarro_pequeno: 2, 298 | # jarro_grande: 0 299 | # } 300 | 301 | # expectedStates = [%{ 302 | # jarro_pequeno: 0, 303 | # jarro_grande: 0 304 | # }, 305 | # %{ 306 | # jarro_pequeno: 3, 307 | # jarro_grande: 0 308 | # }, 309 | # %{ 310 | # jarro_pequeno: 0, 311 | # jarro_grande: 2 312 | # }, 313 | # %{ 314 | # jarro_pequeno: 2, 315 | # jarro_grande: 0 316 | # }, 317 | # %{ 318 | # jarro_pequeno: 2, 319 | # jarro_grande: 5 320 | # }] 321 | 322 | # actions = JarrosDeAgua.next(variables) 323 | # states = Enum.map(actions, fn action -> action[:state] end) 324 | 325 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 326 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 327 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 328 | # end 329 | 330 | # test "fromState 19" do 331 | # variables = %{ 332 | # jarro_pequeno: 1, 333 | # jarro_grande: 0 334 | # } 335 | 336 | # expectedStates = [%{ 337 | # jarro_pequeno: 0, 338 | # jarro_grande: 0 339 | # }, 340 | # %{ 341 | # jarro_pequeno: 3, 342 | # jarro_grande: 0 343 | # }, 344 | # %{ 345 | # jarro_pequeno: 1, 346 | # jarro_grande: 5 347 | # }, 348 | # %{ 349 | # jarro_pequeno: 1, 350 | # jarro_grande: 0 351 | # }, 352 | # %{ 353 | # jarro_pequeno: 0, 354 | # jarro_grande: 1 355 | # }] 356 | 357 | # actions = JarrosDeAgua.next(variables) 358 | # states = Enum.map(actions, fn action -> action[:state] end) 359 | 360 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 361 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 362 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 363 | # end 364 | 365 | # test "fromState 20" do 366 | # variables = %{ 367 | # jarro_pequeno: 2, 368 | # jarro_grande: 5 369 | # } 370 | 371 | # expectedStates = [%{ 372 | # jarro_pequeno: 0, 373 | # jarro_grande: 5 374 | # }, 375 | # %{ 376 | # jarro_pequeno: 3, 377 | # jarro_grande: 5 378 | # }, 379 | # %{ 380 | # jarro_pequeno: 2, 381 | # jarro_grande: 0 382 | # }, 383 | # %{ 384 | # jarro_pequeno: 2, 385 | # jarro_grande: 5 386 | # }, 387 | # %{ 388 | # jarro_pequeno: 3, 389 | # jarro_grande: 4 390 | # }] 391 | 392 | # actions = JarrosDeAgua.next(variables) 393 | # states = Enum.map(actions, fn action -> action[:state] end) 394 | 395 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 396 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 397 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 398 | # end 399 | 400 | # test "fromState 21" do 401 | # variables = %{ 402 | # jarro_pequeno: 0, 403 | # jarro_grande: 1 404 | # } 405 | 406 | # expectedStates = [%{ 407 | # jarro_pequeno: 0, 408 | # jarro_grande: 0 409 | # }, 410 | # %{ 411 | # jarro_pequeno: 0, 412 | # jarro_grande: 5 413 | # }, 414 | # %{ 415 | # jarro_pequeno: 1, 416 | # jarro_grande: 0 417 | # }, 418 | # %{ 419 | # jarro_pequeno: 0, 420 | # jarro_grande: 1 421 | # }, 422 | # %{ 423 | # jarro_pequeno: 3, 424 | # jarro_grande: 1 425 | # }] 426 | 427 | # actions = JarrosDeAgua.next(variables) 428 | # states = Enum.map(actions, fn action -> action[:state] end) 429 | 430 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 431 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 432 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 433 | # end 434 | 435 | # test "fromState 22" do 436 | # variables = %{ 437 | # jarro_pequeno: 3, 438 | # jarro_grande: 4 439 | # } 440 | 441 | # expectedStates = [%{ 442 | # jarro_pequeno: 3, 443 | # jarro_grande: 0 444 | # }, 445 | # %{ 446 | # jarro_pequeno: 3, 447 | # jarro_grande: 5 448 | # }, 449 | # %{ 450 | # jarro_pequeno: 2, 451 | # jarro_grande: 5 452 | # }, 453 | # %{ 454 | # jarro_pequeno: 3, 455 | # jarro_grande: 4 456 | # }, 457 | # %{ 458 | # jarro_pequeno: 0, 459 | # jarro_grande: 4 460 | # }] 461 | 462 | # actions = JarrosDeAgua.next(variables) 463 | # states = Enum.map(actions, fn action -> action[:state] end) 464 | 465 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 466 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 467 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 468 | # end 469 | 470 | # test "fromState 23" do 471 | # variables = %{ 472 | # jarro_pequeno: 3, 473 | # jarro_grande: 1 474 | # } 475 | 476 | # expectedStates = [%{ 477 | # jarro_pequeno: 3, 478 | # jarro_grande: 0 479 | # }, 480 | # %{ 481 | # jarro_pequeno: 3, 482 | # jarro_grande: 5 483 | # }, 484 | # %{ 485 | # jarro_pequeno: 0, 486 | # jarro_grande: 1 487 | # }, 488 | # %{ 489 | # jarro_pequeno: 3, 490 | # jarro_grande: 1 491 | # }, 492 | # %{ 493 | # jarro_pequeno: 0, 494 | # jarro_grande: 4 495 | # }] 496 | 497 | # actions = JarrosDeAgua.next(variables) 498 | # states = Enum.map(actions, fn action -> action[:state] end) 499 | 500 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 501 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 502 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 503 | # end 504 | 505 | # test "fromState 24" do 506 | # variables = %{ 507 | # jarro_pequeno: 0, 508 | # jarro_grande: 4 509 | # } 510 | 511 | # expectedStates = [%{ 512 | # jarro_pequeno: 0, 513 | # jarro_grande: 0 514 | # }, 515 | # %{ 516 | # jarro_pequeno: 0, 517 | # jarro_grande: 5 518 | # }, 519 | # %{ 520 | # jarro_pequeno: 3, 521 | # jarro_grande: 4 522 | # }, 523 | # %{ 524 | # jarro_pequeno: 3, 525 | # jarro_grande: 1 526 | # }, 527 | # %{ 528 | # jarro_pequeno: 0, 529 | # jarro_grande: 4 530 | # }] 531 | 532 | # actions = JarrosDeAgua.next(variables) 533 | # states = Enum.map(actions, fn action -> action[:state] end) 534 | 535 | # assert Enum.sort(Enum.uniq(states)) == Enum.sort(Enum.uniq(expectedStates)) 536 | # assert Enum.all?(actions, fn action -> Enum.member?(expectedStates, action[:state]) end) 537 | # assert Enum.all?(expectedStates, fn s -> Enum.member?(states, s) end) 538 | # end 539 | 540 | # end 541 | --------------------------------------------------------------------------------