├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── main.yml │ └── profile.yml ├── .gitignore ├── .hlint.yaml ├── AARCH64 ├── Dockerfile ├── LICENSE ├── README.md ├── app └── Main.hs ├── example.cairo ├── fourmolu.yaml ├── hie.yaml ├── horus-check.cabal ├── horus_logo.png ├── scripts └── ci │ ├── install-compiler.sh │ ├── install-cvc5-linux.sh │ ├── install-cvc5-macos-aarch64.sh │ ├── install-cvc5-macos.sh │ ├── install-cvc5.sh │ ├── install-z3-from-source.sh │ ├── install-z3-linux-amd64.sh │ ├── install-z3-macos.sh │ └── test_data_extraction.py ├── src └── Horus │ ├── Arguments.hs │ ├── CFGBuild.hs │ ├── CFGBuild │ └── Runner.hs │ ├── CairoSemantics.hs │ ├── CairoSemantics │ └── Runner.hs │ ├── CallStack.hs │ ├── Command │ └── SMT.hs │ ├── ContractDefinition.hs │ ├── ContractInfo.hs │ ├── Expr.hs │ ├── Expr │ ├── SMT.hs │ ├── Std.hs │ ├── Type.hs │ ├── Type │ │ └── SMT.hs │ ├── Util.hs │ └── Vars.hs │ ├── FunctionAnalysis.hs │ ├── Global.hs │ ├── Global │ └── Runner.hs │ ├── Instruction.hs │ ├── JSON │ └── Util.hs │ ├── Label.hs │ ├── Logger.hs │ ├── Logger │ └── Runner.hs │ ├── Module.hs │ ├── Module │ └── Runner.hs │ ├── Preprocessor.hs │ ├── Preprocessor │ ├── Runner.hs │ └── Solvers.hs │ ├── Program.hs │ ├── SW │ ├── Builtin.hs │ ├── CairoType.hs │ ├── CairoType │ │ ├── JSON.hs │ │ ├── Lexer.x │ │ └── Parser.y │ ├── FuncSpec.hs │ ├── Identifier.hs │ ├── ScopedName.hs │ ├── Std.hs │ └── Storage.hs │ ├── Util.hs │ └── Z3Util.hs ├── stack.yaml ├── stack.yaml.lock └── tests ├── entrypoint.sh ├── profiling-entrypoint.sh └── resources ├── bats-template ├── generate-bats.sh ├── generate-profiling-bats.sh └── golden ├── balance_storage_var.cairo ├── balance_storage_var.gold ├── bitwise_simple_fake.cairo ├── bitwise_simple_fake.gold ├── bitwise_simple_not_fake.cairo ├── bitwise_simple_not_fake.gold ├── contra.cairo ├── contra.gold ├── contra_lvars.cairo ├── contra_lvars.gold ├── ecdsa_simple_fake.cairo ├── ecdsa_simple_fake.gold ├── func_add_rec.cairo ├── func_add_rec.gold ├── func_chain.cairo ├── func_chain.gold ├── func_id.cairo ├── func_id.gold ├── func_id_id.cairo ├── func_id_id.gold ├── func_if.cairo ├── func_if.gold ├── func_mul3.cairo ├── func_mul3.gold ├── func_multiple_ret.cairo ├── func_multiple_ret.gold ├── func_peano_prod.cairo ├── func_peano_prod.gold ├── func_pred.cairo ├── func_pred.gold ├── func_pred_succ_id.cairo ├── func_pred_succ_id.gold ├── func_prod_mul.cairo ├── func_prod_mul.gold ├── func_square.cairo ├── func_square.gold ├── func_succ.cairo ├── func_succ.gold ├── func_succ_pred_id.cairo ├── func_succ_pred_id.gold ├── func_succ_pred_id_pre.cairo ├── func_succ_pred_id_pre.gold ├── func_succ_succ.cairo ├── func_succ_succ.gold ├── get_block_timestamp.cairo ├── get_block_timestamp.gold ├── get_caller_address.cairo ├── get_caller_address.gold ├── get_contract_address.cairo ├── get_contract_address.gold ├── if_test.cairo ├── if_test.gold ├── if_test_no_else.cairo ├── if_test_no_else.gold ├── inline_balance_svar_simple.cairo ├── inline_balance_svar_simple.gold ├── inline_balance_svar_simple_asm.cairo ├── inline_balance_svar_simple_asm.gold ├── inline_balance_svar_simple_twice.cairo ├── inline_balance_svar_simple_twice.gold ├── inline_basic.cairo ├── inline_basic.gold ├── inline_if.cairo ├── inline_if.gold ├── inline_if_sat.cairo ├── inline_if_sat.gold ├── inline_many.cairo ├── inline_many.gold ├── inline_many_sat.cairo ├── inline_many_sat.gold ├── inline_pre_success.cairo ├── inline_pre_success.gold ├── inline_range_check.cairo ├── inline_range_check.gold ├── inline_small.cairo ├── inline_small.gold ├── inline_stack_minimal.cairo ├── inline_stack_minimal.gold ├── inline_stack_minimal_sat.cairo ├── inline_stack_minimal_sat.gold ├── inline_svar.cairo ├── inline_svar.gold ├── inline_svar_read_write.cairo ├── inline_svar_read_write.gold ├── inline_svar_read_write_many.cairo ├── inline_svar_read_write_many.gold ├── inline_svar_sat.cairo ├── inline_svar_sat.gold ├── invalidate_sat.cairo ├── invalidate_sat.gold ├── invalidate_unsat.cairo ├── invalidate_unsat.gold ├── loop-no-invariant.cairo ├── loop-no-invariant.gold ├── lvar_addr.cairo ├── lvar_addr.gold ├── lvar_basic.cairo ├── lvar_basic.gold ├── lvar_capture.cairo ├── lvar_capture.gold ├── lvar_struct.cairo ├── lvar_struct.gold ├── many-multipliers.cairo ├── many-multipliers.gold ├── oneoff.sh ├── optimise_notsplit_many.cairo ├── optimise_notsplit_many.gold ├── peano.cairo ├── peano.gold ├── pedersen_simple_fake.cairo ├── pedersen_simple_fake.gold ├── post_with_svar.cairo ├── post_with_svar.gold ├── range_check.cairo ├── range_check.gold ├── range_check_discard.cairo ├── range_check_discard.gold ├── range_check_fake.cairo ├── range_check_fake.gold ├── range_check_past_end.cairo ├── range_check_past_end.gold ├── range_check_rec.cairo ├── range_check_rec.gold ├── range_check_simple.cairo ├── range_check_simple.gold ├── range_check_simple_fake.cairo ├── range_check_simple_fake.gold ├── range_check_simple_fake_no_builtin.cairo ├── range_check_simple_fake_no_builtin.gold ├── range_check_simple_one_greater.cairo ├── range_check_simple_one_greater.gold ├── range_check_simple_one_less.cairo ├── range_check_simple_one_less.gold ├── sat_cause_42.cairo ├── sat_cause_42.gold ├── stack_minimal.cairo ├── stack_minimal.gold ├── stack_storage_var.cairo ├── stack_storage_var.gold ├── storage_pre.cairo ├── storage_pre.gold ├── toy_amm ├── display.sh ├── math.cairo ├── toy_amm_split.cairo └── toy_amm_split.gold ├── two_sats.cairo ├── two_sats.gold ├── violated_call_pre.cairo ├── violated_call_pre.gold ├── violated_spec.cairo └── violated_spec.gold /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | > **Note:** If you do not have time to write full reproduction details, please 11 | > still open an issue! Even if your report is as simple as "there's a typo in 12 | > the output", this is still helpful to us! 13 | 14 | **Describe the bug** 15 | A clear and concise description of what the bug is. 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **To Reproduce** 21 | If you have time, please include the following to make it easier for the maintainers to reproduce the bug: 22 | * Source-code of a minimum working example with relevant annotations 23 | * Versions of SMT solvers used (`z3 --version`, `mathsat -version`, `cvc5 --version`). 24 | * Your operating system, CPU architecture, and Horus version. 25 | 26 | **Operating system** 27 | [ ] Linux 28 | [ ] MacOS 29 | [ ] Windows 30 | [ ] Other (please write) 31 | 32 | **CPU architecture** 33 | [ ] x86-64 34 | [ ] AArch64 35 | [ ] Other (please write) 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | > **Note.** If you do not have the time to answer all these questions, _we 11 | > still want to hear from you_! Even if your feature request is as simple as 12 | > "dump more human-readable queries", it is still helpful to us! 13 | 14 | **Is your feature request related to a problem? Please describe.** 15 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 16 | 17 | **Describe the solution you'd like** 18 | A clear and concise description of what you want to happen. 19 | 20 | **Describe alternatives you've considered** 21 | A clear and concise description of any alternative solutions or features you've considered. 22 | 23 | **Additional context** 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | env: 8 | fourmolu_url: https://github.com/fourmolu/fourmolu/releases/download/v0.7.0.1/fourmolu-0.7.0.1-linux-x86_64 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - uses: actions/setup-python@v4 18 | with: 19 | python-version: '3.9' 20 | 21 | - uses: actions/cache@v2 22 | name: Cache python dependencies 23 | with: 24 | path: ${{ env.pythonLocation }} 25 | key: ${{ env.pythonLocation }}-${{ env.horus_compile_version }} 26 | restore-keys: 27 | ${{ env.pythonLocation }}- 28 | 29 | - name: Install python dependencies 30 | run: 31 | pip install cairo-lang==0.10.1 32 | 33 | - name: Install linters 34 | run: | 35 | curl -L -o /usr/local/bin/fourmolu ${{env.fourmolu_url}} 36 | chmod a+x /usr/local/bin/fourmolu 37 | 38 | - name: Run fourmolu 39 | run: | 40 | find app src -name *.hs -exec fourmolu --mode 'check' {} + 41 | 42 | - name: Install libtinfo (HLint dependency) 43 | run: | 44 | sudo apt-get install libtinfo6 45 | sudo apt-get install libtinfo5 46 | 47 | - name: 'Set up HLint' 48 | uses: rwe/actions-hlint-setup@v1 49 | with: 50 | version: '3.4.1' 51 | 52 | - name: 'Run HLint' 53 | uses: rwe/actions-hlint-run@v2 54 | with: 55 | path: '["src/", "app/"]' 56 | fail-on: suggestion 57 | 58 | - name: Run cairo-format 59 | run: 60 | find tests/resources/golden -name *.cairo -exec cairo-format -c {} + 61 | 62 | test: 63 | runs-on: 64 | labels: ubuntu-latest 65 | 66 | steps: 67 | - uses: actions/checkout@v3 68 | 69 | - name: Install SMT solvers 70 | run: | 71 | echo "${{ secrets.MATHSAT_INSTALL_DEPLOY_KEY }}" >> mathsat_id_ed25519 72 | chmod 400 mathsat_id_ed25519 73 | ssh-agent bash -c 'ssh-add mathsat_id_ed25519; git clone git@github.com:NethermindEth/mathsat-install.git' 74 | cp mathsat-install/install-mathsat.sh ./scripts/ci/install-mathsat.sh 75 | sh ./scripts/ci/install-z3-linux-amd64.sh 76 | sh ./scripts/ci/install-cvc5-linux.sh 77 | sh ./scripts/ci/install-mathsat.sh 78 | 79 | - uses: actions/setup-python@v4 80 | with: 81 | python-version: '3.9' 82 | 83 | - name: Setup BATS 84 | uses: mig4/setup-bats@v1 85 | with: 86 | bats-version: 1.8.2 87 | 88 | - uses: freckle/stack-cache-action@main 89 | 90 | - uses: actions/cache@v3 91 | name: Cache python dependencies 92 | with: 93 | path: ${{ env.pythonLocation }} 94 | key: ${{ env.pythonLocation }}-${{ env.horus_compile_version }} 95 | 96 | - name: Build 97 | run: | 98 | jobs=$(getconf _NPROCESSORS_ONLN) 99 | stack --jobs "$jobs" install --ghc-options -Werror 100 | 101 | - name: Install python dependencies 102 | env: 103 | USER: ${{ secrets.PYPI_USER }} 104 | PASS: ${{ secrets.PYPI_PASSWORD }} 105 | run: | 106 | pip install setuptools wheel 107 | bash ./scripts/ci/install-compiler.sh 108 | 109 | - name: Test 110 | run: bash ./tests/entrypoint.sh 111 | -------------------------------------------------------------------------------- /.github/workflows/profile.yml: -------------------------------------------------------------------------------- 1 | name: profile 2 | 3 | on: 4 | workflow_dispatch: 5 | env: 6 | fourmolu_url: https://github.com/fourmolu/fourmolu/releases/download/v0.7.0.1/fourmolu-0.7.0.1-linux-x86_64 7 | 8 | jobs: 9 | profile: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Install SMT solvers 16 | run: | 17 | sh ./scripts/ci/install-z3-linux-amd64.sh 18 | sh ./scripts/ci/install-cvc5-linux.sh 19 | 20 | - uses: actions/setup-python@v4 21 | with: 22 | python-version: '3.9' 23 | 24 | - name: Setup BATS 25 | uses: mig4/setup-bats@v1 26 | with: 27 | bats-version: 1.8.2 28 | 29 | - uses: freckle/stack-cache-action@main 30 | 31 | - uses: actions/cache@v3 32 | name: Cache python dependencies 33 | with: 34 | path: ${{ env.pythonLocation }} 35 | key: ${{ env.pythonLocation }}-${{ env.horus_compile_version }} 36 | 37 | - name: Build 38 | run: | 39 | jobs=$(getconf _NPROCESSORS_ONLN) 40 | stack --jobs "$jobs" install 41 | 42 | - name: Install python dependencies 43 | env: 44 | USER: ${{ secrets.PYPI_USER }} 45 | PASS: ${{ secrets.PYPI_PASSWORD }} 46 | run: | 47 | pip install setuptools wheel 48 | bash ./scripts/ci/install-compiler.sh 49 | 50 | - name: Test 51 | run: | 52 | bash ./tests/profiling-entrypoint.sh 53 | echo "-----Profiling results-----" 54 | python3 ./scripts/ci/test_data_extraction.py 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .stack-work/ 3 | .vscode/ 4 | dist-newstyle/ 5 | **/__pycache__ 6 | tests/resources/golden/**.out 7 | tests/resources/*.bats 8 | tests/resources/golden/*.json 9 | tests/resources/golden/*.temp 10 | horus-compile/ 11 | -------------------------------------------------------------------------------- /.hlint.yaml: -------------------------------------------------------------------------------- 1 | # HLint configuration file 2 | # https://github.com/ndmitchell/hlint 3 | ########################## 4 | 5 | 6 | # Sometimes we create a record type with just one field, that is meant to be 7 | # extended later. In such cases, this warning is non-helpful. 8 | - ignore: {name: "Use newtype instead of data"} 9 | 10 | # Due to simplified subsumption[1] sometimes eta-expansion is necessary. 11 | # 12 | # [1]: https://gitlab.haskell.org/ghc/ghc/-/wikis/migration/9.0#simplified-subsumption 13 | - ignore: {name: "Avoid lambda"} 14 | -------------------------------------------------------------------------------- /AARCH64: -------------------------------------------------------------------------------- 1 | horus-checker build command 2 | --------------------------- 3 | C_INCLUDE_PATH="`xcrun --show-sdk-path`/usr/include/ffi" stack --extra-include-dirs=/opt/homebrew/opt/z3@4.10.2/include --extra-lib-dirs=/opt/homebrew/opt/z3@4.10.2/lib install 4 | 5 | horus-compile build command 6 | --------------------------- 7 | CFLAGS=-I`brew --prefix gmp`/include LDFLAGS=-L`brew --prefix gmp`/lib poetry install 8 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7-bullseye 2 | 3 | WORKDIR /home 4 | 5 | RUN curl -sSL https://get.haskellstack.org/ | sh 6 | 7 | # Install sudo, vim and dependencies. 8 | RUN apt-get update \ 9 | && apt-get install sudo \ 10 | && apt-get install -y llvm-11 llvm-11-dev \ 11 | && apt-get install -y libnuma-dev \ 12 | && apt-get install -y vim 13 | 14 | # Install solvers 15 | COPY scripts/ scripts/ 16 | RUN sh scripts/ci/install-cvc5-linux.sh \ 17 | && if [ $(arch) = "aarch64" ]; then sh scripts/ci/install-z3-from-source.sh; else sh scripts/ci/install-z3-linux-amd64.sh; fi \ 18 | && rm -rf scripts/ 19 | 20 | # Install horus-checker 21 | COPY stack.yaml horus-checker/ 22 | COPY horus-check.cabal horus-checker/ 23 | COPY app/ horus-checker/app/ 24 | COPY src/ horus-checker/src/ 25 | RUN cd horus-checker/ \ 26 | && stack --install-ghc setup 27 | RUN cd horus-checker/ \ 28 | && stack install --local-bin-path /usr/local/bin/ \ 29 | && cd ../ \ 30 | && rm -rf horus-checker/ 31 | 32 | # Install horus-compile 33 | COPY horus-compile/ horus-compile/ 34 | RUN pip3 install horus-compile/ \ 35 | && rm -rf horus-compile/ 36 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import Control.Applicative ((<**>)) 4 | import Control.Monad (unless, when) 5 | import Control.Monad.Except (ExceptT, liftEither, runExceptT) 6 | import Control.Monad.IO.Class (liftIO) 7 | import Data.Aeson (FromJSON, Result (..), eitherDecodeFileStrict, fromJSON) 8 | import Data.Aeson.Extra.Merge (lodashMerge) 9 | import Data.Foldable (for_) 10 | import Data.IORef (newIORef) 11 | import Data.Text (Text) 12 | import Data.Text qualified as T 13 | import Data.Text.IO qualified as TextIO (putStrLn) 14 | import Data.Version (Version, makeVersion, parseVersion, showVersion) 15 | import Lens.Micro ((%~), (<&>)) 16 | import Options.Applicative 17 | ( execParser 18 | , fullDesc 19 | , header 20 | , helper 21 | , info 22 | , progDescDoc 23 | ) 24 | import Options.Applicative.Help.Pretty (text) 25 | import Paths_horus_check (version) 26 | import Text.ParserCombinators.ReadP (readP_to_S) 27 | 28 | import Horus.Arguments (Arguments (..), argParser, fileArgument, specFileArgument) 29 | import Horus.ContractDefinition (ContractDefinition, cdSpecs, cd_version) 30 | import Horus.ContractInfo (mkContractInfo) 31 | import Horus.Global (HorusResult (..), SolvingInfo (..), cfg_version, solveContract) 32 | import Horus.Global.Runner qualified as Global (Env (..), run) 33 | import Horus.SW.Std (stdSpecs) 34 | import Horus.Util (tShow) 35 | 36 | type EIO = ExceptT Text IO 37 | 38 | hint :: Text 39 | hint = 40 | "A result of 'Unknown' indicates that Horus was not able to prove that the \n\ 41 | \specification holds *nor* find a counterxample. For functions yielding\n\ 42 | \'Unknown', try increasing the solver timeout by passing '-t '.\n\ 43 | \The default is 2000ms.\n\ 44 | \\n\ 45 | \The timeout is per-SMT-query, which means that for a compiled contract\n\ 46 | \containing 7 functions that requires a total of 11 queries, a 5000ms timeout\n\ 47 | \implies a maximum running time of 55 seconds. Each function will give rise to\n\ 48 | \at least one query, and in general, queries are in 1-1 correspondence with \n\ 49 | \branches of control flow (so a function with a single if-then-else will require\n\ 50 | \2 queries).\n\ 51 | \\n\ 52 | \Example:\n\ 53 | \ $ horus-check -s cvc5 -t 5000 a.json" 54 | 55 | issuesMsg :: Text 56 | issuesMsg = 57 | "Horus is currently in the *alpha* stage. Please be aware of the\n\ 58 | \known issues: https://github.com/NethermindEth/horus-checker/issues\n" 59 | 60 | compatibleHorusCompileVersionLower :: Version 61 | compatibleHorusCompileVersionLower = makeVersion [0, 0, 6, 8] 62 | compatibleHorusCompileVersionHigher :: Version 63 | compatibleHorusCompileVersionHigher = makeVersion [0, 0, 7] 64 | 65 | {- | The main entrypoint of everything that happens in our monad stack. 66 | The contract is a 1-1 representation of the data in the compiled JSON file. 67 | The contract is then used to create a 'ContractInfo' which is a more 68 | convenient representation of the contract. 69 | We run `solveContract`, which is the entrypoint into the *rest* of the 70 | program, and gather the results for pretty-printing. 71 | -} 72 | main' :: Arguments -> FilePath -> FilePath -> EIO () 73 | main' Arguments{..} filename specFileName = do 74 | contract <- eioDecodeFileStrict filename specFileName <&> cdSpecs %~ (<> stdSpecs) 75 | guardVersion contract 76 | contractInfo <- mkContractInfo contract 77 | configRef <- liftIO (newIORef arg_config) 78 | let env = Global.Env{e_config = configRef, e_contractInfo = contractInfo} 79 | infos <- liftIO (Global.run env solveContract) >>= liftEither 80 | for_ infos $ \si -> liftIO $ do 81 | TextIO.putStrLn (ppSolvingInfo si) 82 | let unknowns = [res | res@(Timeout{}) <- map si_result infos] 83 | unless (null unknowns) $ liftIO (TextIO.putStrLn hint') 84 | where 85 | hint' = "\ESC[33m" <> (T.strip . T.unlines . map ("hint: " <>) . T.lines) hint <> "\ESC[0m" 86 | guardVersion :: ContractDefinition -> EIO () 87 | guardVersion cd = do 88 | compilerVersion <- case [ x 89 | | x@(_, lst) <- readP_to_S parseVersion (cd_version cd) 90 | , null lst 91 | ] of 92 | [(v, [])] -> pure v 93 | _ -> fail $ "Wrong version format: " <> cd_version cd 94 | when 95 | ( compilerVersion < compatibleHorusCompileVersionLower 96 | || compilerVersion >= compatibleHorusCompileVersionHigher 97 | ) 98 | . fail 99 | . concat 100 | $ [ "The *.json on input has been compiled with an incompatible version of Horus-compile.\nExpected: " 101 | , ">=" 102 | , showVersion compatibleHorusCompileVersionLower 103 | , ", <" 104 | , showVersion compatibleHorusCompileVersionHigher 105 | , " but got: " 106 | , showVersion compilerVersion 107 | ] 108 | 109 | eioDecodeFileStrict :: FromJSON a => FilePath -> FilePath -> EIO a 110 | eioDecodeFileStrict contractFile specFile = do 111 | mbContract <- liftIO (eitherDecodeFileStrict contractFile) 112 | mbSpecs <- liftIO (eitherDecodeFileStrict specFile) 113 | case (mbContract, mbSpecs) of 114 | (Left err, _) -> fail ("Malformed contract *.json. Cause: " ++ err) 115 | (_, Left err) -> fail ("Malformed specification *.json. Cause: " ++ err) 116 | (Right contract, Right specifications) -> do 117 | let mbRes = fromJSON $ lodashMerge contract specifications 118 | case mbRes of 119 | Error err -> fail ("Malformed contract *.json. Cause: " ++ err) 120 | Success res -> pure res 121 | 122 | ppSolvingInfo :: SolvingInfo -> Text 123 | ppSolvingInfo SolvingInfo{..} = 124 | si_moduleName <> inlinedIndicator <> "\n" <> tShow si_result <> "\n" 125 | where 126 | inlinedIndicator = if si_inlinable then " [inlined]" else "" 127 | 128 | {- | Main entrypoint of the program. 129 | Cases 130 | ===== 131 | 1. No arguments are passed. In this case, we print the help message. 132 | 2. The `--version` flag is passed. In this case, we print the version number. 133 | 3. No file is passed. In this case, we print an error. 134 | 4. A file is passed. In this case, we run `main'`. 135 | -} 136 | main :: IO () 137 | main = do 138 | TextIO.putStrLn issuesMsg' 139 | arguments <- execParser opts 140 | if cfg_version (arg_config arguments) 141 | then 142 | putStrLn . concat $ 143 | [ "Horus version: " 144 | , showVersion version 145 | , "\nHorus-compile (required): " 146 | , ">=" 147 | , showVersion compatibleHorusCompileVersionLower 148 | , ", <" 149 | , showVersion compatibleHorusCompileVersionHigher 150 | ] 151 | else case (arg_fileName arguments, arg_specFile arguments) of 152 | (Nothing, _) -> putStrLn "Missing compiled JSON file. Use --help for more information." 153 | (_, Nothing) -> putStrLn "Missing specification JSON file. Use --help for more information." 154 | (Just filename, Just specFileName) -> do 155 | runExceptT (main' arguments filename specFileName) >>= either (fail . T.unpack) pure 156 | where 157 | issuesMsg' = 158 | "\ESC[33m" <> (T.strip . T.unlines . map ("Warning: " <>) . T.lines) issuesMsg <> "\ESC[0m\n" 159 | opts = 160 | info 161 | (argParser <**> helper) 162 | ( fullDesc 163 | <> progDescDoc 164 | ( Just $ 165 | text "Verifies " 166 | <> text (T.unpack fileArgument) 167 | <> text " (a JSON contract compiled with horus-compile) with the specification file " 168 | <> text (T.unpack specFileArgument) 169 | <> text " provided by horus-compile \n\n" 170 | <> text "Example using solver cvc5 (default):\n" 171 | <> text " $ horus-check a.json spec.json\n\n" 172 | <> text "Example using solver mathsat:\n" 173 | <> text " $ horus-check -s mathsat a.json spec.json\n\n" 174 | <> text "Example using solvers z3, mathsat:\n" 175 | <> text " $ horus-check -s z3 -s mathsat a.json spec.json\n" 176 | ) 177 | <> header "horus-check: an SMT-based checker for StarkNet contracts" 178 | ) 179 | -------------------------------------------------------------------------------- /example.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | struct Stack { 4 | value: felt, 5 | next: Stack*, 6 | } 7 | 8 | namespace _Stack { 9 | func empty() -> (stack: Stack*) { 10 | return (cast(0, Stack*),); 11 | } 12 | 13 | func add(stack: Stack*) -> (stack: Stack*) { 14 | let x = stack.value; 15 | let y = stack.next.value; 16 | return (new Stack(value=x + y, next=stack.next.next),); 17 | } 18 | 19 | func lit(stack: Stack*, i: felt) -> (stack: Stack*) { 20 | return (new Stack(value=i, next=stack),); 21 | } 22 | 23 | func top(stack: Stack*) -> (res: felt) { 24 | return (stack.value,); 25 | } 26 | } 27 | 28 | // Perform some example operations on a stack. 29 | @external 30 | func main () -> (res : felt) { 31 | let (stack) = _Stack.empty(); 32 | let (stack) = _Stack.lit(stack, 5); 33 | let (stack) = _Stack.lit(stack, 6); 34 | let (stack) = _Stack.add(stack); 35 | let (top) = _Stack.top(stack); 36 | return (res=top); 37 | } 38 | -------------------------------------------------------------------------------- /fourmolu.yaml: -------------------------------------------------------------------------------- 1 | indentation: 2 2 | comma-style: leading # for lists, tuples etc. - can also be 'trailing' 3 | import-export-comma-style: leading # for module import export lists - can also be 'trailing' 4 | record-brace-space: false # rec {x = 1} vs. rec{x = 1} 5 | indent-wheres: false # 'false' means save space by only half-indenting the 'where' keyword 6 | diff-friendly-import-export: false # 'false' uses Ormolu-style lists 7 | respectful: true # don't be too opinionated about newlines etc. 8 | haddock-style: multi-line # '--' vs. '{-' 9 | newlines-between-decls: 1 # number of newlines between top-level declarations 10 | fixities: [] # fixity information, see the section about fixities below. 11 | -------------------------------------------------------------------------------- /hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | stack: 3 | - path: "./src/Horus" 4 | component: "horus-check:lib" 5 | 6 | - path: "./app/Main.hs" 7 | component: "horus-check:exe:horus-check" 8 | 9 | - path: "./app/disasm/Main.hs" 10 | component: "horus-check:exe:horus-disasm" 11 | -------------------------------------------------------------------------------- /horus-check.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: horus-check 3 | version: 0.1.0.1 4 | category: Language 5 | 6 | common deps 7 | build-depends: base 8 | default-language: Haskell2010 9 | default-extensions: 10 | AllowAmbiguousTypes 11 | ConstraintKinds 12 | DataKinds 13 | DeriveFunctor 14 | DerivingStrategies 15 | FlexibleContexts 16 | FlexibleInstances 17 | GADTs 18 | GeneralizedNewtypeDeriving 19 | ImportQualifiedPost 20 | KindSignatures 21 | MultiParamTypeClasses 22 | OverloadedStrings 23 | PatternSynonyms 24 | PolyKinds 25 | RankNTypes 26 | RecordWildCards 27 | ScopedTypeVariables 28 | StandaloneDeriving 29 | StandaloneKindSignatures 30 | TupleSections 31 | TypeApplications 32 | TypeFamilies 33 | TypeOperators 34 | ViewPatterns 35 | ghc-options: 36 | -Weverything 37 | -Wno-safe -Wno-unsafe -Wno-implicit-prelude 38 | -Wno-all-missed-specializations 39 | -Wno-missing-deriving-strategies 40 | -Wno-missing-home-modules 41 | -Wno-missing-import-lists 42 | -Wno-missing-local-signatures 43 | -Wno-missing-safe-haskell-mode 44 | -Wno-unticked-promoted-constructors 45 | -Wno-unused-packages 46 | extra-libraries: 47 | z3 48 | 49 | library 50 | import: deps 51 | hs-source-dirs: src 52 | build-tool-depends: alex:alex, happy:happy 53 | exposed-modules: 54 | Horus.Arguments 55 | Horus.CallStack 56 | Horus.CFGBuild 57 | Horus.ContractDefinition 58 | Horus.ContractInfo 59 | Horus.FunctionAnalysis 60 | Horus.Global 61 | Horus.Global.Runner 62 | Horus.Instruction 63 | Horus.Label 64 | Horus.Module 65 | Horus.Program 66 | Horus.SW.ScopedName 67 | Horus.SW.Std 68 | Horus.Util 69 | other-modules: 70 | Horus.CFGBuild.Runner 71 | Horus.CairoSemantics 72 | Horus.CairoSemantics.Runner 73 | Horus.Command.SMT 74 | Horus.Expr 75 | Horus.Expr.SMT 76 | Horus.Expr.Std 77 | Horus.Expr.Type 78 | Horus.Expr.Type.SMT 79 | Horus.Expr.Util 80 | Horus.Expr.Vars 81 | Horus.JSON.Util 82 | Horus.Logger 83 | Horus.Logger.Runner 84 | Horus.Module.Runner 85 | Horus.Preprocessor 86 | Horus.Preprocessor.Runner 87 | Horus.Preprocessor.Solvers 88 | Horus.SW.Builtin 89 | Horus.SW.CairoType 90 | Horus.SW.CairoType.JSON 91 | Horus.SW.CairoType.Lexer 92 | Horus.SW.CairoType.Parser 93 | Horus.SW.FuncSpec 94 | Horus.SW.Identifier 95 | Horus.SW.Storage 96 | Horus.Z3Util 97 | build-depends: 98 | aeson, 99 | array, 100 | constraints, 101 | containers, 102 | directory, 103 | co-log-core, 104 | dlist, 105 | filepath, 106 | free, 107 | hashable, 108 | microlens, 109 | microlens-ghc, 110 | microlens-mtl, 111 | mtl, 112 | optparse-applicative, 113 | pretty-simple, 114 | safe-exceptions, 115 | simple-smt, 116 | singletons, 117 | some, 118 | text, 119 | transformers, 120 | vinyl, 121 | z3, 122 | 123 | executable horus-check 124 | import: deps 125 | hs-source-dirs: app 126 | main-is: Main.hs 127 | build-depends: 128 | aeson, 129 | aeson-extra, 130 | containers, 131 | directory, 132 | filepath, 133 | horus-check, 134 | microlens, 135 | monad-logger, 136 | mtl, 137 | optparse-applicative, 138 | pretty-simple, 139 | text, 140 | other-modules: 141 | Paths_horus_check 142 | ghc-options: 143 | -Wno-prepositive-qualified-module 144 | -------------------------------------------------------------------------------- /horus_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NethermindEth/horus-checker/9f9928babf5a8784592bf585be3ea12dbc14e19c/horus_logo.png -------------------------------------------------------------------------------- /scripts/ci/install-compiler.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | versions=$(pip install horus-compile== --extra-index-url http://139.162.29.35/ --trusted-host 139.162.29.35 2>&1 \ 4 | | grep -oE '(\(.*\))' \ 5 | | awk -F:\ '{print$NF}' \ 6 | | sed -E 's/( |\))//g' \ 7 | | tr ',' '\n') 8 | 9 | bounds=$(stack run horus-check -- --version 2>&1 \ 10 | | grep 'Horus-compile (required):' \ 11 | | grep -oE '>=.*<.*' \ 12 | | sed -E 's/(>=| |<)//g' \ 13 | | tr ',' '\n') 14 | 15 | bounds=($bounds) 16 | 17 | lower=${bounds[0]} 18 | upper=${bounds[1]} 19 | 20 | version2int() { 21 | local -i intversion 22 | local -a version 23 | local IFS="." 24 | 25 | version=($1) 26 | if [[ -n ${version[3]} ]]; then 27 | intversion=$(( ${version[0]}*10**4 + ${version[1]}*10**3 + ${version[2]}*10**2 + ${version[3]} )) 28 | else 29 | intversion=$(( ${version[0]}*10**4 + ${version[1]}*10**3 + ${version[2]}*10**2)) 30 | fi 31 | echo $intversion 32 | } 33 | 34 | intlower=$(version2int ${lower}) 35 | intupper=$(version2int ${upper}) 36 | 37 | for elem in $versions; 38 | do 39 | intelem=$(version2int ${elem}) 40 | if (( $intelem >= $intlower && $intelem < $intupper )); 41 | then 42 | filtered+=($elem); 43 | fi 44 | done 45 | 46 | lastversion=${filtered[-1]} 47 | 48 | pip install horus-compile==${lastversion} --extra-index-url http://${USER}:${PASS}@139.162.29.35/ --trusted-host 139.162.29.35 -------------------------------------------------------------------------------- /scripts/ci/install-cvc5-linux.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | TEMP=$(mktemp) 6 | curl -L -o "${TEMP}" "https://github.com/cvc5/cvc5/releases/download/cvc5-1.0.3/cvc5-Linux" 7 | sudo mv "${TEMP}" /usr/local/bin/cvc5 8 | sudo chmod +x /usr/local/bin/cvc5 9 | -------------------------------------------------------------------------------- /scripts/ci/install-cvc5-macos-aarch64.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | TEMP=$(mktemp) 6 | curl -L -o "${TEMP}" "https://github.com/cvc5/cvc5/releases/download/cvc5-1.0.3/cvc5-macOS-arm64" 7 | sudo mv "${TEMP}" /usr/local/bin/cvc5 8 | sudo chmod +x /usr/local/bin/cvc5 -------------------------------------------------------------------------------- /scripts/ci/install-cvc5-macos.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | TEMP=$(mktemp) 6 | curl -L -o "${TEMP}" "https://github.com/cvc5/cvc5/releases/download/cvc5-1.0.3/cvc5-macOS" 7 | sudo mv "${TEMP}" /usr/local/bin/cvc5 8 | sudo chmod +x /usr/local/bin/cvc5 -------------------------------------------------------------------------------- /scripts/ci/install-cvc5.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | curl -L -O https://github.com/cvc5/cvc5/releases/download/cvc5-1.0.3/cvc5-Linux 6 | sudo mv cvc5-Linux /usr/bin/cvc5 7 | sudo chmod +x /usr/bin/cvc5 8 | -------------------------------------------------------------------------------- /scripts/ci/install-z3-from-source.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | version="4.10.2" 6 | 7 | sudo apt-get remove -y libz3-dev 8 | TEMP=$(mktemp) 9 | curl -L -o "${TEMP}" https://github.com/Z3Prover/z3/archive/refs/tags/z3-$version.zip 10 | unzip "${TEMP}" 11 | cd z3-z3-$version/ 12 | python scripts/mk_make.py --prefix=/usr 13 | cd build 14 | make 15 | sudo make install 16 | cd ../../ 17 | rm -r z3-z3-$version -------------------------------------------------------------------------------- /scripts/ci/install-z3-linux-amd64.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | version="4.10.2" 6 | 7 | sudo apt-get remove -y libz3-dev 8 | TEMP=$(mktemp) 9 | curl -L -o "${TEMP}" https://github.com/Z3Prover/z3/releases/download/z3-$version/z3-$version-x64-glibc-2.31.zip 10 | unzip "${TEMP}" 11 | sudo cp z3-$version-x64-glibc-2.31/bin/libz3.so /usr/lib/libz3.so 12 | sudo cp z3-$version-x64-glibc-2.31/include/* /usr/include/ 13 | sudo cp z3-$version-x64-glibc-2.31/bin/z3 /usr/bin/z3 14 | rm -r z3-$version-x64-glibc-2.31 15 | -------------------------------------------------------------------------------- /scripts/ci/install-z3-macos.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | version="4.10.2" 6 | 7 | brew extract --version=$version z3 homebrew/cask 8 | brew install z3@$version 9 | -------------------------------------------------------------------------------- /scripts/ci/test_data_extraction.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | f = open("bats-result.txt") 4 | 5 | # The output from bats has one line for the result of each test, 6 | # as well as multiple lines for any errors encountered. 7 | # In this script we just want the success/failure of each test and how long it took, 8 | # these lines have the following format: 9 | # (not) ok in