├── .gitattributes
├── .github
├── dependabot.yml
└── workflows
│ ├── deploy.yml
│ ├── echidna.yml
│ ├── lint_format.yml
│ ├── lint_links.yml
│ ├── manticore.yml
│ └── medusa.yml
├── .gitignore
├── .gitmodules
├── .prettierrc
├── CODEOWNERS
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SUMMARY.md
├── book.toml
├── development-guidelines
├── README.md
├── code_maturity.md
├── guidelines.md
├── incident_response.md
├── non-standard-tokens.md
├── review_checklist.md
├── token_integration.md
└── workflow.md
├── favicon.png
├── favicon.svg
├── learn_evm
├── README.md
├── arithmetic-checks.md
├── beps_forks.md
├── cips_forks.md
├── eips_forks.md
├── evm_opcodes.md
├── tips_upgrades.md
├── tracing.md
└── yellow-paper.md
├── not-so-smart-contracts
├── README.md
├── algorand
│ ├── README.md
│ ├── access_controls
│ │ └── README.md
│ ├── asset_id_check
│ │ └── README.md
│ ├── clear_state_transaction_check
│ │ └── README.md
│ ├── closing_account
│ │ └── README.md
│ ├── closing_asset
│ │ └── README.md
│ ├── denial_of_service
│ │ └── README.md
│ ├── group_size_check
│ │ └── README.md
│ ├── inner_transaction_fee
│ │ └── README.md
│ ├── rekeying
│ │ └── README.md
│ ├── time_based_replay_attack
│ │ └── README.md
│ └── unchecked_transaction_fee
│ │ └── README.md
├── cairo
│ ├── L1_to_L2_address_conversion
│ │ └── README.md
│ ├── README.md
│ ├── arithmetic_overflow
│ │ └── README.md
│ ├── l1_to_l2_message_failure
│ │ └── README.md
│ ├── overconstrained_l1_l2_interaction
│ │ └── README.md
│ ├── replay_protection
│ │ └── README.md
│ └── unchecked_from_address_in_l1_handler
│ │ └── README.md
├── cosmos
│ ├── README.md
│ ├── abci_fast
│ │ └── README.md
│ ├── abci_panic
│ │ └── README.md
│ ├── broken_bookkeeping
│ │ └── README.md
│ ├── incorrect_getsigners
│ │ └── README.md
│ ├── messages_priority
│ │ └── README.md
│ ├── missing_error_handler
│ │ └── README.md
│ ├── non_determinism
│ │ └── README.md
│ ├── rounding_errors
│ │ └── README.md
│ └── unregistered_msg_handler
│ │ └── README.md
├── solana
│ ├── README.md
│ ├── arbitrary_cpi
│ │ └── README.md
│ ├── improper_instruction_introspection
│ │ └── README.md
│ ├── improper_pda_validation
│ │ └── README.md
│ ├── ownership_check
│ │ └── README.md
│ ├── signer_check
│ │ └── README.md
│ └── sysvar_account_check
│ │ └── README.md
├── substrate
│ ├── README.md
│ ├── arithmetic_overflow
│ │ ├── README.md
│ │ └── pallet-overflow.rs
│ ├── dont_panic
│ │ ├── README.md
│ │ └── pallet-dont-panic.rs
│ ├── origins
│ │ ├── README.md
│ │ └── pallet-bad-origin.rs
│ ├── randomness
│ │ ├── README.md
│ │ └── pallet-bad-lottery.rs
│ ├── validate_unsigned
│ │ ├── README.md
│ │ └── pallet-bad-unsigned.rs
│ ├── verify_first
│ │ ├── README.md
│ │ └── pallet-verify-first.rs
│ └── weights_and_fees
│ │ ├── README.md
│ │ └── pallet-bad-weights.rs
└── ton
│ ├── README.md
│ ├── fake_jetton_contract
│ └── README.md
│ ├── forward_ton_without_gas_check
│ └── README.md
│ └── int_as_boolean
│ └── README.md
├── package-lock.json
├── package.json
├── program-analysis
├── README.md
├── echidna
│ ├── README.md
│ ├── advanced
│ │ ├── README.md
│ │ ├── collecting-a-corpus.md
│ │ ├── end-to-end-testing.md
│ │ ├── finding-transactions-with-high-gas-consumption.md
│ │ ├── hevm-cheats-to-test-permit.md
│ │ ├── interacting-with-offchain-data-via-ffi.md
│ │ ├── on-using-cheat-codes.md
│ │ ├── optimization_mode.md
│ │ ├── smart-contract-fuzzing-at-scale.md
│ │ ├── state-network-forking.md
│ │ ├── testing-bytecode.md
│ │ ├── using-all-contracts.md
│ │ └── working-with-libraries.md
│ ├── basic
│ │ ├── README.md
│ │ ├── assertion-checking.md
│ │ ├── common-testing-approaches.md
│ │ ├── filtering-functions.md
│ │ ├── property-creation.md
│ │ ├── testing-modes.md
│ │ └── working-with-eth.md
│ ├── configuration.md
│ ├── example
│ │ ├── ERC20Permit.sol
│ │ ├── MockERC20Permit.sol
│ │ ├── Popsicle.yaml
│ │ ├── PopsicleBroken.sol
│ │ ├── PopsicleFixed.sol
│ │ ├── TestDepositWithPermit.sol
│ │ ├── allContracts.sol
│ │ ├── allContracts.yaml
│ │ ├── assert.sol
│ │ ├── assert.yaml
│ │ ├── blacklistpushpop.yaml
│ │ ├── filter.yaml
│ │ ├── gas.sol
│ │ ├── gas.yaml
│ │ ├── magic.sol
│ │ ├── medusa.json
│ │ ├── multi.sol
│ │ ├── opt.sol
│ │ ├── pushpop.sol
│ │ ├── pushpop.yaml
│ │ ├── testdeposit.yaml
│ │ ├── testtoken.sol
│ │ └── token.sol
│ ├── exercises
│ │ ├── Exercise-1.md
│ │ ├── Exercise-2.md
│ │ ├── Exercise-3.md
│ │ ├── Exercise-4.md
│ │ ├── Exercise-5.md
│ │ ├── Exercise-6.md
│ │ ├── Exercise-7.md
│ │ ├── Exercise-8.md
│ │ ├── README.md
│ │ ├── exercise1
│ │ │ ├── medusa.json
│ │ │ ├── solution.sol
│ │ │ ├── template.sol
│ │ │ └── token.sol
│ │ ├── exercise2
│ │ │ ├── medusa.json
│ │ │ ├── solution.sol
│ │ │ ├── template.sol
│ │ │ └── token.sol
│ │ ├── exercise3
│ │ │ ├── medusa.json
│ │ │ ├── mintable.sol
│ │ │ ├── solution.sol
│ │ │ ├── template.sol
│ │ │ └── token.sol
│ │ ├── exercise4
│ │ │ ├── config.yaml
│ │ │ ├── medusa.json
│ │ │ ├── solution.sol
│ │ │ ├── template.sol
│ │ │ └── token.sol
│ │ └── exercise7
│ │ │ ├── config.yaml
│ │ │ ├── example.hardhat.config.ts
│ │ │ └── solution.sol
│ ├── frequently_asked_questions.md
│ ├── fuzzing_tips.md
│ └── introduction
│ │ ├── README.md
│ │ ├── fuzzing-introduction.md
│ │ ├── how-to-test-a-property.md
│ │ └── installation.md
└── manticore
│ ├── README.md
│ ├── adding-constraints.md
│ ├── examples
│ ├── example.sol
│ ├── example_constraint.py
│ ├── example_run.py
│ ├── example_throw.py
│ └── suicidal.sol
│ ├── exercises
│ ├── README.md
│ ├── example.md
│ ├── example
│ │ ├── my_token.py
│ │ └── my_token.sol
│ ├── exercise1.md
│ ├── exercise1
│ │ ├── solution.py
│ │ ├── template.py
│ │ └── token.sol
│ ├── exercise2.md
│ └── exercise2
│ │ ├── overflow.sol
│ │ ├── solution.py
│ │ └── template.py
│ ├── getting-throwing-paths.md
│ ├── running-under-manticore.md
│ ├── scripts
│ └── gh_action_test.sh
│ └── symbolic-execution-introduction.md
├── resources
├── README.md
├── contact.md
└── tob_blogposts.md
└── static
├── TOB_Black.svg
├── custom.css
└── script.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | - package-ecosystem: "gitsubmodule"
8 | directory: "/program-analysis/medusa"
9 | schedule:
10 | interval: "daily" # Check for updates daily
11 | commit-message:
12 | prefix: "Update medusa"
13 | assignees:
14 | - ggrieco-tob
15 | reviewers:
16 | - ggrieco-tob
17 | - package-ecosystem: "gitsubmodule"
18 | directory: "/program-analysis/slither"
19 | schedule:
20 | interval: "daily" # Check for updates daily
21 | commit-message:
22 | prefix: "Update slither"
23 | assignees:
24 | - ggrieco-tob
25 | reviewers:
26 | - ggrieco-tob
27 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy GH pages
2 | on:
3 | push:
4 | branches:
5 | - master
6 | ignore-paths:
7 | - "**.rs"
8 | - "**.py"
9 | - "**.sol"
10 | workflow_dispatch:
11 |
12 | jobs:
13 | build:
14 | permissions:
15 | contents: read
16 | runs-on: ubuntu-latest
17 |
18 | env:
19 | CARGO_TERM_COLOR: always
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 | with:
24 | fetch-depth: 0
25 | submodules: true
26 | - name: Install mdbook
27 | run: |
28 | cargo install --git https://github.com/montyly/mdBook.git mdbook || true
29 | - name: Build artifacts
30 | run: mdbook build
31 | - name: Upload artifact
32 | uses: actions/upload-pages-artifact@v3
33 | with:
34 | path: ./book
35 | deploy:
36 | permissions:
37 | pages: write
38 | id-token: write
39 | environment:
40 | name: github-pages
41 | url: ${{ steps.deployment.outputs.page_url }}
42 | runs-on: ubuntu-latest
43 | needs: build
44 | steps:
45 | - name: Deploy to GitHub Pages
46 | id: deployment
47 | uses: actions/deploy-pages@v4
48 |
--------------------------------------------------------------------------------
/.github/workflows/lint_format.yml:
--------------------------------------------------------------------------------
1 | name: Lint Check Format
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout repository
15 | uses: actions/checkout@v4
16 | with:
17 | submodules: true
18 |
19 | - name: Set up Node.js
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: 16
23 |
24 | - name: Install npm and dependencies
25 | run: |
26 | npm ci
27 |
28 | - name: Remove Medusa uncessary files
29 | run: |
30 | rm -rf program-analysis/medusa/chain
31 | rm -rf program-analysis/medusa/compilation
32 | rm -rf program-analysis/medusa/fuzzing
33 | rm program-analysis/medusa/docs/theme/highlight.js
34 |
35 | - name: Remove slither uncessary files
36 | run: |
37 | rm -rf program-analysis/slither
38 |
39 | - name: Run lint
40 | run: |
41 | npm run lint:format
42 |
--------------------------------------------------------------------------------
/.github/workflows/lint_links.yml:
--------------------------------------------------------------------------------
1 | name: Lint Check Markdown Links
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | paths:
8 | - ".github/workflows/lint_links.yml"
9 | - "**.md"
10 | pull_request:
11 | paths:
12 | - ".github/workflows/lint_links.yml"
13 | - "**.md"
14 | schedule:
15 | # run CI at 09:00 every Tuesday even if no PRs/merges occur
16 | - cron: "0 9 * * 2"
17 |
18 | jobs:
19 | markdown-link-check:
20 | runs-on: ubuntu-latest
21 | steps:
22 | - uses: actions/checkout@v4
23 | with:
24 | submodules: recursive
25 | - uses: gaurav-nelson/github-action-markdown-link-check@v1
26 | with:
27 | use-quiet-mode: "yes"
28 | check-modified-files-only: ${{ (github.event_name == 'pull_request' && 'yes') || 'no' }}
29 | submodules: true
30 |
--------------------------------------------------------------------------------
/.github/workflows/manticore.yml:
--------------------------------------------------------------------------------
1 | name: Run Manticore tests
2 |
3 | on:
4 | push:
5 | paths:
6 | - ".github/workflows/manticore.yml"
7 | - "program-analysis/manticore/**/*.py"
8 | branches:
9 | - master
10 | pull_request:
11 | paths:
12 | - ".github/workflows/manticore.yml"
13 | - "program-analysis/manticore/**/*.py"
14 | schedule:
15 | # run CI every day even if no PRs/merges occur
16 | - cron: "0 12 * * *"
17 |
18 | jobs:
19 | tests:
20 | runs-on: ubuntu-22.04
21 | strategy:
22 | fail-fast: false
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Set up Python 3.8
26 | uses: actions/setup-python@v5
27 | with:
28 | python-version: 3.8
29 | - name: Install dependencies
30 | run: |
31 | pip install solc-select
32 | solc-select install 0.5.11
33 | solc-select use 0.5.11
34 | - name: Run Tests
35 | run: |
36 | bash program-analysis/manticore/scripts/gh_action_test.sh
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "program-analysis/medusa"]
2 | path = program-analysis/medusa
3 | url = https://github.com/crytic/medusa.git
4 | [submodule "program-analysis/slither"]
5 | path = program-analysis/slither
6 | url = https://github.com/crytic/slither.git
7 | branch = master
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "printWidth": 80,
4 | "embeddedLanguageFormatting": "off",
5 | "plugins": ["prettier-plugin-solidity"],
6 | "overrides":
7 | [
8 | { "files": "*.sol", "options": { "tabWidth": 4, "printWidth": 120 } },
9 | { "files": ["*.json", ".prettierrc"], "options": { "tabWidth": 2, "printWidth": 120, "trailingComma": "none" } }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @montyly @ggrieco-tob @james-miller-93 @Jaime-Iglesias @anishnaik @bsamuels453
2 | /program-analysis/echidna/ @ggrieco-tob
3 | /learn_evm/ @bohendo
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Building-Secure-Contracts
2 |
3 | First, thank you for your interest in contributing to Building-Secure-Contracts! We appreciate and warmly welcome all contributions, which include bug reports, feature suggestions, tutorials/blog posts, and code improvements.
4 |
5 | If you're not sure where to begin, we recommend checking out our [`good first issue`](https://github.com/crytic/building-secure-contracts/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) and [`help wanted`](https://github.com/crytic/building-secure-contracts/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) issue labels.
6 |
7 | ## Bug Reports and Feature Suggestions
8 |
9 | Please submit bug reports and feature suggestions to our issue tracker. When reporting a bug, attaching the contract causing the issue is helpful for efficient debugging and resolution. If you discover a security vulnerability, do not open an issue; instead, email opensource@trailofbits.com.
10 |
11 | ## Questions
12 |
13 | Questions can be submitted to the issue tracker, but you may get a faster response if you ask in our [chat room](https://slack.empirehacking.nyc/) (in the #ethereum channel).
14 |
15 | ## Code Contributions
16 |
17 | Building-Secure-Contracts follows the pull request contribution model. Create an account on Github, fork this repo, and submit code contributions through pull requests. For additional documentation, refer [here](https://guides.github.com/activities/forking/).
18 |
19 | Some pull request guidelines:
20 |
21 | - Limit unnecessary changes (formatting, whitespace, etc.) to code unrelated to the patch. Save formatting or style corrections for a separate pull request, which doesn't include any semantic changes.
22 | - When possible, break down large changes into smaller, focused pull requests.
23 | - Complete the pull request description with an overview of your patch, including key modifications, and any further discussion points if relevant.
24 | - Use a concise title to describe your pull request's changes. "Fixes #123" is suitable for adding to the description, but not as a standalone title.
25 |
26 | ## Directory Structure
27 |
28 | Here's a basic overview of Building-Secure-Contracts' structure:
29 |
30 | ```text
31 | .
32 | ├── development-guidelines # High-level best practices for all smart contracts
33 | ├── learn_evm # EVM technical knowledge
34 | ├── not-so-smart-contracts # Examples of common smart contract issues, including descriptions, examples, and recommendations
35 | ├── program-analysis # How to utilize automated tools to secure contracts
36 | ├── resources # Various online resources
37 | └── ...
38 | ```
39 |
40 | ## Linting and Formatting
41 |
42 | To install the formatters and linters, run:
43 |
44 | ```bash
45 | npm install
46 | ```
47 |
48 | To use the formatter, run:
49 |
50 | ```bash
51 | npm run format
52 | ```
53 |
54 | To use the linters, run:
55 |
56 | ```bash
57 | npm run lint
58 | ```
59 |
60 | To use individual linters, run:
61 |
62 | - `npm run lint:format` to check the formatting
63 | - `npm run lint:links` to verify the validity of links in markdown files
64 |
65 | ## Creating the Book
66 |
67 | We utilize `mdbook` to generate [secure-contracts.com](https://secure-contracts.com/).
68 |
69 | To run it locally:
70 |
71 | ```
72 | cargo install --git https://github.com/montyly/mdBook.git mdbook
73 | mdbook build
74 | ```
75 |
76 | Note: We use https://github.com/montyly/mdBook.git, which contains https://github.com/rust-lang/mdBook/pull/1584.
77 |
--------------------------------------------------------------------------------
/book.toml:
--------------------------------------------------------------------------------
1 | [book]
2 | authors = ["Trail of Bits"]
3 | language = "en"
4 | multilingual = false
5 | src = "."
6 | title = "Building Secure Contracts"
7 | description = "This repository, brought to you by Trail of Bits, outlines our guidelines and best practices to write secure smart contracts."
8 | logo = "static/TOB_Black.svg"
9 |
10 | [output.html]
11 | git-repository-url = "https://github.com/crytic/building-secure-contracts"
12 | edit-url-template = "https://github.com/crytic/building-secure-contracts/edit/master/{path}"
13 | cname = "crytic.github.io/building-secure-contracts"
14 | no-section-label = true
15 | additional-css = ["static/custom.css"]
16 | additional-js = ["static/script.js"]
17 | default-theme = "light"
18 | mathjax-support = true
19 |
20 | [output.html.fold]
21 | enable = true
22 | level = 1
23 |
24 | [output.html.redirect]
25 | "medusa/index.html" = "../program-analysis/medusa/docs/src"
26 | "slither/index.html" = "../program-analysis/slither"
27 | "echidna/index.html" = "../program-analysis/echidna"
--------------------------------------------------------------------------------
/development-guidelines/README.md:
--------------------------------------------------------------------------------
1 | List of Best Practices for Smart Contract Development
2 |
3 | - [Code Maturity](./code_maturity.md): Criteria for developers and security engineers to use when evaluating a codebase’s maturity
4 | - [High-Level Best Practices](./guidelines.md): Essential high-level best practices for all smart contracts
5 | - [Token Integration Checklist](./token_integration.md): Important aspects to consider when interacting with various tokens
6 | - [Incident Response Recommendations](./incident_response.md): Guidelines on establishing an effective incident response plan
7 | - [Secure Development Workflow](./workflow.md): A recommended high-level process to adhere to while writing code
8 | - [Preparing for a Security Review](./review_checklist.md): A checklist of things to consider when preparing for a security review
9 |
--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/crytic/building-secure-contracts/b2c07d5d84c44e53c64369eb871dda50118b277b/favicon.png
--------------------------------------------------------------------------------
/favicon.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/learn_evm/README.md:
--------------------------------------------------------------------------------
1 | # Learn EVM
2 |
3 | List of EVM Technical Knowledge
4 |
5 | - [EVM Opcode Reference](evm_opcodes.md): Reference and notes for each of the EVM opcodes
6 | - [Transaction Tracing](tracing.md): Helper scripts and guidance for generating and navigating transaction traces
7 | - [Arithmetic Checks](./arithmetic-checks.md): Guide to performing arithmetic checks in the EVM
8 | - [Yellow Paper Guidance](yellow-paper.md): Symbol reference for more easily reading the Ethereum yellow paper
9 | - [Forks <> EIPs](eips_forks.md): Summarizes the EIPs included in each fork
10 | - [Forks <> CIPs](cips_forks.md): Summarizes the CIPs and EIPs included in each Celo fork _(EVM-compatible chain)_
11 | - [Upgrades <> TIPs](tips_upgrades.md): Summarizes the TIPs included in each TRON upgrade _(EVM-compatible chain)_
12 | - [Forks <> BEPs](beps_forks.md): Summarizes the BEPs included in each BSC fork _(EVM-compatible chain)_
13 |
--------------------------------------------------------------------------------
/learn_evm/beps_forks.md:
--------------------------------------------------------------------------------
1 | The following list includes each BEP associated with a Binance Smart Chain fork.
2 |
3 | | Release | BEP | Functionality |
4 | | ---------------------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------- |
5 | | [v1.0.6](https://github.com/bnb-chain/bsc/releases/tag/v1.0.6) | [84](https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP84.md) | Issue or bind BEP2 with existing BEP20 tokens |
6 | | [v1.1.5](https://github.com/bnb-chain/bsc/releases/tag/v1.1.5) | [93](https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP93.md) | Introduce new block synchronization protocol |
7 | | [v1.1.5](https://github.com/bnb-chain/bsc/releases/tag/v1.1.5) | [95](https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP95.md) | Establish real-time burning mechanism |
8 | | [v1.1.11](https://github.com/bnb-chain/bsc/releases/tag/v1.1.11) | [127](https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP127.md) | Implement "Temporary Maintenance" mode for validators |
9 | | [v1.1.11](https://github.com/bnb-chain/bsc/releases/tag/v1.1.11) | [131](https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP131.md) | Expand validator set with "Candidate" validators |
10 | | [v1.1.18](https://github.com/bnb-chain/bsc/releases/tag/v1.1.11) | [153](https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP153.md) | Develop native staking protocol |
11 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/README.md:
--------------------------------------------------------------------------------
1 | # (Not So) Smart Contracts
2 |
3 | This repository contains examples of common smart contract vulnerabilities, including code from real smart contracts. Use Not So Smart Contracts to learn about vulnerabilities, as a reference when performing security reviews, and as a benchmark for security and analysis tools:
4 |
5 | - [Algorand](./algorand/README.md)
6 | - [Cairo](./cairo/README.md)
7 | - [Cosmos](./cosmos/README.md)
8 | - [Solana](./solana/README.md)
9 | - [Substrate](./substrate/README.md)
10 | - [TON](./ton/README.md)
11 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/access_controls/README.md:
--------------------------------------------------------------------------------
1 | # Access Controls
2 |
3 | Lack of appropriate checks for application calls of type UpdateApplication and DeleteApplication allows attackers to update application’s code or delete an application entirely.
4 |
5 | ## Description
6 |
7 | When an application call is successful, additional operations are executed based on the OnComplete field. If the OnComplete field is set to UpdateApplication the approval and clear programs of the application are replaced with the programs specified in the transaction. Similarly, if the OnComplete field is set to DeleteApplication, application parameters are deleted.
8 | This allows attackers to update or delete the application if proper access controls are not enforced in the application.
9 |
10 | ## Exploit Scenarios
11 |
12 | A stateful contract serves as a liquidity pool for a pair of tokens. Users can deposit the tokens to get the liquidity tokens and can get back their funds with rewards through a burn operation. The contract does not enforce restrictions for UpdateApplication type application calls. Attacker updates the approval program with a malicious program that transfers all assets in the pool to the attacker's address.
13 |
14 | ## Recommendations
15 |
16 | - Set proper access controls and apply various checks before approving applications calls of type UpdateApplication and DeleteApplication.
17 |
18 | - Use [Tealer](https://github.com/crytic/tealer) to detect this issue.
19 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/asset_id_check/README.md:
--------------------------------------------------------------------------------
1 | # Asset Id Check
2 |
3 | Lack of verification of asset id in the contract allows attackers to transfer a different asset in place of the expected asset and mislead the application.
4 |
5 | ## Description
6 |
7 | Contracts accepting and doing operations based on the assets transferred to the contract must verify that the transferred asset is the expected asset by checking the asset Id. Absence of check for expected asset Id could allow attackers to manipulate contract’s logic by transferring a fake, less or more valuable asset instead of the correct asset.
8 |
9 | ## Exploit Scenarios
10 |
11 | - A liquidity pool contract mints liquidity tokens on deposit of two tokens. Contract does not check that the asset Ids in the two asset transfer transactions are correct. Attacker deposits the same less valuable asset in the two transactions and withdraws both tokens by burning the pool tokens.
12 | - User creates a delegate signature that allows recurring transfers of a certain asset. Attacker creates a valid asset transfer transaction of more valuable assets.
13 |
14 | ## Examples
15 |
16 | Note: This code contains several other vulnerabilities, see [Rekeying](../rekeying), [Unchecked Transaction Fees](../unchecked_transaction_fee), [Closing Asset](../closing_asset), [Time-based Replay Attack](../time_based_replay_attack).
17 |
18 | ```py
19 | def withdraw_asset(
20 | duration,
21 | period,
22 | amount,
23 | receiver,
24 | timeout,
25 | ):
26 | return And(
27 | Txn.type_enum() == TxnType.AssetTransfer,
28 | Txn.first_valid() % period == Int(0),
29 | Txn.last_valid() == Txn.first_valid() + duration,
30 | Txn.asset_receiver() == receiver,
31 | Txn.asset_amount() == amount,
32 | Txn.first_valid() < timeout,
33 | )
34 | ```
35 |
36 | ## Recommendations
37 |
38 | Verify the asset id to be expected asset for all asset related operations in the contract.
39 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/clear_state_transaction_check/README.md:
--------------------------------------------------------------------------------
1 | # Clear State Transaction Check
2 |
3 | The lack of checks on the OnComplete field of the application calls might allow an attacker to execute the clear state program instead of the approval program, breaking core validations.
4 |
5 | ## Description
6 |
7 | Algorand applications make use of group transactions to realize operations that may not be possible using a single transaction model. Some operations require that other transactions in the group call certain methods and applications. These requirements are asserted by validating that the transactions are ApplicationCall transactions. However, the OnComplete field of these transactions is not always validated, allowing an attacker to submit ClearState ApplicationCall transactions. The ClearState transaction invokes the clear state program instead of the intended approval program of the application.
8 |
9 | ## Exploit Scenario
10 |
11 | A protocol offers flash loans from a liquidity pool. The flash loan operation is implemented using two methods: `take_flash_loan` and `pay_flash_loan`. `take_flash_loan` method transfers the assets to the user and `pay_flash_loan` verifies that the user has returned the borrowed assets. `take_flash_loan` verifies that a later transaction in the group calls the `pay_flash_loan` method. However, It does not validate the OnComplete field.
12 |
13 | ```py
14 | @router.method(no_op=CallConfig.CALL)
15 | def take_flash_loan(offset: abi.Uint64, amount: abi.Uint64) -> Expr:
16 | return Seq([
17 | # Ensure the pay_flash_loan method is called
18 | Assert(And(
19 | Gtxn[Txn.group_index() + offset.get()].type_enum == TxnType.ApplicationCall,
20 | Gtxn[Txn.group_index() + offset.get()].application_id() == Global.current_application_id(),
21 | Gtxn[Txn.group_index() + offset.get()].application_args[0] == MethodSignature("pay_flash_loan(uint64)")
22 | )),
23 | # Perform other validations, transfer assets to the user, update the global state
24 | # [...]
25 | ])
26 |
27 | @router.method(no_op=CallConfig.CALL)
28 | def pay_flash_loan(offset: abi.Uint64) -> Expr:
29 | return Seq([
30 | # Validate the "take_flash_loan" transaction at `Txn.group_index() - offset.get()`
31 | # Ensure the user has returned the funds to the pool along with the fee. Fail the transaction otherwise
32 | # [...]
33 | ])
34 | ```
35 |
36 | An attacker constructs a valid group transaction for flash loan but sets the OnComplete field of `pay_flash_loan` call to ClearState. The clear state program is executed for complete_flash_loan call, which does not validate that the attacker has returned the funds. The attacker steals all the assets in the pool.
37 |
38 | ## Recommendations
39 |
40 | Validate the OnComplete field of the ApplicationCall transactions.
41 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/closing_account/README.md:
--------------------------------------------------------------------------------
1 | # Closing Account
2 |
3 | Lack of check for CloseRemainderTo transaction field in smart signatures allows attackers to transfer entire funds of the contract account or the delegator’s account to their account.
4 |
5 | ## Description
6 |
7 | Algorand accounts must satisfy minimum balance requirement and protocol rejects transactions whose execution results in account balance lower than the required minimum. In order to transfer the entire balance and close the account, users should use the CloseRemainderTo field of a payment transaction. Setting the CloseRemainderTo field transfers the entire account balance remaining after transaction execution to the specified address.
8 |
9 | Any user with access to the smart signature may construct and submit the transactions using the smart signature. The smart signatures approving payment transactions have to ensure that the CloseRemainderTo field is set to the ZeroAddress or any other specific address to avoid unintended transfer of funds.
10 |
11 | ## Exploit Scenarios
12 |
13 | A user creates a delegate signature for recurring payments. Attacker creates a valid transaction and sets the CloseRemainderTo field to their address.
14 |
15 | ## Examples
16 |
17 | Note: This code contains several other vulnerabilities, see [Rekeying](../rekeying), [Unchecked Transaction Fees](../unchecked_transaction_fee), [Time-based Replay Attack](../time_based_replay_attack).
18 |
19 | ```py
20 | def withdraw(
21 | duration,
22 | period,
23 | amount,
24 | receiver,
25 | timeout,
26 | ):
27 | return And(
28 | Txn.type_enum() == TxnType.Payment,
29 | Txn.first_valid() % period == Int(0),
30 | Txn.last_valid() == Txn.first_valid() + duration,
31 | Txn.receiver() == receiver,
32 | Txn.amount() == amount,
33 | Txn.first_valid() < timeout,
34 | )
35 | ```
36 |
37 | ## Recommendations
38 |
39 | Verify that the CloseRemainderTo field is set to the ZeroAddress or to any intended address before approving the transaction in the Teal contract.
40 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/closing_asset/README.md:
--------------------------------------------------------------------------------
1 | # Closing Asset
2 |
3 | Lack of check for AssetCloseTo transaction field in smart signatures allows attackers to transfer the entire asset balance of the contract account or the delegator’s account to their account.
4 |
5 | ## Description
6 |
7 | Algorand supports Fungible and Non Fungible Tokens using Algorand Standard Assets(ASA). An Algorand account must first opti-in to the asset before that account can receive any tokens. Opting to an asset increases the minimum balance requirement of the account. Users can opt-out of the asset and decrease the minimum balance requirement using the AssetCloseTo field of Asset Transfer transaction. Setting the AssetCloseTo field transfers the account’s entire token balance remaining after transaction execution to the specified address.
8 |
9 | Any user with access to the smart signature may construct and submit the transactions using the smart signature. The smart signatures approving asset transfer transactions have to ensure that the AssetCloseTo field is set to the ZeroAddress or any other specific address to avoid unintended transfer of tokens.
10 |
11 | ## Exploit Scenarios
12 |
13 | User creates a delegate signature that allows recurring transfers of a certain asset. Attacker creates a valid asset transfer transaction with AssetCloseTo field set to their address.
14 |
15 | ## Examples
16 |
17 | Note: This code contains several other vulnerabilities, see [Rekeying](../rekeying), [Unchecked Transaction Fees](../unchecked_transaction_fee), [Closing Asset](../closing_asset), [Time-based Replay Attack](../time_based_replay_attack), [Asset Id Check](../asset_id_check).
18 |
19 | ```py
20 | def withdraw_asset(
21 | duration,
22 | period,
23 | amount,
24 | receiver,
25 | timeout,
26 | ):
27 | return And(
28 | Txn.type_enum() == TxnType.AssetTransfer,
29 | Txn.first_valid() % period == Int(0),
30 | Txn.last_valid() == Txn.first_valid() + duration,
31 | Txn.asset_receiver() == receiver,
32 | Txn.asset_amount() == amount,
33 | Txn.first_valid() < timeout,
34 | )
35 | ```
36 |
37 | ## Recommendations
38 |
39 | Verify that the AssetCloseTo field is set to the ZeroAddress or to the intended address before approving the transaction in the Teal contract.
40 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/denial_of_service/README.md:
--------------------------------------------------------------------------------
1 | # Denial of Service
2 |
3 | When a contract does not verify whether an account has opted in to an asset and attempts to transfer that asset, an attacker can DoS other users if the contract's operation is to transfer asset to multiple accounts.
4 |
5 | ## Description
6 |
7 | A user must explicitly opt-in to receive any particular Algorand Standard Asset(ASAs). A user may also opt out of an ASA. A transaction will fail if it attempts to transfer tokens to an account that didn’t opt in to that asset. This could be leveraged by attackers to DOS a contract if the contract’s operation depends on successful transfer of an asset to the attacker owned address.
8 |
9 | ## Exploit Scenarios
10 |
11 | Contract attempts to transfer assets to multiple users. One user is not opted in to the asset. The transfer operation fails for all users.
12 |
13 | ## Examples
14 |
15 | Note: This code contains several other vulnerabilities, see [Rekeying](../rekeying), [Unchecked Transaction Fees](../unchecked_transaction_fee), [Closing Asset](../closing_asset), [Group Size Check](../group_size_check), [Time-based Replay Attack](../time_based_replay_attack), [Asset Id Check](../asset_id_check)
16 |
17 | ```py
18 | def split_and_withdraw_asset(
19 | amount_1,
20 | receiver_1,
21 | amount_2,
22 | receiver_2,
23 | lock_expire_round,
24 | ):
25 | return And(
26 | Gtxn[0].type_enum() == TxnType.AssetTransfer,
27 | Gtxn[0].asset_receiver() == receiver_1,
28 | Gtxn[0].asset_amount() == amount_1,
29 |
30 | Gtxn[1].type_enum() == TxnType.AssetTransfer,
31 | Gtxn[1].receiver() == receiver_2,
32 | Gtxn[1].amount() == amount_2,
33 |
34 | Gtxn[0].first_valid == lock_expire_round,
35 | )
36 | ```
37 |
38 | ## Recommendations
39 |
40 | Use pull over push pattern for transferring assets to users.
41 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/group_size_check/README.md:
--------------------------------------------------------------------------------
1 | # Group Size Check
2 |
3 | Lack of group size check in contracts that are supposed to be called in an atomic group transaction might allow attackers to misuse the application.
4 |
5 | ## Description
6 |
7 | Algorand supports atomic transfers, an atomic transfer is a group of transactions that are submitted and processed as a single transaction. A group can contain upto 16 transactions and the group transaction fails if any of the included transactions fails. Algorand applications make use of group transactions to realize operations that may not be possible using a single transaction model. In such cases, it is necessary to check that the group transaction in itself is valid along with the individual transactions. One of the checks whose absence could be misused is group size check.
8 |
9 | ## Exploit Scenarios
10 |
11 | Application only checks that transactions at particular indices are meeting the criteria and performs the operations based on that. Attackers can create the transactions at the checked indices correctly and include equivalent application call transactions at all the remaining indices. Each application call executes successfully as every execution checks the same set of transactions. This results in performing operations multiple times, once for each application call. This could be damaging if those operations include funds or assets transfers among others.
12 |
13 | ## Examples
14 |
15 | Note: This code contains several other vulnerabilities, see [Rekeying](../rekeying), [Unchecked Transaction Fees](../unchecked_transaction_fee), [Closing Account](../closing_account), [Time-based Replay Attack](../time_based_replay_attack).
16 |
17 | ```py
18 | def split_and_withdraw(
19 | amount_1,
20 | receiver_1,
21 | amount_2,
22 | receiver_2,
23 | lock_expire_round,
24 | ):
25 | return And(
26 | Gtxn[0].type_enum() == TxnType.Payment,
27 | Gtxn[0].receiver() == receiver_1,
28 | Gtxn[0].amount() == amount_1,
29 |
30 | Gtxn[1].type_enum() == TxnType.Payment,
31 | Gtxn[1].receiver() == receiver_2,
32 | Gtxn[1].amount() == amount_2,
33 |
34 | Gtxn[0].first_valid == lock_expire_round,
35 | )
36 | ```
37 |
38 | ## Recommendations
39 |
40 | - Verify that the group size of an atomic transfer is the intended size in the contracts.
41 |
42 | - Use [Tealer](https://github.com/crytic/tealer) to detect this issue.
43 |
44 | - Favor using ABI for smart contracts and relative indexes to verify the group transaction.
45 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/inner_transaction_fee/README.md:
--------------------------------------------------------------------------------
1 | # Inner Transaction Fee
2 |
3 | Inner transaction fees are by default set to an estimated amount which are deducted from the application account if it is the Sender. An attacker can perform operations executing inner transactions and drain system funds, making it under-collateralized.
4 |
5 | ## Description
6 |
7 | Inner transactions are initialized with Sender set to the application account and Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions. The inner transaction fee depends on the transaction fee paid by the user. As a result, the user controls, to some extent, the fee paid by the application.
8 |
9 | If the application does not explicitly set the Fee to zero, an attacker can burn the application’s balance in the form of fees. This also becomes an issue if the application implements internal bookkeeping to track the application balance and does not account for fees.
10 |
11 | ## Exploit Scenarios
12 |
13 | ```py
14 | @router.method(no_op=CallConfig.CALL)
15 | def mint(pay: abi.PaymentTransaction) -> Expr:
16 | return Seq([
17 | # perform validations and other operations
18 | # [...]
19 | # mint wrapped-asset id
20 | InnerTxnBuilder.Begin(),
21 | InnerTxnBuilder.SetFields(
22 | {
23 | TxnField.type_enum: TxnType.AssetTransfer,
24 | TxnField.asset_receiver: Txn.sender(),
25 | TxnField.xfer_asset: wrapped_algo_asset_id,
26 | TxnField.asset_amount: pay.get().amount(),
27 | }
28 | ),
29 | InnerTxnBuilder.Submit(),
30 | # [...]
31 | ])
32 | ```
33 |
34 | The application does not explicitly set the inner transaction fee to zero. When user mints wrapped-algo, some of the ALGO is burned in the form of fees. The amount of wrapped-algo in circulation will be greater than the application ALGO balance. The system will be under-collateralized.
35 |
36 | ## Recommendations
37 |
38 | Explicitly set the inner transaction fees to zero and use the fee pooling feature of the Algorand.
39 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/time_based_replay_attack/README.md:
--------------------------------------------------------------------------------
1 | # Time-based Replay Attack
2 |
3 | Lack of check for lease field in smart signatures that intend to approve a single transaction in the particular period allows attackers to submit multiple valid transactions in that period.
4 |
5 | ## Description
6 |
7 | Algorand stops transaction replay attacks using a validity period. A validity period of a transaction is the sequence of blocks between FirstValid block and LastValid block. The transaction is considered valid only in that period and a transaction with the same hash can be processed only once in that period. Algorand also limits the period to a maximum of 1000 blocks. This allows the transaction creator to select the FirstValid, LastValid fields appropriately and feel assured that the transaction is processed only once in that period.
8 |
9 | However, The same does not apply for transactions authorized by smart signatures. Even if the contract developer verifies the FirstValid and LastValid transaction fields to fixed values, an attacker can submit multiple transactions that are valid as per the contract. This is because any user can create and submit transactions authorized by a smart signature. The attacker can create transactions which have equal values for most transaction fields, for fields verified in the contract and slightly different values for the rest. Each one of these transactions will have a different hash and will be accepted by the protocol.
10 |
11 | ## Exploit Scenarios
12 |
13 | A user creates a delegate signature for recurring payments. Contract verifies the FirstValid and LastValid to only allow a single transaction each time. Attacker creates and submits multiple valid transactions with different hashes.
14 |
15 | ## Examples
16 |
17 | Note: This code contains several other vulnerabilities, see [Rekeying](../rekeying), [Unchecked Transaction Fees](../unchecked_transaction_fee), [Closing Account](../closing_account).
18 |
19 | ```py
20 | def withdraw(
21 | duration,
22 | period,
23 | amount,
24 | receiver,
25 | timeout,
26 | ):
27 | return And(
28 | Txn.type_enum() == TxnType.Payment,
29 | Txn.first_valid() % period == Int(0),
30 | Txn.last_valid() == Txn.first_valid() + duration,
31 | Txn.receiver() == receiver,
32 | Txn.amount() == amount,
33 | Txn.first_valid() < timeout,
34 | )
35 | ```
36 |
37 | ## Recommendations
38 |
39 | Verify that the Lease field of the transaction is set to a specific value. Lease enforces mutual exclusion, once a transaction with non-zero lease is confirmed by the protocol, no other transactions with same lease and sender will be accepted till the LastValid block
40 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/algorand/unchecked_transaction_fee/README.md:
--------------------------------------------------------------------------------
1 | # Unchecked Transaction Fee
2 |
3 | Lack of transaction fee check in smart signatures allows malicious users to drain the contract account or the delegator’s account by specifying excessive fees.
4 |
5 | ## Description
6 |
7 | Any user can submit transactions using the smart signatures and decide on the transaction fields. It is the responsibility of the creator to enforce restrictions on all the transaction fields to prevent malicious users from misusing the smart signature.
8 |
9 | One of these transaction fields is Fee. Fee field specifies the number of micro-algos paid for processing the transaction. Protocol only verifies that the transaction pays a fee greater than protocol decided minimum fee. If a smart signature doesn’t bound the transaction fee, a user could set an excessive fee and drain the sender funds. Sender will be the signer of the Teal program in case of delegate signature and the contract account otherwise.
10 |
11 | ## Exploit Scenarios
12 |
13 | A user creates a delegate signature for recurring payments. Attacker creates a valid transaction and drains the user funds by specifying excessive fee.
14 |
15 | ## Examples
16 |
17 | Note: This code contains several other vulnerabilities, see [Rekeying](../rekeying), [Closing Account](../closing_account), [Time-based Replay Attack](../time_based_replay_attack).
18 |
19 | ```py
20 | def withdraw(
21 | duration,
22 | period,
23 | amount,
24 | receiver,
25 | timeout,
26 | ):
27 | return And(
28 | Txn.type_enum() == TxnType.Payment,
29 | Txn.first_valid() % period == Int(0),
30 | Txn.last_valid() == Txn.first_valid() + duration,
31 | Txn.receiver() == receiver,
32 | Txn.amount() == amount,
33 | Txn.first_valid() < timeout,
34 | )
35 | ```
36 |
37 | ## Recommendations
38 |
39 | - Force the transaction fee to be `0` and use fee pooling. If the users should be able to call the smart signature outside of a group, force the transaction fee to be minimum transaction fee: `global MinTxnFee`.
40 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cairo/L1_to_L2_address_conversion/README.md:
--------------------------------------------------------------------------------
1 | # L1 to L2 Address Conversion
2 |
3 | In Starknet, addresses are of the `felt` type, while on L1 addresses are of the `uint160` type. To pass address types during cross-layer messaging, the address variable is typically given as a `uint256`. However, this may create an issue where an address on L1 maps to the zero address (or an unexpected address) on L2. This is because the primitive type in Cairo is the `felt`, which lies within the range `0 < x < P`, where P is the prime order of the curve. Usually, we have `P = 2^251 + 17 * 2^192 + 1`.
4 |
5 | # Example
6 |
7 | Consider the following code to initiate L2 deposits from L1. The first example has no checks on the `to` parameter, and depending on the user's address, it could transfer tokens to an unexpected address on L2. The second example, however, adds verification to ensure this does not happen. Note that the code is a simplified version of how messages are sent on L1 and processed on L2. For a more comprehensive overview, see here: [https://www.cairo-lang.org/docs/hello_starknet/l1l2.html](https://docs.cairo-lang.org/hello_starknet/l1l2.html).
8 |
9 | ```solidity
10 | contract L1ToL2Bridge {
11 | uint256 public constant STARKNET_FIELD_PRIME; // the prime order P of the elliptic curve used
12 | IERC20 public constant token; // some token to deposit on L2
13 |
14 | event Deposited(uint256 to, uint256 amount);
15 |
16 | function badDepositToL2(uint256 to, uint256 amount) public returns (bool) {
17 | token.transferFrom(msg.sender, address(this), amount);
18 | emit Deposited(to, amount); // this message gets processed on L2
19 | return true;
20 | }
21 |
22 | function betterDepositToL2(uint256 to, uint256 amount) public returns (bool) {
23 | require(to != 0 && to < STARKNET_FIELD_PRIME, "invalid address"); // verifies 0 < to < P
24 | token.transferFrom(msg.sender, address(this), amount);
25 | emit Deposited(to, amount); // this message gets processed on L2
26 | return true;
27 | }
28 | }
29 | ```
30 |
31 | # Mitigations
32 |
33 | When sending a message from L1 to L2, ensure verification of parameters, particularly user-supplied ones. Keep in mind that Cairo's default `felt` type range is smaller than the `uint256` type used by Solidity.
34 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cairo/README.md:
--------------------------------------------------------------------------------
1 | # (Not So) Smart Contracts
2 |
3 | This repository contains examples of common Cairo smart contract vulnerabilities, featuring code from real smart contracts. Utilize the Not So Smart Contracts to learn about Cairo vulnerabilities, refer to them during security reviews, and use them as a benchmark for security analysis tools.
4 |
5 | ## Features
6 |
7 | Each _Not So Smart Contract_ consists of a standard set of information:
8 |
9 | - Vulnerability type description
10 | - Attack scenarios to exploit the vulnerability
11 | - Recommendations to eliminate or mitigate the vulnerability
12 | - Real-world contracts exhibiting the flaw
13 | - References to third-party resources providing more information
14 |
15 | ## Vulnerabilities
16 |
17 | | Not So Smart Contract | Description |
18 | | ---------------------------------------------------------------------------- | ------------------------------------------------------------ |
19 | | [Arithmetic overflow](arithmetic_overflow) | Insecure arithmetic in Cairo for the felt252 type |
20 | | [L1 to L2 Address Conversion](L1_to_L2_address_conversion) | Essential L2 address checks for L1 to L2 messaging |
21 | | [L1 to L2 message failure](l1_to_l2_message_failure) | Messages sent from L1 may not be processed by the sequencer |
22 | | [Overconstrained L1 <-> L2 interaction](overconstrained_l1_l2_interaction) | Asymmetrical checks on the L1 or L2 side can cause a DOS |
23 | | [Signature replays](replay_protection) | Necessary robust reuse protection due to account abstraction |
24 | | [Unchecked from address in L1 Handler](unchecked_from_address_in_l1_handler) | Access control issue when sending messages from L1 to L2 |
25 |
26 | ## Credits
27 |
28 | These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/).
29 |
30 | If you have any questions, issues, or wish to learn more, join the #ethereum channel on the [Empire Hacking Slack](https://slack.empirehacking.nyc/) or [contact us](https://www.trailofbits.com/contact/) directly.
31 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cairo/arithmetic_overflow/README.md:
--------------------------------------------------------------------------------
1 | # Arithmetic Overflow with Felt Type
2 |
3 | The default primitive type, the field element (felt), behaves much like an integer in other languages, but there are a few important differences to keep in mind. A felt can be interpreted as an unsigned integer in the range [0, P], where P, a 252 bit prime, represents the order of the field used by Cairo. Arithmetic operations using felts are unchecked for overflow or underflow, which can lead to unexpected results if not properly accounted for. Do note that Cairo's builtin primitives for unsigned integers are overflow/underflow safe and will revert.
4 |
5 | ## Example
6 |
7 | The following simplified code highlights the risk of felt underflow. The `check_balance` function is used to validate if a user has a large enough balance. However, the check is faulty because passing an amount such that `amt > balance` will underflow and the check will be true.
8 |
9 | ```Cairo
10 |
11 | struct Storage {
12 | balances: LegacyMap
13 | }
14 |
15 | fn check_balance(self: @ContractState, amt: felt252) {
16 | let caller = get_caller_address();
17 | let balance = self.balances.read(caller);
18 | assert(balance - amt >= 0);
19 | }
20 |
21 | ```
22 |
23 | ## Mitigations
24 |
25 | - Always add checks for overflow when working with felts directly.
26 | - Use the default signed integer types unless a felt is explicitly required.
27 | - Consider using Caracal, as it comes with a detector for checking potential overflows when doing felt252 arithmetic operaitons.
28 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cairo/l1_to_l2_message_failure/README.md:
--------------------------------------------------------------------------------
1 | # L1 to L2 Message Failure
2 |
3 | In Starknet, Ethereum contracts can send messages from L1 to L2 using a bridge. However, it is not guaranteed that the message will be processed by the sequencer. For instance, a message can fail to be processed if there is a sudden spike in the gas price and the value provided is too low. To address this issue, Starknet developers have provided an API to cancel ongoing messages.
4 |
5 | # Example
6 |
7 | Consider the following code to initiate L2 deposits from L1, taking the tokens from the user:
8 |
9 | ```solidity
10 | contract L1ToL2Bridge {
11 | IERC20 public token; // some token to deposit on L2
12 |
13 | function depositToL2(address to, uint256 amount) public returns (bool) {
14 | require(token.transferFrom(msg.sender, address(this), amount));
15 | // ...
16 | StarknetCore.sendMessageToL2(data);
17 | return true;
18 | }
19 | }
20 | ```
21 |
22 | If an L1 message is never processed by the sequencer, users will never receive their tokens in either L1 or L2, and they need a way to cancel the message.
23 |
24 | A recent AAVE audit highlighted this issue and required the addition of code to cancel messages.
25 |
26 | # Mitigations
27 |
28 | When sending a message from L1 to L2, it is essential to consider the possibility that a message may never be processed by the sequencer. This can block either the contract from reaching a certain state or users from retrieving their funds. If needed, allow the use of `startL1ToL2MessageCancellation` and `cancelL1ToL2Message` to cancel ongoing messages.
29 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cairo/overconstrained_l1_l2_interaction/README.md:
--------------------------------------------------------------------------------
1 | # Overconstrained L1 <-> L2 interaction
2 |
3 | When interacting with contracts that are designed to interact with both L1 and L2, care must be taken that the checks and validations on both sides are symmetrical. If one side has more validations than the other, this could create a situation where a user performs an action on one side, but is unable to perform the corresponding action on the other side, leading to a loss of funds or a denial of service.
4 |
5 | ## Example
6 |
7 | The following Starknet bridge contract allows for permissionless deposit to any address on L1 via the `deposit_to_L1` function. In particular, someone can deposit tokens to the `BAD_ADDRESS`. However, in that case the tokens will be lost forever, because the tokens are burned on L2 and the L1 contract's `depositFromL2` function prevents `BAD_ADDRESS` from being the recipient.
8 |
9 | ```Cairo
10 | #[storage]
11 | struct Storage {
12 | l1_bridge: EthAddress,
13 | balances: LegacyMap
14 | }
15 |
16 | #[derive(Serde)]
17 | struct Deposit {
18 | recipient: EthAddress,
19 | token: EthAddress,
20 | amount: u256
21 | }
22 |
23 | fn deposit_to_l1(ref self: ContractState, deposit: Deposit) {
24 | let caller = get_caller_address();
25 | //burn the tokens on the L2 side
26 | self.balances.write(caller, self.balances.read(caller) - deposit.amount);
27 | let payload = ArrayTrait::new();
28 | starknet::send_message_to_l1_syscall(self.l1_bridge.read(), deposit.serialize(ref payload)).unwrap();
29 | }
30 | ```
31 |
32 | ```solidity
33 |
34 | address public immutable MESSENGER_CONTRACT;
35 | address public immutable L2_TOKEN_BRIDGE;
36 | address public constant BAD_ADDRESS = address(0xdead);
37 |
38 | constructor(address _messenger, address _bridge) {
39 | MESSENGER_CONTRACT = _messenger;
40 | L2_TOKEN_BRIDGE = _bridge;
41 | }
42 |
43 | function depositFromL2(address recipient, address token, uint256 amount) external {
44 | require(recipient != BAD_ADDRESS, "blacklisted");
45 | uint256[] memory payload = _buildPayload(recipient,token,amount);
46 | MESSENGER_CONTRACT.consumeMessageFromL2(L2_TOKEN_BRIDGE,payload);
47 | //deposit logic
48 | [...]
49 | }
50 |
51 | function _buildPayload(address recipient, address token, uint256 amount) internal returns (uint256[] memory) {
52 | //payload building logic for Starknet message
53 | [...]
54 | }
55 | ```
56 |
57 | ## Mitigations
58 |
59 | - Make sure to validate that the checks on both the L1 and L2 side are similar enough to prevent unexpected behavior. Ensure that any unsymmetric validations on either side cannot lead to a tokens being trapped or any other denial of service.
60 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cairo/replay_protection/README.md:
--------------------------------------------------------------------------------
1 | # Signature Replay Protection
2 |
3 | The StarkNet account abstraction model enables offloading many authentication details to contracts, providing a higher degree of flexibility. However, this also means that signature schemes must be designed with great care. Signatures should be resistant to replay attacks and signature malleability. They must include a nonce and preferably have a domain separator to bind the signature to a specific contract and chain. For instance, this prevents testnet signatures from being replayed against mainnet contracts.
4 |
5 | ## Example
6 |
7 | Consider the following function that validates a signature for EIP712-style permit functionality. Notice that the contract lacks a way of keeping track of nonces. As a result, the same signature can be replayed over and over again. In addition, there is no method for identifying the specific chain a signature is designed for. Consequently, this signature schema would allow signatures to be replayed both on the same chain and across different chains, such as between a testnet and mainnet.
8 |
9 | ```cairo
10 | #[storage]
11 | struct Storage {
12 | authorized_pubkey: felt252
13 | }
14 |
15 | #[derive(Hash)]
16 | struct Signature {
17 | sig_r: felt252,
18 | sig_s: felt252,
19 | amount: u256,
20 | recipient: ContractAddress
21 | }
22 |
23 | fn bad_is_valid_signature(self: @ContractState, sig: Signature) {
24 | let hasher = PoseidonTrait::new();
25 | let hash = hasher.update_with(sig).finalize();
26 | ecdsa::check_ecdsa_signature(hash,authorized_pubkey,sig.r,sig.s);
27 | }
28 | ```
29 |
30 | ## Mitigations
31 |
32 | - Consider using the [OpenZeppelin Contracts for Cairo Account contract](https://github.com/OpenZeppelin/cairo-contracts/blob/main/docs/modules/ROOT/pages/accounts.adoc) or another existing account contract implementation.
33 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cairo/unchecked_from_address_in_l1_handler/README.md:
--------------------------------------------------------------------------------
1 | # Unchecked from address in L1 Handler function
2 |
3 | A function with the `l1_handler` annotation is intended to be called from L1. The first parameter of the `l1_handler` function is always `from`, which represents the `msg.sender` of the L1 transaction that attempted to invoke the function on Starknet. If the `l1_handler` function is designed to be invoked from a specific address on mainnet, not checking the from address may allow anyone to call the function, opening up access control vulnerabilities.
4 |
5 | ## Example
6 |
7 | The following Starknet bridge contract's owner, specified in the `uint256[] calldata payload` array, is designed to be called only from the `setOwnerOnL2()` function. Even though the owner is checked on the solidity side, the lack of validation of the `from_address` parameter allows anyone to call the function from an arbitrary L1 contract, becoming the owner of the bridge on L2.
8 |
9 | ```solidity
10 | address public immutable OWNER;
11 | address public immutable MESSENGER_CONTRACT;
12 | address public immutable L2_BRIDGE_ADDRESS;
13 | constructor(address _owner, address _messenger, address _bridge) {
14 | OWNER = _owner;
15 | MESSENGER_CONTRACT = _messenger;
16 | L2_BRIDGE_ADDRESS = _bridge;
17 |
18 | }
19 |
20 | function setOwnerOnL2(uint256[] calldata payload, uint256 selector) external {
21 | require(owner == msg.sender, "not owner");
22 | IStarknetMessaging(MESSENGER_CONTRACT).sendMessageToL2(L2_BRIDGE_ADDRESS, selector, payload);
23 | }
24 | ```
25 |
26 | ```Cairo
27 | #[storage]
28 | struct Storage {
29 | owner: ContractAddress
30 | }
31 |
32 | #[l1_handler]
33 | fn set_owner_from_l1(ref self: ContractState, from_address: felt252, new_owner: ContractAddress) {
34 | self.owner.write(new_owner);
35 | }
36 |
37 | ```
38 |
39 | ## Mitigations
40 |
41 | - Make sure to validate the `from_address`, otherwise any L1 contract can invoke the annotated Starknet function.
42 | - Consider using Caracal, as it comes with a detector for verifying if the `from_address` is unchecked in an `l1_handler` function.
43 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cosmos/README.md:
--------------------------------------------------------------------------------
1 | # (Not So) Smart Cosmos
2 |
3 | This repository contains examples of common Cosmos applications vulnerabilities, including code from real applications. Use Not So Smart Cosmos to learn about Cosmos (Tendermint) vulnerabilities, as a reference when performing security reviews, and as a benchmark for security and analysis tools.
4 |
5 | ## Features
6 |
7 | Each _Not So Smart Cosmos_ includes a standard set of information:
8 |
9 | - Description of the vulnerability type
10 | - Attack scenarios to exploit the vulnerability
11 | - Recommendations to eliminate or mitigate the vulnerability
12 | - Real-world contracts that exhibit the flaw
13 | - References to third-party resources with more information
14 |
15 | ## Vulnerabilities
16 |
17 | | Not So Smart Contract | Description |
18 | | -------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
19 | | [Incorrect signers](incorrect_getsigners) | Broken access controls due to incorrect signers validation |
20 | | [Non-determinism](non_determinism) | Consensus failure because of non-determinism |
21 | | [Not prioritized messages](messages_priority) | Risks arising from usage of not prioritized message types |
22 | | [Slow ABCI methods](abci_fast) | Consensus failure because of slow ABCI methods |
23 | | [ABCI methods panic](abci_panic) | Chain halt due to panics in ABCI methods |
24 | | [Broken bookkeeping](broken_bookkeeping) | Exploit mismatch between different modules' views on balances |
25 | | [Rounding errors](rounding_errors) | Bugs related to imprecision of finite precision arithmetic |
26 | | [Unregistered message handler](unregistered_msg_handler) | Broken functionality because of unregistered msg handler |
27 | | [Missing error handler](missing_error_handler) | Missing error handling leads to successful execution of a transaction that should have failed |
28 |
29 | ## Credits
30 |
31 | These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/).
32 |
33 | If you have questions, problems, or just want to learn more, then join the #ethereum channel on the [Empire Hacking Slack](https://slack.empirehacking.nyc/) or [contact us](https://www.trailofbits.com/contact/) directly.
34 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cosmos/abci_fast/README.md:
--------------------------------------------------------------------------------
1 | # Slow ABCI methods
2 |
3 | ABCI methods (like `EndBlocker`) [are not constrained by `gas`](https://docs.cosmos.network/v0.45/basics/app-anatomy.html#beginblocker-and-endblocker). Therefore, it is essential to ensure that they always will finish in a reasonable time. Otherwise, the chain will halt.
4 |
5 | ## Example
6 |
7 | Below you can find part of a tokens lending application. Before a block is executed, the `BeginBlocker` method charges an interest for each borrower.
8 |
9 | ```go
10 | func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
11 | updatePrices(ctx, k)
12 | accrueInterest(ctx, k)
13 | }
14 |
15 | func accrueInterest(ctx sdk.Context, k keeper.Keeper) {
16 | for _, pool := range k.GetLendingPools() {
17 | poolAssets := k.GetPoolAssets(ctx, pool.Id)
18 | for userId, _ := range k.GetAllUsers() {
19 | for _, asset := range poolAssets {
20 | for _, loan := range k.GetUserLoans(ctx, pool, asset, userId) {
21 | if err := k.AccrueInterest(ctx, loan); err != nil {
22 | k.PunishUser(ctx, userId)
23 | }
24 | }
25 | }
26 | }
27 | }
28 | }
29 | ```
30 |
31 | The `accrueInterest` contains multiple nested for loops and is obviously too complex to be efficient. Mischievous
32 | users can take a lot of small loans to slow down computation to a point where the chain is not able to keep up with blocks production and halts.
33 |
34 | ## Mitigations
35 |
36 | - Estimate computational complexity of all implemented ABCI methods and ensure that they will scale correctly with the application's usage growth
37 | - Implement stress tests for the ABCI methods
38 | - [Ensure that minimal fees are enforced on all messages](https://docs.cosmos.network/v0.46/basics/gas-fees.html#introduction-to-gas-and-fees) to prevent spam
39 |
40 | ## External examples
41 |
42 | - [Gravity Bridge's `slashing` method was executed in the `EndBlocker` and contained a computationally expensive, nested loop](https://github.com/althea-net/cosmos-gravity-bridge/issues/347).
43 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cosmos/abci_panic/README.md:
--------------------------------------------------------------------------------
1 | # ABCI methods panic
2 |
3 | A `panic` inside an ABCI method (e.g., `EndBlocker`) will stop the chain. There should be no unanticipated `panic`s in these methods.
4 |
5 | Some less expected `panic` sources are:
6 |
7 | - [`Coins`, `DecCoins`, `Dec`, `Int`, and `UInt` types panics a lot](https://github.com/cosmos/cosmos-sdk/blob/afbb0bd1941f7ad36e086913153af02eb6a68f5a/types/coin.go#L68), [for example on overflows](https://github.com/cosmos/cosmos-sdk/blob/afbb0bd1941f7ad36e086913153af02eb6a68f5a/types/dec_coin.go#L105) and [rounding errors](https://github.com/cosmos/cosmos-sdk/blob/afbb0bd1941f7ad36e086913153af02eb6a68f5a/types/decimal.go#L648)
8 | - [`new Dec` panics](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/types@v0.45.5#Dec)
9 | - [`x/params`'s `SetParamSet` panics if arguments are invalid](https://github.com/cosmos/cosmos-sdk/blob/1b1dbf8ab722e4689e14a5a2a1fc433b69bc155e/x/params/doc.go#L107-L108)
10 |
11 | ## Example
12 |
13 | The application below enforces limits on how much coins can be borrowed globally. If the `loan.Borrowed` array of Coins can be forced to be not-sorted (by coins' denominations), the `Add` method will `panic`.
14 |
15 | Moreover, the `Mul` may panic if some asset's price becomes large.
16 |
17 | ```go
18 | func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
19 | if !validateTotalBorrows(ctx, k) {
20 | k.PauseNewLoans(ctx)
21 | }
22 | }
23 |
24 | func validateTotalBorrows(ctx sdk.Context, k keeper.Keeper) {
25 | total := sdk.NewCoins()
26 | for _, loan := range k.GetUsersLoans() {
27 | total.Add(loan.Borrowed...)
28 | }
29 |
30 | for _, totalOneAsset := range total {
31 | if totalOneAsset.Amount.Mul(k.GetASsetPrice(totalOneAsset.Denom)).GTE(k.GetGlobalMaxBorrow()) {
32 | return false
33 | }
34 | }
35 | return true
36 | }
37 | ```
38 |
39 | ## Mitigations
40 |
41 | - [Use CodeQL static analysis](https://github.com/crypto-com/cosmos-sdk-codeql/blob/main/src/beginendblock-panic.ql) to detect `panic`s in ABCI methods
42 | - Review the code against unexpected `panic`s
43 |
44 | ## External examples
45 |
46 | - [Gravity Bridge can `panic` in multiple locations in the `EndBlocker` method](https://giters.com/althea-net/cosmos-gravity-bridge/issues/348)
47 | - [Agoric `panic`s purposefully if the `PushAction` method returns an error](https://github.com/Agoric/agoric-sdk/blob/9116ede69169ebb252faf069d90022e8e05c6a4e/golang/cosmos/x/vbank/module.go#L166)
48 | - [Setting invalid parameters in `x/distribution` module causes `panic` in `BeginBlocker`](https://github.com/cosmos/cosmos-sdk/issues/5808). Valid parameters are [described in the documentation](https://docs.cosmos.network/v0.45/modules/distribution/07_params.html).
49 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cosmos/incorrect_getsigners/README.md:
--------------------------------------------------------------------------------
1 | # Incorrect Signers
2 |
3 | In Cosmos, transaction's signature(s) are validated against public keys (addresses) taken from the transaction itself,
4 | where locations of the keys [are specified in `GetSigners` methods](https://docs.cosmos.network/v0.46/core/transactions.html#signing-transactions).
5 |
6 | In the simplest case there is just one signer required, and its address is simple to use correctly.
7 | However, in more complex scenarios like when multiple signatures are required or a delegation schema is implemented,
8 | it is possible to make mistakes about what addresses in the transaction (the message) are actually authenticated.
9 |
10 | Fortunately, mistakes in `GetSigners` should make part of application's intended functionality not working,
11 | making it easy to spot the bug.
12 |
13 | ## Example
14 |
15 | The example application allows an author to create posts. A post can be created with a `MsgCreatePost` message, which has `signer` and `author` fields.
16 |
17 | ```proto
18 | service Msg {
19 | rpc CreatePost(MsgCreatePost) returns (MsgCreatePostResponse);
20 | }
21 |
22 | message MsgCreatePost {
23 | string signer = 1;
24 | string author = 2;
25 | string title = 3;
26 | string body = 4;
27 | }
28 |
29 | message MsgCreatePostResponse {
30 | uint64 id = 1;
31 | }
32 |
33 | message Post {
34 | string author = 1;
35 | uint64 id = 2;
36 | string title = 3;
37 | string body = 4;
38 | }
39 | ```
40 |
41 | The `signer` field is used for signature verification - as can be seen in `GetSigners` method below.
42 |
43 | ```go
44 | func (msg *MsgCreatePost) GetSigners() []sdk.AccAddress {
45 | signer, err := sdk.AccAddressFromBech32(msg.Signer)
46 | if err != nil {
47 | panic(err)
48 | }
49 | return []sdk.AccAddress{Signer}
50 | }
51 |
52 | func (msg *MsgCreatePost) GetSignBytes() []byte {
53 | bz := ModuleCdc.MustMarshalJSON(msg)
54 | return sdk.MustSortJSON(bz)
55 | }
56 |
57 | func (msg *MsgCreatePost) ValidateBasic() error {
58 | _, err := sdk.AccAddressFromBech32(msg.Signer)
59 | if err != nil {
60 | return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err)
61 | }
62 | return nil
63 | }
64 | ```
65 |
66 | The `author` field is saved along with the post's content:
67 |
68 | ```go
69 | func (k msgServer) CreatePost(goCtx context.Context, msg *types.MsgCreatePost) (*types.MsgCreatePostResponse, error) {
70 | ctx := sdk.UnwrapSDKContext(goCtx)
71 |
72 | var post = types.Post{
73 | Author: msg.Author,
74 | Title: msg.Title,
75 | Body: msg.Body,
76 | }
77 |
78 | id := k.AppendPost(ctx, post)
79 |
80 | return &types.MsgCreatePostResponse{Id: id}, nil
81 | }
82 | ```
83 |
84 | The bug here - mismatch between the message signer address and the stored address - allows users to impersonate other users by sending an arbitrary `author` field.
85 |
86 | ## Mitigations
87 |
88 | - Keep signers-related logic simple
89 | - Implement basic sanity tests for all functionalities
90 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cosmos/missing_error_handler/README.md:
--------------------------------------------------------------------------------
1 | # Missing error handler
2 |
3 | The idiomatic way of handling errors in `Go` is to compare the returned error to nil. This way of checking for errors gives the programmer a lot of control. However, when error handling is ignored it can also lead to numerous problems. The impact of this is most obvious in method calls in the `bankKeeper` module, which even causes some accounts with insufficient balances to perform `SendCoin` operations normally without triggering a transaction failure.
4 |
5 | ## Example
6 |
7 | In the following code, `k.bankKeeper.SendCoins(ctx, sender, receiver, amount)` does not have any return values being used, including `err`. This results in `SendCoin` not being able to prevent the transaction from executing even if there is an `error` due to insufficient balance in `SendCoin`.
8 |
9 | ```golang
10 | func (k msgServer) Transfer(goCtx context.Context, msg *types.MsgTransfer) (*types.MsgTransferResponse, error) {
11 | ...
12 | k.bankKeeper.SendCoins(ctx, sender, receiver, amount)
13 | ...
14 | return &types.MsgTransferResponse{}, nil
15 | }
16 | ```
17 |
18 | ## Mitigations
19 |
20 | - Implement the error handling process instead of missing it
21 |
22 | ## External examples
23 |
24 | - [ignite's tutorials](https://github.com/ignite/cli/issues/2828).
25 | - [Fadeev's Loan Project](https://github.com/fadeev/loan/blob/master/x/loan/keeper/msg_server_approve_loan.go)
26 | - [JackalLabs](https://github.com/JackalLabs/canine-chain/issues/8).
27 | - [OllO](https://github.com/OllO-Station/ollo/issues/20)
28 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cosmos/non_determinism/README.md:
--------------------------------------------------------------------------------
1 | # Non-determinism
2 |
3 | Non-determinism in conensus-relevant code will cause the blockchain to halt.
4 | There are quite a few sources of non-determinism, some of which are specific to the Go language:
5 |
6 | - [`range` iterations over an unordered map or other operations involving unordered structures](https://go.dev/blog/maps#iteration-order)
7 | - [Implementation (platform) dependent types like `int`](https://go.dev/ref/spec#Numeric_types) or `filepath.Ext`
8 | - [goroutines and `select` statement](https://github.com/golang/go/issues/33702)
9 | - [Memory addresses](https://github.com/cosmos/cosmos-sdk/issues/11726#issuecomment-1108427164)
10 | - [Floating point arithmetic operations](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems)
11 | - Randomness ([may be problematic even with a constant seed](https://github.com/golang/go/issues/42701))
12 | - Local time and timezones
13 | - Packages like `unsafe`, `reflect`, and `runtime`
14 |
15 | ## Example
16 |
17 | Below we can see an iteration over a `amounts` `map`. If `k.GetPool` fails for more than one `asset`, then different nodes will fail with different errors, causing the chain to halt.
18 |
19 | ```go
20 | func (k msgServer) CheckAmounts(goCtx context.Context, msg *types.MsgCheckAmounts) (*types.MsgCheckAmountsResponse, error) {
21 | ctx := sdk.UnwrapSDKContext(goCtx)
22 |
23 | amounts := make(map[Asset]int)
24 | for asset, coin := range allMoney.Coins {
25 | amounts[asset] = Compute(coin)
26 | }
27 |
28 | total int := 0
29 | for asset, f := range amounts {
30 | poolSize, err := k.GetPool(ctx, asset, f)
31 | if err != nil {
32 | return nil, err
33 | }
34 | total += poolSize
35 | }
36 |
37 | if total == 0 {
38 | return nil, errors.New("Zero total")
39 | }
40 |
41 | return &types.MsgCheckAmountsResponse{}, nil
42 | }
43 | ```
44 |
45 | Even if we fix the `map` problem, it is still possible that the `total` overflows for nodes running on 32-bit architectures earlier than for the rest of the nodes, again causing the chain split.
46 |
47 | ## Mitigations
48 |
49 | - Use static analysis, for example [custom CodeQL rules](https://github.com/crypto-com/cosmos-sdk-codeql)
50 | - Test your application with nodes running on various architectures or require nodes to run on a specific one
51 | - Prepare and test procedures for recovering from a blockchain split
52 |
53 | ## External examples
54 |
55 | - [ThorChain halt due to "iteration over a map error-ing at different indexes"](https://gitlab.com/thorchain/thornode/-/issues/1169)
56 | - [Cyber's had problems with `float64` type](https://github.com/cybercongress/go-cyber/issues/66)
57 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/cosmos/rounding_errors/README.md:
--------------------------------------------------------------------------------
1 | # Rounding errors
2 |
3 | Application developers must take care of correct rounding of numbers, especially if the rounding impacts tokens amounts.
4 |
5 | Cosmos-sdk offers two custom types for dealing with numbers:
6 |
7 | - `sdk.Int` (`sdk.UInt`) type for integral numbers
8 | - `sdk.Dec` type for decimal arithmetic
9 |
10 | The `sdk.Dec` type [has problems with precision and does not guarantee associativity](https://github.com/cosmos/cosmos-sdk/issues/7773), so it must be used carefully. But even if a more robust library for decimal numbers is deployed in the cosmos-sdk, rounding may be unavoidable.
11 |
12 | ## Example
13 |
14 | Below we see a simple example demonstrating `sdk.Dec` type's precision problems.
15 |
16 | ```go
17 | func TestDec() {
18 | a := sdk.MustNewDecFromStr("10")
19 | b := sdk.MustNewDecFromStr("1000000010")
20 | x := a.Quo(b).Mul(b)
21 | fmt.Println(x) // 9.999999999999999000
22 |
23 | q := float32(10)
24 | w := float32(1000000010)
25 | y := (q / w) * w
26 | fmt.Println(y) // 10
27 | }
28 | ```
29 |
30 | ## Mitigations
31 |
32 | - Ensure that all tokens operations that must round results always benefit the system (application) and not users. In other words, always decide on the correct rounding direction. See [Appendix G in the Umee audit report](https://github.com/trailofbits/publications/blob/master/reviews/Umee.pdf)
33 |
34 | - Apply "multiplication before division" pattern. That is, instead of computing `(x / y) * z` do `(x * z) / y`
35 |
36 | - Observe [issue #11783](https://github.com/cosmos/cosmos-sdk/issues/11783) for a replacement of the `sdk.Dec` type
37 |
38 | ## External examples
39 |
40 | - [Umee had vulnerability caused by incorrect rounding direction](https://github.com/umee-network/umee/issues/545)
41 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/solana/README.md:
--------------------------------------------------------------------------------
1 | # (Not So) Smart Contracts
2 |
3 | This repository contains examples of common Solana smart contract vulnerabilities, including code from real smart contracts. Use Not So Smart Contracts to learn about Solana vulnerabilities, as a reference when performing security reviews, and as a benchmark for security and analysis tools.
4 |
5 | ## Features
6 |
7 | Each _Not So Smart Contract_ includes a standard set of information:
8 |
9 | - Description of the vulnerability type
10 | - Attack scenarios to exploit the vulnerability
11 | - Recommendations to eliminate or mitigate the vulnerability
12 | - Real-world contracts that exhibit the flaw
13 | - References to third-party resources with more information
14 |
15 | ## Vulnerabilities
16 |
17 | | Not So Smart Contract | Description |
18 | | ------------------------------------------------------------------------ | --------------------------------------------------------- |
19 | | [Arbitrary CPI](arbitrary_cpi) | Arbitrary program account passed in upon invocation |
20 | | [Improper PDA Validation](improper_pda_validation) | PDAs are vulnerable to being spoofed via bump seeds |
21 | | [Ownership Check](ownership_check) | Broken access control due to missing ownership validation |
22 | | [Signer Check](signer_check) | Broken access control due to missing signer validation |
23 | | [Sysvar Account Check](sysvar_account_check) | Sysvar accounts are vulnerable to being spoofed |
24 | | [Improper Instruction Introspection](improper_instruction_introspection) | Program accesses instruction using absolute index |
25 |
26 | ## Credits
27 |
28 | These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/).
29 |
30 | If you have questions, problems, or just want to learn more, then join the #solana channel on the [Empire Hacking Slack](https://slack.empirehacking.nyc/) or [contact us](https://www.trailofbits.com/contact/) directly.
31 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/solana/arbitrary_cpi/README.md:
--------------------------------------------------------------------------------
1 | # Arbitrary CPI
2 |
3 | Solana allows programs to call one another through cross-program invocation (CPI). This can be done via `invoke`, which is responsible for routing the passed in instruction to the program. Whenever an external contract is invoked via CPI, the program must check and verify the program ID. If the program ID isn't verified, then the contract can call an attacker-controlled program instead of the intended one.
4 |
5 | View ToB's lint implementation for the arbitrary CPI issue [here](https://github.com/crytic/solana-lints/tree/master/lints/arbitrary_cpi).
6 |
7 | ## Exploit Scenario
8 |
9 | Consider the following `withdraw` function. Tokens are able to be withdrawn from the pool to a user account. The program invoked here is user-controlled and there's no check that the program passed in is the intended `token_program`. This allows a malicious user to pass in their own program with functionality to their discretion - such as draining the pool of the inputted `amount` tokens.
10 |
11 | ### Example Contract
12 |
13 | ```rust
14 | pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
15 | let account_info_iter = &mut accounts.iter();
16 | let token_program = next_account_info(account_info_iter)?;
17 | let pool = next_account_info(account_info_iter)?;
18 | let pool_auth = next_account_info(account_info_iter)?;
19 | let destination = next_account_info(account_info_iter)?;
20 | invoke(
21 | &spl_token::instruction::transfer(
22 | &token_program.key,
23 | &pool.key,
24 | &destination.key,
25 | &pool_auth.key,
26 | &[],
27 | amount,
28 | )?,
29 | &[
30 | &pool.clone(),
31 | &destination.clone(),
32 | &pool_auth.clone(),
33 | ],
34 | )
35 | }
36 | ```
37 |
38 | _Inspired by [Sealevel](https://github.com/coral-xyz/sealevel-attacks/)_
39 |
40 | ## Mitigation
41 |
42 | ```rust
43 | if INPUTTED_PROGRAM.key != &INTENDED_PROGRAM::id() {
44 | return Err(ProgramError::InvalidProgramId);
45 | }
46 | ```
47 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/solana/improper_instruction_introspection/README.md:
--------------------------------------------------------------------------------
1 | # Improper Instruction Introspection
2 |
3 | Solana allows programs to inspect other instructions in the transaction using the [Instructions sysvar](https://docs.solanalabs.com/implemented-proposals/instruction_introspection). The programs requiring instruction introspection divide an operation into two or more instructions. The program have to ensure that all the instructions related to an operation are correlated. The program could access the instructions using absolute indexes or relative indexes. Using relative indexes ensures that the instructions are implicitly correlated. The programs using absolute indexes might become vulnerable to exploits if additional validations to ensure the correlation between instructions are not performed.
4 |
5 | ## Exploit Scenario
6 |
7 | A program mints tokens based on the amount of tokens transferred to it. A program checks that `Token::transfer` instruction is called in the first instruction of the transaction. The program uses absolute index `0` to access the instruction data, program id and validates them. If the first instruction is a `Token::transfer` then program mints some tokens.
8 |
9 | ```rust
10 | pub fn mint(
11 | ctx: Context,
12 | // ...
13 | ) -> Result<(), ProgramError> {
14 | // [...]
15 | let transfer_ix = solana_program::sysvar::instructions::load_instruction_at_checked(
16 | 0usize,
17 | ctx.instructions_account.to_account_info(),
18 | )?;
19 |
20 | if transfer_ix.program_id != spl_token::id() {
21 | return Err(ProgramError::InvalidInstructionData);
22 | }
23 | // check transfer_ix transfers
24 | // mint to the user account
25 | // [...]
26 | Ok(())
27 | }
28 | ```
29 |
30 | The program uses absolute index to access the transfer instruction. An attacker can create transaction containing multiple calls to `mint` and single transfer instruction.
31 |
32 | 0. `transfer()`
33 | 1. `mint(, ...)`
34 | 2. `mint(, ...)`
35 | 3. `mint(, ...)`
36 | 4. `mint(, ...)`
37 | 5. `mint(, ...)`
38 |
39 | All the `mint` instructions verify the same transfer instruction. The attacker gets 4 times more than the intended tokens.
40 |
41 | ## Mitigation
42 |
43 | Use a relative index, for example `-1`, and ensure the instruction at that offset is the `transfer` instruction.
44 |
45 | ```rust
46 | pub fn mint(
47 | ctx: Context,
48 | // ...
49 | ) -> Result<(), ProgramError> {
50 | // [...]
51 | let transfer_ix = solana_program::sysvar::instructions::get_instruction_relative(
52 | -1i64,
53 | ctx.instructions_account.to_account_info(),
54 | )?;
55 | // [...]
56 | }
57 | ```
58 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/solana/improper_pda_validation/README.md:
--------------------------------------------------------------------------------
1 | # Improper PDA bump seed validation
2 |
3 | PDAs (Program Derived Addresses) are, by definition, [program-controlled](https://docs.solana.com/terminology#program-derived-account-pda) accounts and therefore can be used to sign without the need to provide a private key. PDAs are generated through a set of seeds and a program id, which are then collectively hashed to verify that the point doesn't lie on the ed25519 curve (the curve used by Solana to sign transactions).
4 |
5 | Values on this elliptic curve have a corresponding private key, which wouldn't make it a PDA. In the case a point lying on the elliptic curve is found, our 32-byte address is modified through the addition of a bump to "bump" it off the curve. A bump, represented by a singular byte iterating through 255 to 0, is added onto our input until a point that doesn’t lie on the elliptic curve is generated, meaning that we’ve found an address without an associated private key.
6 |
7 | The issue arises with seeds being able to have multiple bumps, thus allowing varying PDAs that are valid from the same seeds. An attacker can create a PDA with the correct program ID but with a different bump. Without any explicit check against the bump seed itself, the program leaves itself vulnerable to the attacker tricking the program into thinking they’re using the expected PDA when in fact they're interacting with an illegitimate account.
8 |
9 | View ToB's lint implementation for the bump seed canonicalization issue [here](https://github.com/crytic/solana-lints/tree/master/lints/bump_seed_canonicalization).
10 |
11 | ## Exploit Scenario
12 |
13 | In Solana, the `create_program_address` function creates a 32-byte address based off the set of seeds and program address. On its own, the point may lie on the ed25519 curve. Consider the following without any other validation being referenced within a sensitive function, such as one that handles transfers. That PDA could be spoofed by a passed in user-controlled PDA.
14 |
15 | ### Example Contract
16 |
17 | ```rust
18 | let program_address = Pubkey::create_program_address(&[key.to_le_bytes().as_ref(), &[reserve_bump]], program_id)?;
19 |
20 | ...
21 | ```
22 |
23 | ## Mitigation
24 |
25 | The `find_program_address` function finds the largest bump seeds for which there exists a corresponding PDA (i.e., a point not on the ed25519 curve), and returns both the address and the bump seed. The function panics in the case that no PDA address can be found.
26 |
27 | ```rust
28 | let (address, _system_bump) = Pubkey::find_program_address(&[key.to_le_bytes().as_ref()], program_id);
29 |
30 | if program_address != &account_data.key() {
31 | return Err(ProgramError::InvalidAddress);
32 | }
33 | ```
34 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/solana/ownership_check/README.md:
--------------------------------------------------------------------------------
1 | # Missing Ownership Check
2 |
3 | Accounts in Solana include metadata of an owner. These owners are identified by their own program ID. Without sufficient checks that the expected program ID matches that of the passed in account, an attacker can fabricate an account with spoofed data to pass any other preconditions.
4 |
5 | This malicious account will inherently have a different program ID as owner, but considering there’s no check that the program ID is the same, as long as the other preconditions are passed, the attacker can trick the program into thinking their malicious account is the expected account.
6 |
7 | ## Exploit Scenario
8 |
9 | The following contract allows funds to be dispersed from an escrow account vault, provided the escrow account's state is `Complete`. Unfortunately, there is no check that the `State` account is owned by the program.
10 | Therefore, a malicious actor can pass in their own fabricated `State` account with spoofed data, allowing the attacker to send the vault's funds to themselves.
11 |
12 | ### Example Contract
13 |
14 | ```rust
15 | fn pay_escrow(_program_id: &Pubkey, accounts: &[AccountInfo], _instruction_data: &[u8]) -> ProgramResult {
16 | let account_info_iter = &mut accounts.iter();
17 | let state_info = next_account_info(account_info_iter)?;
18 | let escrow_vault_info = next_account_info(account_info_iter)?;
19 | let escrow_receiver_info = next_account_info(account_info_iter)?;
20 |
21 | let state = State::deserialize(&mut &**state_info.data.borrow())?;
22 |
23 | if state.escrow_state == EscrowState::Complete {
24 | **escrow_vault_info.try_borrow_mut_lamports()? -= state.amount;
25 | **escrow_receiver_info.try_borrow_mut_lamports()? += state.amount;
26 | }
27 |
28 | Ok(())
29 | }
30 | ```
31 |
32 | _Inspired by [SPL Lending Program](https://github.com/solana-labs/solana-program-library/tree/master/token-lending/program)_
33 |
34 | ## Mitigation
35 |
36 | ```rust
37 | if EXPECTED_ACCOUNT.owner != program_id {
38 | return Err(ProgramError::IncorrectProgramId);
39 | }
40 | ```
41 |
42 | For further reading on different forms of account verification in Solana and implementation refer to the [Solana Cookbook](https://solanacookbook.com/references/programs.html#how-to-verify-accounts).
43 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/solana/signer_check/README.md:
--------------------------------------------------------------------------------
1 | # Missing Signer Check
2 |
3 | In Solana, each public key has an associated private key that can be used to generate signatures. A [transaction](https://docs.solana.com/developing/programming-model/transactions) lists each account public key whose private key was used to generate a signature for the transaction. These signatures are verified using the inputted public keys prior to transaction execution.
4 |
5 | In case certain permissions are required to perform a sensitive function of the contract, a missing signer check becomes an issue. Without this check, an attacker would be able to call the respective access controlled functions permissionlessly.
6 |
7 | ## Exploit Scenario
8 |
9 | The following contract sets an escrow account's state to `Complete`. Unfortunately, the contract does not check whether the `State` account's `authority` has signed the transaction.
10 | Therefore, a malicious actor can set the state to `Complete`, without needing access to the `authority`’s private key.
11 |
12 | ### Example Contract
13 |
14 | ```rust
15 | fn complete_escrow(_program_id: &Pubkey, accounts: &[AccountInfo], _instruction_data: &[u8]) -> ProgramResult {
16 | let account_info_iter = &mut accounts.iter();
17 | let state_info = next_account_info(account_info_iter)?;
18 | let authority = next_account_info(account_info_iter)?;
19 |
20 | let mut state = State::deserialize(&mut &**state_info.data.borrow())?;
21 |
22 | if state.authority != *authority.key {
23 | return Err(ProgramError::IncorrectAuthority);
24 | }
25 |
26 | state.escrow_state = EscrowState::Complete;
27 | state.serialize(&mut &mut **state_info.data.borrow_mut())?;
28 |
29 | Ok(())
30 |
31 | }
32 | ```
33 |
34 | _Inspired by [SPL Lending Program](https://github.com/solana-labs/solana-program-library/tree/master/token-lending/program)_
35 |
36 | ## Mitigation
37 |
38 | ```rust
39 | if !EXPECTED_ACCOUNT.is_signer {
40 | return Err(ProgramError::MissingRequiredSignature);
41 | }
42 | ```
43 |
44 | For further reading on different forms of account verification in Solana and implementation refer to the [Solana Cookbook](https://solanacookbook.com/references/programs.html#how-to-verify-accounts).
45 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/substrate/README.md:
--------------------------------------------------------------------------------
1 | # (Not So) Smart Pallets
2 |
3 | This repository contains examples of common Substrate pallet vulnerabilities. Use Not So Smart Pallets to learn about Substrate vulnerabilities, as a reference when performing security reviews, and as a benchmark for security and analysis tools.
4 |
5 | ## Features
6 |
7 | Each _Not So Smart Pallet_ includes a standard set of information:
8 |
9 | - Description of the vulnerability type
10 | - Attack scenarios to exploit the vulnerability
11 | - Recommendations to eliminate or mitigate the vulnerability
12 | - A mock pallet that exhibits the flaw
13 | - References to third-party resources with more information
14 |
15 | ## Vulnerabilities
16 |
17 | | Not So Smart Pallet | Description |
18 | | ---------------------------------------------------- | ---------------------------------------------------------------------- |
19 | | [Arithmetic overflow](arithmetic_overflow) | Integer overflow due to incorrect use of arithmetic operators |
20 | | [Don't panic!](dont_panic) | System panics create a potential DoS attack vector |
21 | | [Weights and fees](weights_and_fees) | Incorrect weight calculations can create a potential DoS attack vector |
22 | | [Verify first](verify_first) | Verify first, write last |
23 | | [Unsigned transaction validation](validate_unsigned) | Insufficient validation of unsigned transactions |
24 | | [Bad randomness](randomness) | Unsafe sources of randomness in Substrate |
25 | | [Bad origin](origins) | Incorrect use of call origin can lead to bypassing access controls |
26 |
27 | ## Credits
28 |
29 | These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/).
30 |
31 | If you have questions, problems, or just want to learn more, then join the #ethereum channel on the [Empire Hacking Slack](https://slack.empirehacking.nyc/) or [contact us](https://www.trailofbits.com/contact/) directly.
32 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/substrate/arithmetic_overflow/README.md:
--------------------------------------------------------------------------------
1 | # Arithmetic overflow
2 |
3 | Arithmetic overflow in Substrate occurs when arithmetic operations are performed using primitive operations instead of specialized functions that check for overflow. When a Substrate node is compiled in `debug` mode, integer overflows will cause the program to panic. However, when the node is compiled in `release` mode (e.g. `cargo build --release`), Substrate will perform two's complement wrapping. A production-ready node will be compiled in `release` mode, which makes it vulnerable to arithmetic overflow.
4 |
5 | # Example
6 |
7 | In the [`pallet-overflow`](https://github.com/crytic/building-secure-contracts/blob/master/not-so-smart-contracts/substrate/arithmetic_overflow/pallet-overflow.rs) pallet, notice that the `transfer` function sets `update_sender` and `update_to` using primitive arithmetic operations.
8 |
9 | ```rust
10 | /// Allow minting account to transfer a given balance to another account.
11 | ///
12 | /// Parameters:
13 | /// - `to`: The account to receive the transfer.
14 | /// - `amount`: The amount of balance to transfer.
15 | ///
16 | /// Emits `Transferred` event when successful.
17 | #[pallet::weight(10_000)]
18 | pub fn transfer(
19 | origin: OriginFor,
20 | to: T::AccountId,
21 | amount: u64,
22 | ) -> DispatchResultWithPostInfo {
23 | let sender = ensure_signed(origin)?;
24 | let sender_balance = Self::get_balance(&sender);
25 | let receiver_balance = Self::get_balance(&to);
26 |
27 | // Calculate new balances.
28 | let update_sender = sender_balance - amount;
29 | let update_to = receiver_balance + amount;
30 | [...]
31 | }
32 | ```
33 |
34 | The sender of the extrinsic can exploit this vulnerability by causing `update_sender` to underflow, which artificially inflates their balance.
35 |
36 | **Note**: Aside from the stronger mitigations mentioned below, a check to make sure that `sender` has at least `amount` balance would have also prevented an underflow.
37 |
38 | # Mitigations
39 |
40 | - Use `checked` or `saturating` functions for arithmetic operations.
41 | - [`CheckedAdd` trait](https://docs.rs/num/0.4.0/num/traits/trait.CheckedAdd.html)
42 | - [`Saturating` trait](https://docs.rs/num/0.4.0/num/traits/trait.Saturating.html)
43 |
44 | # References
45 |
46 | - https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-overflow
47 | - https://docs.substrate.io/reference/how-to-guides/basics/use-helper-functions/
48 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/substrate/dont_panic/README.md:
--------------------------------------------------------------------------------
1 | # Don't Panic!
2 |
3 | Panics occur when the node enters a state that it cannot handle and stops the program / process instead of trying to proceed. Panics can occur for a large variety of reasons such as out-of-bounds array access, incorrect data validation, type conversions, and much more. A well-designed Substrate node must NEVER panic! If a node panics, it opens up the possibility for a denial-of-service (DoS) attack.
4 |
5 | # Example
6 |
7 | In the [`pallet-dont-panic`](https://github.com/crytic/building-secure-contracts/blob/master/not-so-smart-contracts/substrate/dont_panic/pallet-dont-panic.rs) pallet, the `find_important_value` dispatchable checks to see if `useful_amounts[0]` is greater than `1_000`. If so, it sets the `ImportantVal` `StorageValue` to the value held in `useful_amounts[0]`.
8 |
9 | ```rust
10 | /// Do some work
11 | ///
12 | /// Parameters:
13 | /// - `useful_amounts`: A vector of u64 values in which there is a important value.
14 | ///
15 | /// Emits `FoundVal` event when successful.
16 | #[pallet::weight(10_000)]
17 | pub fn find_important_value(
18 | origin: OriginFor,
19 | useful_amounts: Vec,
20 | ) -> DispatchResultWithPostInfo {
21 | let sender = ensure_signed(origin)?;
22 |
23 | ensure!(useful_amounts[0] > 1_000, >::NoImportantValueFound);
24 |
25 | // Found the important value
26 | ImportantValue::::put(&useful_amounts[0]);
27 | [...]
28 | }
29 | ```
30 |
31 | However, notice that there is no check before the array indexing to see whether the length of `useful_amounts` is greater than zero. Thus, if `useful_amounts` is empty, the indexing will cause an array out-of-bounds error which will make the node panic. Since the `find_important_value` function is callable by anyone, an attacker can set `useful_amounts` to an empty array and spam the network with malicious transactions to launch a DoS attack.
32 |
33 | # Mitigations
34 |
35 | - Write non-throwing Rust code (e.g. prefer returning [`Result`](https://paritytech.github.io/substrate/master/frame_support/dispatch/result/enum.Result.html) types, use [`ensure!`](https://paritytech.github.io/substrate/master/frame_support/macro.ensure.html), etc.).
36 | - Proper data validation of all input parameters is crucial to ensure that an unexpected panic does not occur.
37 | - A thorough suite of unit tests should be implemented.
38 | - Fuzz testing (e.g. using [`test-fuzz`](https://github.com/trailofbits/test-fuzz)) can aid in exploring more of the input space.
39 |
40 | # References
41 |
42 | - https://docs.substrate.io/main-docs/build/events-errors/#errors
43 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/substrate/dont_panic/pallet-dont-panic.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(feature = "std"), no_std)]
2 |
3 | pub use pallet::*;
4 |
5 | #[frame_support::pallet]
6 | pub mod pallet {
7 | use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*};
8 | use frame_system::pallet_prelude::*;
9 | use sp_std::prelude::*;
10 |
11 | /// Pallet configuration
12 | #[pallet::config]
13 | pub trait Config: frame_system::Config {
14 | /// Because this pallet emits events, it depends on the runtime's definition of an event.
15 | type Event: From> + IsType<::Event>;
16 | }
17 |
18 | #[pallet::pallet]
19 | #[pallet::without_storage_info]
20 | #[pallet::generate_store(pub(super) trait Store)]
21 | pub struct Pallet(_);
22 |
23 | /// Storage item for holding an ImportantValue
24 | #[pallet::storage]
25 | #[pallet::getter(fn get_val)]
26 | pub(super) type ImportantValue = StorageValue<_, u64, ValueQuery>;
27 |
28 | #[pallet::event]
29 | #[pallet::generate_deposit(pub(super) fn deposit_event)]
30 | pub enum Event {
31 | /// Emit after val is found
32 | FoundVal(T::AccountId, u64),
33 | }
34 |
35 | #[pallet::hooks]
36 | impl Hooks> for Pallet {}
37 |
38 | #[pallet::error]
39 | pub enum Error {
40 | NoImportantValueFound,
41 | }
42 |
43 | #[pallet::call]
44 | impl Pallet {
45 | /// Find important value
46 | ///
47 | /// Parameters:
48 | /// - `useful_amounts`: A vector of u64 values in which there is a important value.
49 | ///
50 | /// Emits `FoundVal` event when successful.
51 | #[pallet::weight(10_000)]
52 | pub fn find_important_value(
53 | origin: OriginFor,
54 | useful_amounts: Vec,
55 | ) -> DispatchResultWithPostInfo {
56 | let sender = ensure_signed(origin)?;
57 |
58 | ensure!(useful_amounts[0] > 1_000, >::NoImportantValueFound);
59 |
60 | // Found the important value
61 | ImportantValue::::put(&useful_amounts[0]);
62 |
63 | // Emit event
64 | Self::deposit_event(Event::FoundVal(sender, useful_amounts[0]));
65 | Ok(().into())
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/not-so-smart-contracts/substrate/origins/README.md:
--------------------------------------------------------------------------------
1 | # Origins
2 |
3 | The origin of a call tells a dispatchable function where the call has come from. Origins are a way to implement access controls in the system.
4 |
5 | There are three types of origins that can used in the runtime:
6 |
7 | ```rust
8 | pub enum RawOrigin {
9 | Root,
10 | Signed(AccountId),
11 | None,
12 | }
13 | ```
14 |
15 | Outside of the out-of-box origins, custom origins can also be created that are catered to a specific runtime. The primary use case for custom origins is to configure privileged access to dispatch calls in the runtime, outside of `RawOrigin::Root`.
16 |
17 | Using privileged origins, like `RawOrigin::Root` or custom origins, can lead to access control violations if not used correctly. It is a common error to use `ensure_signed` in place of `ensure_root` which would allow any user to bypass the access control placed by using `ensure_root`.
18 |
19 | # Example
20 |
21 | In the [`pallet-bad-origin`](https://github.com/crytic/building-secure-contracts/blob/master/not-so-smart-contracts/substrate/origins/pallet-bad-origin.rs) pallet, there is a `set_important_val` function that should be only callable by the `ForceOrigin` _custom_ origin type. This custom origin allows the pallet to specify that only a specific account can call `set_important_val`.
22 |
23 | ```rust
24 | #[pallet::call]
25 | impl Pallet {
26 | /// Set the important val
27 | /// Should be only callable by ForceOrigin
28 | #[pallet::weight(10_000)]
29 | pub fn set_important_val(
30 | origin: OriginFor,
31 | new_val: u64
32 | ) -> DispatchResultWithPostInfo {
33 | let sender = ensure_signed(origin)?;
34 | // Change to new value
35 | >::put(new_val);
36 |
37 | // Emit event
38 | Self::deposit_event(Event::ImportantValSet(sender, new_val));
39 |
40 | Ok(().into())
41 | }
42 | }
43 | ```
44 |
45 | However, the `set_important_val` is using `ensure_signed`; this allows any account to set `ImportantVal`. To allow only the `ForceOrigin` to call `set_important_val` the following change can be made:
46 |
47 | ```rust
48 | T::ForceOrigin::ensure_origin(origin.clone())?;
49 | let sender = ensure_signed(origin)?;
50 | ```
51 |
52 | # Mitigations
53 |
54 | - Ensure that the correct access controls are placed on privileged functions.
55 | - Develop user documentation on all risks associated with the system, including those associated with privileged users.
56 | - A thorough suite of unit tests that validates access controls is crucial.
57 |
58 | # References
59 |
60 | - https://docs.substrate.io/main-docs/build/origins/
61 | - https://docs.substrate.io/tutorials/build-application-logic/specify-the-origin-for-a-call/
62 | - https://paritytech.github.io/substrate/master/pallet_sudo/index.html#
63 | - https://paritytech.github.io/substrate/master/pallet_democracy/index.html
64 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/substrate/origins/pallet-bad-origin.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(feature = "std"), no_std)]
2 |
3 | pub use pallet::*;
4 |
5 | #[frame_support::pallet]
6 | pub mod pallet {
7 | use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::{*, ValueQuery}};
8 | use frame_system::pallet_prelude::*;
9 | use sp_std::prelude::*;
10 |
11 | /// Pallet configuration
12 | #[pallet::config]
13 | pub trait Config: frame_system::Config {
14 | /// Because this pallet emits events, it depends on the runtime's definition of an event.
15 | type Event: From> + IsType<::Event>;
16 |
17 | // Specifies the account that can perform some action
18 | type ForceOrigin: EnsureOrigin;
19 | }
20 |
21 | #[pallet::pallet]
22 | #[pallet::generate_store(pub(super) trait Store)]
23 | pub struct Pallet(_);
24 |
25 | /// Storage item for important value that should be editable only by root
26 | #[pallet::storage]
27 | #[pallet::getter(fn get_important_val)]
28 | pub(super) type ImportantVal = StorageValue<_, u64, ValueQuery>;
29 |
30 | #[pallet::event]
31 | #[pallet::generate_deposit(pub(super) fn deposit_event)]
32 | pub enum Event {
33 | /// Emit when new important val is set.
34 | ImportantValSet(T::AccountId, u64),
35 | }
36 |
37 | #[pallet::hooks]
38 | impl Hooks> for Pallet {}
39 |
40 | #[pallet::call]
41 | impl Pallet {
42 | /// Set the important val
43 | /// Should be only callable by ForceOrigin
44 | #[pallet::weight(10_000)]
45 | pub fn set_important_val(
46 | origin: OriginFor,
47 | new_val: u64
48 | ) -> DispatchResultWithPostInfo {
49 | T::ForceOrigin::ensure_origin(origin.clone())?;
50 | let sender = ensure_signed(origin)?;
51 | // Change to new value
52 | >::put(new_val);
53 |
54 | // Emit event
55 | Self::deposit_event(Event::ImportantValSet(sender, new_val));
56 |
57 | Ok(().into())
58 | }
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/not-so-smart-contracts/substrate/randomness/pallet-bad-lottery.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(feature = "std"), no_std)]
2 |
3 | pub use pallet::*;
4 |
5 | #[frame_support::pallet]
6 | pub mod pallet {
7 | use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::{*, ValueQuery}};
8 | use frame_system::pallet_prelude::*;
9 | use frame_support::traits::Randomness;
10 | use sp_std::prelude::*;
11 |
12 | /// Pallet configuration
13 | #[pallet::config]
14 | pub trait Config: frame_system::Config {
15 | /// Because this pallet emits events, it depends on the runtime's definition of an event.
16 | type Event: From> + IsType<::Event>;
17 | /// Create a randomness type for this pallet
18 | type MyRandomness: Randomness;
19 | }
20 |
21 | #[pallet::pallet]
22 | #[pallet::generate_store(pub(super) trait Store)]
23 | pub struct Pallet(_);
24 |
25 | /// Storage item for nonce
26 | #[pallet::storage]
27 | #[pallet::getter(fn get_nonce)]
28 | pub(super) type Nonce = StorageValue<_, u64, ValueQuery>;
29 |
30 | /// Storage item for current winner
31 | #[pallet::storage]
32 | #[pallet::getter(fn get_winner)]
33 | pub(super) type Winner = StorageValue<_, T::AccountId, OptionQuery>;
34 |
35 | #[pallet::event]
36 | #[pallet::generate_deposit(pub(super) fn deposit_event)]
37 | pub enum Event {
38 | /// Emit when new winner is found
39 | NewWinner(T::AccountId),
40 | }
41 |
42 | #[pallet::hooks]
43 | impl Hooks> for Pallet {}
44 |
45 | #[pallet::error]
46 | pub enum Error {
47 | /// Guessed the wrong number.
48 | IncorrectGuess,
49 | }
50 |
51 | #[pallet::call]
52 | impl Pallet {
53 | /// Guess the random value
54 | /// If you guess correctly, you become the winner
55 | #[pallet::weight(10_000)]
56 | pub fn guess(
57 | origin: OriginFor,
58 | guess: T::Hash
59 | ) -> DispatchResultWithPostInfo {
60 | let sender = ensure_signed(origin)?;
61 | // Random value.
62 | let nonce = Self::get_and_increment_nonce();
63 | let (random_value, _) = T::MyRandomness::random(&nonce);
64 | // Check if guess is correct
65 | ensure!(guess == random_value, >::IncorrectGuess);
66 | >::put(&sender);
67 |
68 | Self::deposit_event(Event::NewWinner(sender));
69 |
70 | Ok(().into())
71 | }
72 | }
73 |
74 | impl Pallet {
75 | /// Increment the nonce each time guess() is called
76 | pub fn get_and_increment_nonce() -> Vec {
77 | let nonce = Nonce::::get();
78 | Nonce::::put(nonce.wrapping_add(1));
79 | nonce.encode()
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/not-so-smart-contracts/substrate/verify_first/README.md:
--------------------------------------------------------------------------------
1 | # Verify First, Write Last
2 |
3 | **NOTE**: As of [Polkadot v0.9.25](https://github.com/substrate-developer-hub/substrate-docs/issues/1215), the **Verify First, Write Last** practice is no longer required. However, since older versions are still vulnerable and because it is still best practice, it is worth discussing the "Verify First, Write Last" idiom.
4 |
5 | Substrate does not cache state prior to extrinsic dispatch. Instead, state changes are made as they are invoked. This is in contrast to a transaction in Ethereum where, if the transaction reverts, no state changes will persist. In the case of Substrate, if a state change is made and then the dispatch throws a `DispatchError`, the original state change will persist. This unique behavior has led to the "Verify First, Write Last" practice.
6 |
7 | ```rust
8 | {
9 | // all checks and throwing code go here
10 |
11 | // ** no throwing code below this line **
12 |
13 | // all event emissions & storage writes go here
14 | }
15 | ```
16 |
17 | # Example
18 |
19 | In the [`pallet-verify-first`](https://github.com/crytic/building-secure-contracts/blob/master/not-so-smart-contracts/substrate/verify_first/pallet-verify-first.rs) pallet, the `init` dispatchable is used to set up the `TotalSupply` of the token and transfer them to the `sender`. `init` should be only called once. Thus, the `Init` boolean is set to `true` when it is called initially. If `init` is called more than once, the transaction will throw an error because `Init` is already `true`.
20 |
21 | ```rust
22 | /// Initialize the token
23 | /// Transfers the total_supply amount to the caller
24 | /// If init() has already been called, throw AlreadyInitialized error
25 | #[pallet::weight(10_000)]
26 | pub fn init(
27 | origin: OriginFor,
28 | supply: u64
29 | ) -> DispatchResultWithPostInfo {
30 | let sender = ensure_signed(origin)?;
31 |
32 | if supply > 0 {
33 | >::put(&supply);
34 | }
35 | // Set sender's balance to total_supply()
36 | >::insert(&sender, supply);
37 |
38 | // Revert above changes if init() has already been called
39 | ensure!(!Self::is_init(), >::AlreadyInitialized);
40 |
41 | // Set Init StorageValue to `true`
42 | Init::::put(true);
43 |
44 | // Emit event
45 | Self::deposit_event(Event::Initialized(sender));
46 |
47 | Ok(().into())
48 | }
49 | ```
50 |
51 | However, notice that the setting of `TotalSupply` and the transfer of funds happens before the check on `Init`. This violates the "Verify First, Write Last" practice. In an older version of Substrate, this would allow a malicious `sender` to call `init` multiple times and change the value of `TotalSupply` and their balance of the token.
52 |
53 | # Mitigations
54 |
55 | - Follow the "Verify First, Write Last" practice by doing all the necessary data validation before performing state changes and emitting events.
56 |
57 | # References
58 |
59 | - https://docs.substrate.io/main-docs/build/runtime-storage/#best-practices
60 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/substrate/weights_and_fees/pallet-bad-weights.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(feature = "std"), no_std)]
2 |
3 | pub use pallet::*;
4 |
5 | #[frame_support::pallet]
6 | pub mod pallet {
7 | use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*, weights::*};
8 | use frame_system::pallet_prelude::*;
9 | use sp_std::prelude::*;
10 |
11 | /// Pallet configuration
12 | #[pallet::config]
13 | pub trait Config: frame_system::Config {
14 | /// Because this pallet emits events, it depends on the runtime's definition of an event.
15 | type Event: From> + IsType<::Event>;
16 | }
17 |
18 | #[pallet::pallet]
19 | #[pallet::without_storage_info]
20 | #[pallet::generate_store(pub(super) trait Store)]
21 | pub struct Pallet(_);
22 |
23 | /// Storage item for useful_amounts passed to the do_work function
24 | #[pallet::storage]
25 | #[pallet::getter(fn useful_amounts)]
26 | pub(super) type UsefulAmounts = StorageValue<_, Vec, ValueQuery>;
27 |
28 | #[pallet::event]
29 | #[pallet::generate_deposit(pub(super) fn deposit_event)]
30 | pub enum Event {
31 | /// Emit after do_work successfully completes
32 | DidWork(T::AccountId),
33 | }
34 |
35 | #[pallet::hooks]
36 | impl Hooks> for Pallet {}
37 |
38 | /// Custom weight function implementation
39 | pub struct MyWeightFunction(u64);
40 |
41 | /// The weight is linearly proportional to the length of the amounts array
42 | impl WeighData<(&Vec,)> for MyWeightFunction {
43 | fn weigh_data(&self, (amounts,): (&Vec,)) -> Weight {
44 | self.0.saturating_mul(amounts.len() as u64).into()
45 | }
46 | }
47 |
48 | /// Custom weight function implementations need to implement the PaysFee trait
49 | impl PaysFee for MyWeightFunction {
50 | fn pays_fee(&self, _: T) -> Pays {
51 | Pays::Yes
52 | }
53 | }
54 |
55 | /// Custom weight function implementations need to implement the ClassifyDispatch trait
56 | impl ClassifyDispatch for MyWeightFunction {
57 | fn classify_dispatch(&self, _: T) -> DispatchClass {
58 | // Classify all calls as Normal (which is the default)
59 | Default::default()
60 | }
61 | }
62 |
63 | #[pallet::call]
64 | impl Pallet {
65 | /// Do some work
66 | ///
67 | /// Parameters:
68 | /// - `useful_amount`: A vector of u64 values that we want to store.
69 | ///
70 | /// Emits `DidWork` event when successful.
71 | #[pallet::weight(MyWeightFunction(10_000_000))]
72 | pub fn do_work(
73 | origin: OriginFor,
74 | useful_amounts: Vec,
75 | ) -> DispatchResultWithPostInfo {
76 | let sender = ensure_signed(origin)?;
77 | UsefulAmounts::::put(useful_amounts);
78 | // Do other important constant-time (O(1)) work
79 |
80 | // Emit event
81 | Self::deposit_event(Event::DidWork(sender));
82 | Ok(().into())
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/not-so-smart-contracts/ton/README.md:
--------------------------------------------------------------------------------
1 | # (Not So) Smart Contracts
2 |
3 | This repository contains examples of common TON smart contract vulnerabilities, featuring code from real smart contracts. Utilize the Not So Smart Contracts to learn about TON vulnerabilities, refer to them during security reviews, and use them as a benchmark for security analysis tools.
4 |
5 | ## Features
6 |
7 | Each _Not So Smart Contract_ consists of a standard set of information:
8 |
9 | - Vulnerability type description
10 | - Attack scenarios to exploit the vulnerability
11 | - Recommendations to eliminate or mitigate the vulnerability
12 | - Real-world contracts exhibiting the flaw
13 | - References to third-party resources providing more information
14 |
15 | ## Vulnerabilities
16 |
17 | | Not So Smart Contract | Description |
18 | | -------------------------------------------------------------- | ----------------------------------------------------------- |
19 | | [Int as boolean](int_as_boolean) | Unexpected result of logical operations on the int type |
20 | | [Fake Jetton contract](fake_jetton_contract) | Any contract can send a `transfer_notification` message |
21 | | [Forward TON without gas check](forward_ton_without_gas_check) | Users can drain TON balance of a contract lacking gas check |
22 |
23 | ## Credits
24 |
25 | These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/).
26 |
27 | If you have any questions, issues, or wish to learn more, join the #ethereum channel on the [Empire Hacking Slack](https://slack.empirehacking.nyc/) or [contact us](https://www.trailofbits.com/contact/) directly.
28 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/ton/forward_ton_without_gas_check/README.md:
--------------------------------------------------------------------------------
1 | # Foward TON without gas check
2 |
3 | TON smart contracts needs to send TON as gas fee along with the Jetton transfers or any other message they send to another contract. If a contract allows its users to specify the amount of TON to be sent with an outgoing message then it must check that the user supplied enough TON with their message to cover for the transaction fee and the forward TON amount.
4 |
5 | If a contract lacks such a gas check then users can specify a higher forward TON amount to drain the TON balance of the smart contract, freezing the smart contract and potentially destroying it.
6 |
7 | ## Example
8 |
9 | The following simplified code highlights the lack of a gas check. The contract implements a `withdraw` operation that allows users to specify a forward TON amount and a forward payload to send with the Jettons. However, the contract does not check if the user included enough TON with the `withdraw` message to cover the `withdraw` message transaction, the Jetton transfer gas fee, and the forward TON amount. This allows users to drain the TON balance of the smart contract.
10 |
11 | ```FunC
12 | #include "imports/stdlib.fc";
13 |
14 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
15 | slice cs = in_msg_full.begin_parse();
16 | int flags = cs~load_uint(4);
17 | ;; ignore all bounced messages
18 | if (is_bounced?(flags)) {
19 | return ();
20 | }
21 | slice sender_address = cs~load_msg_addr(); ;; incorrectly assumed to be Jetton wallet contract owned by this contract
22 |
23 | (int op, int query_id) = in_msg_body~load_op_and_query_id();
24 |
25 | if (op == op::withdraw) {
26 | int amount = in_msg_body~load_coins();
27 | slice to_address = in_msg_body~load_msg_addr();
28 | int forward_ton_amount = in_msg_body~load_coins(); ;; user specified forward TON amount
29 | cell forward_payload = begin_cell().store_slice(in_msg_body).end_cell();
30 |
31 | var msg_body = begin_cell()
32 | .store_op_and_query_id(op::transfer, query_id)
33 | .store_coins(amount)
34 | .store_slice(to_address)
35 | .store_slice(to_address)
36 | .store_uint(0, 1)
37 | .store_coins(forward_ton_amount)
38 | .store_maybe_ref(forward_payload)
39 | .end_cell();
40 |
41 | cell msg = begin_cell()
42 | .store_uint(0x18, 6)
43 | .store_slice(USDT_WALLET_ADDRESS)
44 | .store_coins(JETTON_TRANSFER_GAS + forward_ton_amount) ;; sending user specified forward TON amount
45 | .store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1) ;; message parameters
46 | .store_ref(ref)
47 | .end_cell();
48 |
49 | send_raw_message(msg, 0);
50 |
51 | return ();
52 | }
53 | }
54 | ```
55 |
56 | ## Mitigations
57 |
58 | - Avoid allowing users to specify forward TON amount with the outgoing messages.
59 | - Always check that the user sent enough TON in the `msg_value` to cover for the current transaction fee and sum of all the outgoing message values.
60 |
--------------------------------------------------------------------------------
/not-so-smart-contracts/ton/int_as_boolean/README.md:
--------------------------------------------------------------------------------
1 | # Using int as boolean values
2 |
3 | In FunC, booleans are represented as integers; false is represented as 0 and true is represented as -1 (257 ones in binary notation).
4 |
5 | Logical operations are done as bitwise operations over the binary representation of the integer values. Notably, The not operation `~` flips all the bits of an integer value; therefore, a non-zero value other than -1 becomes another non-zero value.
6 |
7 | When a condition is checked, every non-zero integer is considered a true value. This, combined with the logical operations being bitwise operations, leads to an unexpected behavior of `if` conditions using the logical operations.
8 |
9 | ## Example
10 |
11 | The following simplified code highlights the unexpected behavior of the `~` operator on a non-zero interger value.
12 |
13 | ```FunC
14 | #include "imports/stdlib.fc";
15 |
16 | () recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
17 | int correct_true = -1;
18 | if (correct_true) {
19 | ~strdump("correct_true is true"); ;; printed
20 | } else {
21 | ~strdump("correct_true is false");
22 | }
23 |
24 | if (~ correct_true) {
25 | ~strdump("~correct_true is true");
26 | } else {
27 | ~strdump("~correct_true is false"); ;; printed
28 | }
29 |
30 | int correct_false = 0;
31 | if (correct_false) {
32 | ~strdump("correct_false is true");
33 | } else {
34 | ~strdump("correct_false is false"); ;; printed
35 | }
36 |
37 | if (~ correct_false) {
38 | ~strdump("~correct_false is true"); ;; printed
39 | } else {
40 | ~strdump("~correct_false is false");
41 | }
42 |
43 | int positive = 10;
44 | if (positive) {
45 | ~strdump("positive is true"); ;; printed
46 | } else {
47 | ~strdump("positive is false");
48 | }
49 |
50 | if (~ positive) {
51 | ~strdump("~positive is true"); ;; printed but unexpected
52 | } else {
53 | ~strdump("~positive is false");
54 | }
55 |
56 | int negative = -10;
57 | if (negative) {
58 | ~strdump("negative is true"); ;; printed
59 | } else {
60 | ~strdump("negative is false");
61 | }
62 |
63 | if (~ negative) {
64 | ~strdump("~negative is true"); ;; printed but unexpected
65 | } else {
66 | ~strdump("~negative is false");
67 | }
68 | }
69 | ```
70 |
71 | The `recv_internal` function above prints the following debug logs:
72 |
73 | ```
74 | #DEBUG#: correct_true is true
75 | #DEBUG#: ~correct_true is false
76 | #DEBUG#: correct_false is false
77 | #DEBUG#: ~correct_false is true
78 | #DEBUG#: positive is true
79 | #DEBUG#: ~positive is true
80 | #DEBUG#: negative is true
81 | #DEBUG#: ~negative is true
82 | ```
83 |
84 | It demonstrats that the `~ 10` and `~ -10` both evaluate to `true` instead of becoming `false` with the `~` operator.
85 |
86 | ## Mitigations
87 |
88 | - Always use `0` or `-1` in condition checks to get correct results.
89 | - Be careful with the logical operator usage on non-zero integer values.
90 | - Implement test cases to verify correct behavior of all condition checks with different integer values.
91 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "building-secure-contracts",
3 | "version": "1.0.0",
4 | "repository": "https://github.com/crytic/building-secure-contracts",
5 | "author": "crytic",
6 | "dependencies": {
7 | "markdown-link-check": "^3.10.3",
8 | "prettier": "^2.8.4",
9 | "prettier-plugin-solidity": "^1.1.3"
10 | },
11 | "scripts": {
12 | "format": "prettier --write . && npm run format:embedded",
13 | "format:embedded": "prettier --write \"**/*.md\" --embedded-language-formatting=auto --plugin prettier-plugin-solidity --tab-width 4 --print-width 120 && prettier --write \"**/*.md\"",
14 | "lint": "npm run lint:format && npm run lint:links",
15 | "lint:format": "prettier --check .",
16 | "lint:links": "find . -name '*.md' -print0 | xargs -0 -n1 markdown-link-check"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/program-analysis/echidna/README.md:
--------------------------------------------------------------------------------
1 | # Echidna Tutorial
2 |
3 | The objective of this tutorial is to demonstrate how to use Echidna to automatically test smart contracts.
4 |
5 | To learn through live coding sessions, watch our [Fuzzing workshop](https://www.youtube.com/watch?v=QofNQxW_K08&list=PLciHOL_J7Iwqdja9UH4ZzE8dP1IxtsBXI).
6 |
7 | **Table of Contents:**
8 |
9 | - [Introduction](introduction): Introductory material on fuzzing and Echidna
10 | - [Basic](basic): Learn the first steps for using Echidna
11 | - [Advanced](advanced): Explore the advanced features of Echidna
12 | - [Fuzzing tips](./fuzzing_tips.md): General fuzzing recommendations
13 | - [Frequently Asked Questions](./frequently_asked_questions.md): Responses to common questions about Echidna
14 | - [Configuration options](./configuration.md): Description of all Echidna configuration options
15 | - [Exercises](exercises): Practical exercises to enhance your understanding
16 |
17 | Join the team on Slack at: https://slack.empirehacking.nyc/ #ethereum.
18 |
19 | If you are looking for help to build fuzzing capabilities for your team, check out our [invariant development as a service](https://www.trailofbits.com/services/software-assurance/#blockchain-invariant-development).
20 |
--------------------------------------------------------------------------------
/program-analysis/echidna/advanced/README.md:
--------------------------------------------------------------------------------
1 | # Advanced
2 |
3 | - [How to Collect a Corpus](./collecting-a-corpus.md): Learn how to use Echidna to gather a corpus of transactions.
4 | - [How to Use Optimization Mode](./optimization_mode.md): Discover how to optimize a function using Echidna.
5 | - [How to Detect High Gas Consumption](./finding-transactions-with-high-gas-consumption.md): Find out how to identify functions with high gas consumption.
6 | - [How to Perform Large-scale Smart Contract Fuzzing](./smart-contract-fuzzing-at-scale.md): Explore how to use Echidna for long fuzzing campaigns on complex smart contracts.
7 | - [How to Test a Library](https://blog.trailofbits.com/2020/08/17/using-echidna-to-test-a-smart-contract-library/): Learn about using Echidna to test the Set Protocol library (blog post).
8 | - [How to Test Bytecode-only Contracts](./testing-bytecode.md): Learn how to fuzz contracts without source code or perform differential fuzzing between Solidity and Vyper.
9 | - [How and when to use cheat codes](./on-using-cheat-codes.md): How to use hevm cheat codes in general
10 | - [How to Use Hevm Cheats to Test Permit](./hevm-cheats-to-test-permit.md): Find out how to test code that relies on ecrecover signatures by using hevm cheat codes.
11 | - [How to Seed Echidna with Unit Tests](./end-to-end-testing.md): Discover how to use existing unit tests to seed Echidna.
12 | - [Understanding and Using `allContracts`](./using-all-contracts.md): Learn what `allContracts` testing is and how to utilize it effectively.
13 | - [How to do on-chain fuzzing with state forking](./state-network-forking.md): How Echidna can use the state of blockchain during a fuzzing campaign
14 | - [Interacting with off-chain data via FFI cheatcode](./interacting-with-offchain-data-via-ffi.md): Using the `ffi` cheatcode as a way of communicating with the operating system
15 |
--------------------------------------------------------------------------------
/program-analysis/echidna/advanced/hevm-cheats-to-test-permit.md:
--------------------------------------------------------------------------------
1 | # Using HEVM Cheats To Test Permit
2 |
3 | ## Introduction
4 |
5 | [EIP 2612](https://eips.ethereum.org/EIPS/eip-2612) introduces the function `permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)` to the ERC20 ABI. This function takes in signature parameters generated through ECDSA, combined with the [EIP 712](https://eips.ethereum.org/EIPS/eip-712) standard for typed data hashing, and recovers the author of the signature through `ecrecover()`. It then sets `allowances[owner][spender]` to `value`.
6 |
7 | ## Uses
8 |
9 | This method presents a new way of allocating allowances, as signatures can be computed off-chain and passed to a contract. It allows a relayer to pay the entire gas fee of the permit transaction in exchange for a fee, enabling completely gasless transactions for a user. Furthermore, this removes the typical `approve() -> transferFrom()` pattern that forces users to send two transactions instead of just one through this new method.
10 |
11 | Note that for the permit function to work, a valid signature is needed. This example will demonstrate how we can use [`hevm`'s `sign` cheatcode](https://hevm.dev/std-test-tutorial.html#supported-cheat-codes) to sign data with a private key. More generally, you can use this cheatcode to test anything that requires valid signatures.
12 |
13 | ## Example
14 |
15 | We use Solmate’s implementation of the ERC20 standard that includes the permit function. Observe that there are also values for the `PERMIT_TYPEHASH` and a `mapping(address -> uint256) public nonces`. The former is part of the EIP712 standard, and the latter is used to prevent signature replay attacks.
16 |
17 | In our `TestDepositWithPermit` contract, we need to have the signature signed by an owner for validation. To accomplish this, we can use `hevm`’s `sign` cheatcode, which takes in a message and a private key and creates a valid signature. For this example, we use the private key `0x02`, and the following signed message representing the permit signature following the EIP 712:
18 |
19 | ```solidity
20 | keccak256(
21 | abi.encodePacked(
22 | "\x19\x01",
23 | asset.DOMAIN_SEPARATOR(),
24 | keccak256(
25 | abi.encode(
26 | keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
27 | owner,
28 | spender,
29 | assetAmount,
30 | asset.nonces(owner),
31 | block.timestamp
32 | )
33 | )
34 | )
35 | );
36 | ```
37 |
38 | The helper function `getSignature(address owner, address spender, uint256 assetAmount)` returns a valid signature generated via the `sign` cheatcode. Note that the sign cheatcode exposes the private key, so it is best to use dummy keys when testing. Our keypair data was taken from [this site](https://privatekeys.pw/keys/ethereum/1). To test the signature, we will mint a random amount to the `OWNER` address, the address corresponding to the private key `0x02`, which was the signer of the permit signature. We then check whether we can use that signature to transfer the owner’s tokens to ourselves.
39 |
40 | First, we call `permit()` on our Mock ERC20 token with the signature generated in `getSignature()`, and then call `transferFrom()`. If our permit request and transfer are successful, our balance of the mock ERC20 should increase by the amount permitted, and the `OWNER`'s balance should decrease as well. For simplicity, we'll transfer all the minted tokens so that the `OWNER`'s balance will be `0`, and our balance will be `amount`.
41 |
42 | ## Code
43 |
44 | The complete example code can be found [here](https://github.com/crytic/building-secure-contracts/blob/master/program-analysis/echidna/example/TestDepositWithPermit.sol).
45 |
--------------------------------------------------------------------------------
/program-analysis/echidna/advanced/on-using-cheat-codes.md:
--------------------------------------------------------------------------------
1 | # How and when to use cheat codes
2 |
3 | **Table of contents:**
4 |
5 | - [How and when to use cheat codes](#how-and-when-to-use-cheat-codes)
6 | - [Introduction](#introduction)
7 | - [Cheat codes available in Echidna](#cheat-codes-available-in-echidna)
8 | - [Risks of cheat codes](#risks-of-cheat-codes)
9 |
10 | ## Introduction
11 |
12 | When testing smart contracts in Solidity itself, it can be helpful to use cheat codes in order to overcome some of the limitations of the EVM/Solidity.
13 | Cheat codes are special functions that allow to change the state of the EVM in ways that are not posible in production. These were introduced by Dapptools in hevm and adopted (and expanded) in other projects such as Foundry.
14 |
15 | ## Cheat codes available in Echidna
16 |
17 | Echidna supports all cheat codes that are available in [hevm](https://github.com/ethereum/hevm). These are documented here: [https://hevm.dev/controlling-the-unit-testing-environment.html#cheat-codes](https://hevm.dev/std-test-tutorial.html#supported-cheat-codes).
18 | If a new cheat code is added in the future, Echidna only needs to update the hevm version and everything should work out of the box.
19 |
20 | As an example, the `prank` cheat code is able to set the `msg.sender` address in the context of the next external call:
21 |
22 | ```solidity
23 | interface IHevm {
24 | function prank(address) external;
25 | }
26 |
27 | contract TestPrank {
28 | address constant HEVM_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
29 | IHevm hevm = IHevm(HEVM_ADDRESS);
30 | Contract c = ...
31 |
32 | function prankContract() public payable {
33 | hevm.prank(address(0x42424242);
34 | c.f(); // `c` will be called with `msg.sender = 0x42424242`
35 | }
36 | }
37 | ```
38 |
39 | A specific example on the use of `sign` cheat code is available [here in our documentation](hevm-cheats-to-test-permit.md).
40 |
41 | ## Risks of cheat codes
42 |
43 | While we provide support for the use of cheat codes, these should be used responsibly. Consider that:
44 |
45 | - Cheat codes can break certain assumptions in Solidity. For example, the compiler assumes that `block.number` is constant during a transaction. There are [reports of the optimizer interfering with (re)computation of the `block.number` or `block.timestamp`](https://github.com/ethereum/solidity/issues/12963#issuecomment-1110162425), which can generate incorrect tests when using cheat codes.
46 |
47 | - Cheat codes can introduce false positives on the testing. For instance, using `prank` to simulate calls from a contract can allow transactions that are not possible in the blockchain.
48 |
49 | - Using too many cheat codes:
50 | - can be confusing or error-prone. Certain cheat code like `prank` allow to change caller in the next external call: It can be difficult to follow, in particular if it is used in internal functions or modifiers.
51 | - will create a dependency of your code with the particular tool or cheat code implementation: It can cause produce migrations to other tools or reusing the test code to be more difficult than expected.
52 |
--------------------------------------------------------------------------------
/program-analysis/echidna/advanced/working-with-libraries.md:
--------------------------------------------------------------------------------
1 | # Working with external libraries
2 |
3 | **Table of contents:**
4 |
5 | - [Introduction](#introduction)
6 | - [Example code](#example-code)
7 | - [Deploying libraries](#deploying-libraries)
8 | - [Linking libraries](#linking-libraries)
9 | - [Summary](#summary)
10 |
11 | ## Introduction
12 |
13 | Solidity support two types of libraries ([see the documentation](https://docs.soliditylang.org/en/v0.8.19/contracts.html#libraries)):
14 |
15 | - If all the functions are internal, the library is compiled into bytecode and added into the contracts that use it.
16 | - If there are some external functions, the library should be deployed into some address. Finally, the bytecode calling the library should be linked.
17 |
18 | The following is only needed if your codebase uses libraries that need to be linked.
19 |
20 | ## Example code
21 |
22 | For this tutorial, we will use [the metacoin example](https://github.com/truffle-box/metacoin-box). Let's start compiling it:
23 |
24 | ```
25 | $ git clone https://github.com/truffle-box/metacoin-box
26 | $ cd metacoin-box
27 | $ npm i
28 | ```
29 |
30 | ## Deploying libraries
31 |
32 | Libraries are contracts that need to be deployed first. Fortunately, Echidna allows us to do that easily, using the `deployContracts` option. In the metacoin example, we can use:
33 |
34 | ```yaml
35 | deployContracts: [["0x1f", "ConvertLib"]]
36 | ```
37 |
38 | The address where the library should be deployed is arbitrary, but it should be the same as the one in the used during the linking process.
39 |
40 | ## Linking libraries
41 |
42 | Before a contract can use a deployed library, its bytecode requires to be linked (e.g set the address that points to the deployed library contract). Normally, a compilation framework (e.g. truffle) will take care of this. However, in our case, we will use `crytic-compile`, since it is easier to handle all cases from different frameworks just adding one new argument to pass to `crytic-compile` from Echidna:
43 |
44 | ```yaml
45 | cryticArgs: ["--compile-libraries=(ConvertLib,0x1f)"]
46 | ```
47 |
48 | Going back to the example, if we have both config options in a single config file (`echidna.yaml`), we can run the metacoin contract
49 | in `exploration` mode:
50 |
51 | ```
52 | $ echidna . --test-mode exploration --corpus-dir corpus --contract MetaCoin --config echidna.yaml
53 | ```
54 |
55 | We can use the coverage report to verify that function using the library (`getBalanceInEth`) is not reverting:
56 |
57 | ```
58 | 28 | * | function getBalanceInEth(address addr) public view returns(uint){
59 | 29 | * | return ConvertLib.convert(getBalance(addr),2);
60 | 30 | | }
61 | ```
62 |
63 | ## Summary
64 |
65 | Working with libraries in Echidna is supported. It involves to deploy the library to a particular address using `deployContracts` and then asking `crytic-compile` to link the bytecode with the same address using `--compile-libraries` command line.
66 |
--------------------------------------------------------------------------------
/program-analysis/echidna/basic/README.md:
--------------------------------------------------------------------------------
1 | # Basic
2 |
3 | - [How to Choose the Most Suitable Testing Mode](./testing-modes.md): Selecting the Most Appropriate Testing Mode
4 | - [How to Determine the Best Testing Approach](./common-testing-approaches.md): Deciding on the Optimal Testing Method
5 | - [How to Filter Functions](./filtering-functions.md): Filtering the Functions to be Fuzzed
6 | - [How to Test Assertions Effectively](./assertion-checking.md): Efficiently Testing Assertions with Echidna
7 | - [How to write properties that use ether](./working-with-eth.md): Fuzzing ether during fuzzing campaigns
8 | - [How to Write Good Properties Step by Step](./property-creation.md): Improving Property Testing through Iteration
9 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/MockERC20Permit.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./ERC20Permit.sol";
5 |
6 | contract MockERC20Permit is ERC20Permit {
7 | constructor(string memory _name, string memory _symbol, uint8 _decimals) ERC20Permit(_name, _symbol, _decimals) {}
8 |
9 | function mint(address _to, uint256 _value) external {
10 | _mint(_to, _value);
11 | }
12 |
13 | function burn(address _from, uint256 _value) external {
14 | _burn(_from, _value);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/Popsicle.yaml:
--------------------------------------------------------------------------------
1 | testMode: assertion
2 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/TestDepositWithPermit.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./MockERC20Permit.sol";
5 |
6 | interface iHevm {
7 | //signs digest with private key sk
8 | function sign(uint256 sk, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);
9 | }
10 |
11 | contract TestDepositWithPermit {
12 | MockERC20Permit asset;
13 | iHevm hevm;
14 |
15 | event AssertionFailed(string reason);
16 | event LogBalance(uint256 balanceOwner, uint256 balanceCaller);
17 |
18 | address constant OWNER = 0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF; //address corresponding to private key 0x2
19 |
20 | constructor() {
21 | hevm = iHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
22 | asset = new MockERC20Permit("Permit Token", "PMT", 18);
23 | }
24 |
25 | //helper method to get signature, signs with private key 2
26 | function getSignature(
27 | address owner,
28 | address spender,
29 | uint256 assetAmount
30 | ) internal returns (uint8 v, bytes32 r, bytes32 s) {
31 | bytes32 digest = keccak256(
32 | abi.encodePacked(
33 | "\x19\x01",
34 | asset.DOMAIN_SEPARATOR(),
35 | keccak256(
36 | abi.encode(
37 | keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
38 | owner,
39 | spender,
40 | assetAmount,
41 | asset.nonces(owner),
42 | block.timestamp
43 | )
44 | )
45 | )
46 | );
47 | (v, r, s) = hevm.sign(2, digest); //this gives us OWNER's signature
48 | }
49 |
50 | function testERC20PermitDeposit(uint256 amount) public {
51 | amount = 1 + (amount % 1000000e18); // we'll only consider transfers of up to 1M tokens
52 | asset.mint(OWNER, amount);
53 |
54 | uint256 previousOwnerBalance = asset.balanceOf(OWNER);
55 | uint256 previousCallerBalance = asset.balanceOf(address(this));
56 |
57 | emit LogBalance(previousOwnerBalance, previousCallerBalance);
58 | (uint8 v, bytes32 r, bytes32 s) = getSignature(OWNER, address(this), amount);
59 | try asset.permit(OWNER, address(this), amount, block.timestamp, v, r, s) {} catch {
60 | emit AssertionFailed("signature is invalid");
61 | }
62 | try asset.transferFrom(OWNER, address(this), amount) {} catch {
63 | emit AssertionFailed("transferFrom reverted");
64 | }
65 | uint256 currentOwnerBalance = asset.balanceOf(OWNER);
66 | uint256 currentCallerBalance = asset.balanceOf(address(this));
67 | emit LogBalance(currentOwnerBalance, currentCallerBalance);
68 | if (currentCallerBalance != previousCallerBalance + amount && currentOwnerBalance != 0) {
69 | emit AssertionFailed("incorrect amount transferred");
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/allContracts.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract Flag {
5 | bool flag = false;
6 |
7 | function flip() public {
8 | flag = !flag;
9 | }
10 |
11 | function get() public view returns (bool) {
12 | return flag;
13 | }
14 |
15 | function test_fail() public pure {
16 | assert(false);
17 | }
18 | }
19 |
20 | contract EchidnaTest {
21 | Flag f;
22 |
23 | constructor() {
24 | f = new Flag();
25 | }
26 |
27 | function test_flag_is_false() public view {
28 | assert(f.get() == false);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/allContracts.yaml:
--------------------------------------------------------------------------------
1 | testMode: assertion
2 | testLimit: 50000
3 | # echidna < 2.1
4 | multi-abi: true
5 | # echidna >= 2.1
6 | allContracts: true
7 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/assert.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract Incrementor {
5 | uint256 private counter = 2 ** 200;
6 |
7 | function inc(uint256 val) public returns (uint256) {
8 | uint256 tmp = counter;
9 | unchecked {
10 | counter += val;
11 | }
12 | assert(tmp <= counter);
13 | return (counter - tmp);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/assert.yaml:
--------------------------------------------------------------------------------
1 | testMode: assertion
2 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/blacklistpushpop.yaml:
--------------------------------------------------------------------------------
1 | estimateGas: true
2 | filterBlacklist: true
3 | filterFunctions: ["C.pop()", "C.clear()"]
4 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/filter.yaml:
--------------------------------------------------------------------------------
1 | testLimit: 1000000 # increased test limit as a workaround for this test taking longer than expected
2 | filterBlacklist: true
3 | filterFunctions: ["C.reset1()", "C.reset2()"]
4 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/gas.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract C {
5 | uint256 state;
6 |
7 | function expensive(uint8 times) internal {
8 | for (uint8 i = 0; i < times; i++) {
9 | state = state + i;
10 | }
11 | }
12 |
13 | function f(uint256 x, uint256 y, uint8 times) public {
14 | if (x == 42 && y == 123) expensive(times);
15 | else state = 0;
16 | }
17 |
18 | function echidna_test() public pure returns (bool) {
19 | return true;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/gas.yaml:
--------------------------------------------------------------------------------
1 | seqLen: 2
2 | estimateGas: true
3 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/magic.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract C {
5 | bool value_found = false;
6 |
7 | function magic(uint256 magic_1, uint256 magic_2, uint256 magic_3, uint256 magic_4) public {
8 | require(magic_1 == 42);
9 | require(magic_2 == 129);
10 | require(magic_3 == magic_4 + 333);
11 | value_found = true;
12 | return;
13 | }
14 |
15 | function echidna_magic_values() public view returns (bool) {
16 | return !value_found;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/medusa.json:
--------------------------------------------------------------------------------
1 | {
2 | "fuzzing": {
3 | "workers": 10,
4 | "workerResetLimit": 50,
5 | "timeout": 0,
6 | "testLimit": 0,
7 | "shrinkLimit": 5000,
8 | "callSequenceLength": 100,
9 | "corpusDirectory": "",
10 | "coverageEnabled": true,
11 | "coverageFormats": ["html", "lcov"],
12 | "targetContracts": [],
13 | "predeployedContracts": {},
14 | "targetContractsBalances": [],
15 | "constructorArgs": {},
16 | "deployerAddress": "0x30000",
17 | "senderAddresses": ["0x10000", "0x20000", "0x30000"],
18 | "blockNumberDelayMax": 60480,
19 | "blockTimestampDelayMax": 604800,
20 | "blockGasLimit": 125000000,
21 | "transactionGasLimit": 12500000,
22 | "testing": {
23 | "stopOnFailedTest": true,
24 | "stopOnFailedContractMatching": false,
25 | "stopOnNoTests": true,
26 | "testAllContracts": false,
27 | "traceAll": false,
28 | "assertionTesting": {
29 | "enabled": true,
30 | "testViewMethods": false,
31 | "panicCodeConfig": {
32 | "failOnCompilerInsertedPanic": false,
33 | "failOnAssertion": true,
34 | "failOnArithmeticUnderflow": false,
35 | "failOnDivideByZero": false,
36 | "failOnEnumTypeConversionOutOfBounds": false,
37 | "failOnIncorrectStorageAccess": false,
38 | "failOnPopEmptyArray": false,
39 | "failOnOutOfBoundsArrayAccess": false,
40 | "failOnAllocateTooMuchMemory": false,
41 | "failOnCallUninitializedVariable": false
42 | }
43 | },
44 | "propertyTesting": {
45 | "enabled": true,
46 | "testPrefixes": ["echidna_"]
47 | },
48 | "optimizationTesting": {
49 | "enabled": true,
50 | "testPrefixes": ["optimize_"]
51 | },
52 | "targetFunctionSignatures": [],
53 | "excludeFunctionSignatures": []
54 | },
55 | "chainConfig": {
56 | "codeSizeCheckDisabled": true,
57 | "cheatCodes": {
58 | "cheatCodesEnabled": true,
59 | "enableFFI": false
60 | },
61 | "skipAccountChecks": true
62 | }
63 | },
64 | "compilation": {
65 | "platform": "crytic-compile",
66 | "platformConfig": {
67 | "target": ".",
68 | "solcVersion": "",
69 | "exportDirectory": "",
70 | "args": []
71 | }
72 | },
73 | "logging": {
74 | "level": "info",
75 | "logDirectory": "",
76 | "noColor": false
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/multi.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract C {
5 | bool state1 = false;
6 | bool state2 = false;
7 | bool state3 = false;
8 | bool state4 = false;
9 |
10 | function f(uint256 x) public {
11 | require(x == 12);
12 | state1 = true;
13 | }
14 |
15 | function g(uint256 x) public {
16 | require(state1);
17 | require(x == 8);
18 | state2 = true;
19 | }
20 |
21 | function h(uint256 x) public {
22 | require(state2);
23 | require(x == 42);
24 | state3 = true;
25 | }
26 |
27 | function i() public {
28 | require(state3);
29 | state4 = true;
30 | }
31 |
32 | function reset1() public {
33 | state1 = false;
34 | state2 = false;
35 | state3 = false;
36 | return;
37 | }
38 |
39 | function reset2() public {
40 | state1 = false;
41 | state2 = false;
42 | state3 = false;
43 | return;
44 | }
45 |
46 | function echidna_state4() public view returns (bool) {
47 | return (!state4);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/opt.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract TestDutchAuctionOptimization {
5 | int256 maxPriceDifference;
6 |
7 | function setMaxPriceDifference(uint256 startPrice, uint256 endPrice, uint256 startTime, uint256 endTime) public {
8 | if (endTime < (startTime + 900)) {
9 | revert();
10 | }
11 | if (startPrice <= endPrice) {
12 | revert();
13 | }
14 | uint256 numerator = (startPrice - endPrice) * (block.timestamp - startTime);
15 | uint256 denominator = endTime - startTime;
16 | uint256 stepDecrease = numerator / denominator;
17 | uint256 currentAuctionPrice = startPrice - stepDecrease;
18 | if (currentAuctionPrice < endPrice) {
19 | maxPriceDifference = int256(endPrice - currentAuctionPrice);
20 | }
21 | if (currentAuctionPrice > startPrice) {
22 | maxPriceDifference = int256(currentAuctionPrice - startPrice);
23 | }
24 | }
25 |
26 | function echidna_opt_price_difference() public view returns (int256) {
27 | return maxPriceDifference;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/pushpop.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract C {
5 | address[] addrs;
6 |
7 | function push(address a) public {
8 | addrs.push(a);
9 | }
10 |
11 | function pop() public {
12 | addrs.pop();
13 | }
14 |
15 | function clear() public {
16 | addrs.length = 0;
17 | }
18 |
19 | function check() public {
20 | for (uint256 i = 0; i < addrs.length; i++) {
21 | for (uint256 j = i + 1; j < addrs.length; j++) {
22 | if (addrs[i] == addrs[j]) addrs[j] = address(0);
23 | }
24 | }
25 | }
26 |
27 | function echidna_test() public pure returns (bool) {
28 | return true;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/pushpop.yaml:
--------------------------------------------------------------------------------
1 | estimateGas: true
2 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/testdeposit.yaml:
--------------------------------------------------------------------------------
1 | testMode: assertion
2 | testLimit: 5000
3 | shrinkLimit: 1
4 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/testtoken.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./token.sol";
5 |
6 | contract TestToken is Token {
7 | function echidna_balance_under_1000() public view returns (bool) {
8 | return balances[msg.sender] <= 1000;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/program-analysis/echidna/example/token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract Token {
5 | mapping(address => uint256) public balances;
6 |
7 | function airdrop() public {
8 | balances[msg.sender] = 1000;
9 | }
10 |
11 | function consume() public {
12 | require(balances[msg.sender] > 0);
13 | balances[msg.sender] -= 1;
14 | }
15 |
16 | function backdoor() public {
17 | balances[msg.sender] += 1;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/Exercise-1.md:
--------------------------------------------------------------------------------
1 | # Exercise 1
2 |
3 | **Table of Contents:**
4 |
5 | - [Exercise 1](#exercise-1)
6 | - [Targeted Contract](#targeted-contract)
7 | - [Testing a Token Balance](#testing-a-token-balance)
8 | - [Goals](#goals)
9 | - [Solution](#solution)
10 |
11 | Join the team on Slack at: https://slack.empirehacking.nyc/ #ethereum
12 |
13 | ## Targeted Contract
14 |
15 | We will test the following contract _[token.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise1/token.sol)_:
16 |
17 | ```solidity
18 | pragma solidity ^0.8.0;
19 |
20 | contract Ownable {
21 | address public owner = msg.sender;
22 |
23 | modifier onlyOwner() {
24 | require(msg.sender == owner, "Ownable: Caller is not the owner.");
25 | _;
26 | }
27 | }
28 |
29 | contract Pausable is Ownable {
30 | bool private _paused;
31 |
32 | function paused() public view returns (bool) {
33 | return _paused;
34 | }
35 |
36 | function pause() public onlyOwner {
37 | _paused = true;
38 | }
39 |
40 | function resume() public onlyOwner {
41 | _paused = false;
42 | }
43 |
44 | modifier whenNotPaused() {
45 | require(!_paused, "Pausable: Contract is paused.");
46 | _;
47 | }
48 | }
49 |
50 | contract Token is Ownable, Pausable {
51 | mapping(address => uint256) public balances;
52 |
53 | function transfer(address to, uint256 value) public whenNotPaused {
54 | // unchecked to save gas
55 | unchecked {
56 | balances[msg.sender] -= value;
57 | balances[to] += value;
58 | }
59 | }
60 | }
61 | ```
62 |
63 | ## Testing a Token Balance
64 |
65 | ### Goals
66 |
67 | - Add a property to check that the address `echidna` cannot have more than an initial balance of 10,000.
68 | - After Echidna finds the bug, fix the issue, and re-check your property with Echidna.
69 |
70 | The skeleton for this exercise is (_[template.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise1/template.sol)_):
71 |
72 | ````solidity
73 | pragma solidity ^0.8.0;
74 |
75 | import "./token.sol";
76 |
77 | /// @dev Run the template with
78 | /// ```
79 | /// solc-select use 0.8.0
80 | /// echidna program-analysis/echidna/exercises/exercise1/template.sol
81 | /// ```
82 | contract TestToken is Token {
83 | address echidna = tx.origin;
84 |
85 | constructor() {
86 | balances[echidna] = 10_000;
87 | }
88 |
89 | function echidna_test_balance() public view returns (bool) {
90 | // TODO: add the property
91 | }
92 | }
93 | ````
94 |
95 | ## Solution
96 |
97 | This solution can be found in [solution.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise1/solution.sol).
98 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/Exercise-2.md:
--------------------------------------------------------------------------------
1 | # Exercise 2
2 |
3 | This exercise requires completing [exercise 1](Exercise-1.md).
4 |
5 | **Table of contents:**
6 |
7 | - [Exercise 2](#exercise-2)
8 | - [Targeted contract](#targeted-contract)
9 | - [Testing access control](#testing-access-control)
10 | - [Goals](#goals)
11 | - [Solution](#solution)
12 |
13 | Join the team on Slack at: https://slack.empirehacking.nyc/ #ethereum
14 |
15 | ## Targeted contract
16 |
17 | We will test the following contract, _[token.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise2/token.sol)_:
18 |
19 | ```solidity
20 | pragma solidity ^0.8.0;
21 |
22 | contract Ownable {
23 | address public owner = msg.sender;
24 |
25 | function Owner() public {
26 | owner = msg.sender;
27 | }
28 |
29 | modifier onlyOwner() {
30 | require(owner == msg.sender);
31 | _;
32 | }
33 | }
34 |
35 | contract Pausable is Ownable {
36 | bool private _paused;
37 |
38 | function paused() public view returns (bool) {
39 | return _paused;
40 | }
41 |
42 | function pause() public onlyOwner {
43 | _paused = true;
44 | }
45 |
46 | function resume() public onlyOwner {
47 | _paused = false;
48 | }
49 |
50 | modifier whenNotPaused() {
51 | require(!_paused, "Pausable: Contract is paused.");
52 | _;
53 | }
54 | }
55 |
56 | contract Token is Ownable, Pausable {
57 | mapping(address => uint256) public balances;
58 |
59 | function transfer(address to, uint256 value) public whenNotPaused {
60 | balances[msg.sender] -= value;
61 | balances[to] += value;
62 | }
63 | }
64 | ```
65 |
66 | ## Testing access control
67 |
68 | ### Goals
69 |
70 | - Assume `pause()` is called at deployment, and the ownership is removed.
71 | - Add a property to check that the contract cannot be unpaused.
72 | - When Echidna finds the bug, fix the issue and retry your property with Echidna.
73 |
74 | The skeleton for this exercise is (_[template.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise2/template.sol)_):
75 |
76 | ````solidity
77 | pragma solidity ^0.8.0;
78 |
79 | import "./token.sol";
80 |
81 | /// @dev Run the template with
82 | /// ```
83 | /// solc-select use 0.8.0
84 | /// echidna program-analysis/echidna/exercises/exercise2/template.sol
85 | /// ```
86 | contract TestToken is Token {
87 | constructor() {
88 | pause(); // pause the contract
89 | owner = address(0); // lose ownership
90 | }
91 |
92 | function echidna_cannot_be_unpause() public view returns (bool) {
93 | // TODO: add the property
94 | }
95 | }
96 | ````
97 |
98 | ## Solution
99 |
100 | The solution can be found in [solution.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise2/solution.sol).
101 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/Exercise-4.md:
--------------------------------------------------------------------------------
1 | # Exercise 4
2 |
3 | **Table of contents:**
4 |
5 | - [Exercise 4](#exercise-4)
6 | - [Targeted contract](#targeted-contract)
7 | - [Exercise](#exercise)
8 | - [Goals](#goals)
9 | - [Solution](#solution)
10 |
11 | This exercise is based on the tutorial [How to test assertions](../basic/assertion-checking.md).
12 |
13 | Join the team on Slack at: https://slack.empirehacking.nyc/ #ethereum
14 |
15 | ## Targeted contract
16 |
17 | We will test the following contract, _[token.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise4/token.sol)_:
18 |
19 | ```solidity
20 | pragma solidity ^0.8.0;
21 |
22 | contract Ownable {
23 | address public owner = msg.sender;
24 |
25 | function transferOwnership(address newOwner) public onlyOwner {
26 | owner = newOwner;
27 | }
28 |
29 | modifier onlyOwner() {
30 | require(msg.sender == owner, "Ownable: Caller is not the owner.");
31 | _;
32 | }
33 | }
34 |
35 | contract Pausable is Ownable {
36 | bool private _paused;
37 |
38 | function paused() public view returns (bool) {
39 | return _paused;
40 | }
41 |
42 | function pause() public onlyOwner {
43 | _paused = true;
44 | }
45 |
46 | function resume() public onlyOwner {
47 | _paused = false;
48 | }
49 |
50 | modifier whenNotPaused() {
51 | require(!_paused, "Pausable: Contract is paused.");
52 | _;
53 | }
54 | }
55 |
56 | contract Token is Ownable, Pausable {
57 | mapping(address => uint256) public balances;
58 |
59 | function transfer(address to, uint256 value) public virtual whenNotPaused {
60 | // unchecked to save gas
61 | unchecked {
62 | balances[msg.sender] -= value;
63 | balances[to] += value;
64 | }
65 | }
66 | }
67 | ```
68 |
69 | ## Exercise
70 |
71 | ### Goals
72 |
73 | Add assertions to ensure that after calling `transfer`:
74 |
75 | - `msg.sender` must have its initial balance or less.
76 | - `to` must have its initial balance or more.
77 |
78 | Once Echidna finds the bug, fix the issue, and re-try your assertion with Echidna.
79 |
80 | This exercise is similar to the [first one](Exercise-1.md), but it uses assertions instead of explicit properties.
81 |
82 | The skeleton for this exercise is ([template.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise4/template.sol)):
83 |
84 | ````solidity
85 | pragma solidity ^0.8.0;
86 |
87 | import "./token.sol";
88 |
89 | /// @dev Run the template with
90 | /// ```
91 | /// solc-select use 0.8.0
92 | /// echidna program-analysis/echidna/exercises/exercise4/template.sol --contract TestToken --test-mode assertion
93 | /// ```
94 | /// or by providing a config
95 | /// ```
96 | /// echidna program-analysis/echidna/exercises/exercise4/template.sol --contract TestToken --config program-analysis/echidna/exercises/exercise4/config.yaml
97 | /// ```
98 | contract TestToken is Token {
99 | function transfer(address to, uint256 value) public {
100 | // TODO: include `assert(condition)` statements that
101 | // detect a breaking invariant on a transfer.
102 | // Hint: you may use the following to wrap the original function.
103 | super.transfer(to, value);
104 | }
105 | }
106 | ````
107 |
108 | ## Solution
109 |
110 | This solution can be found in [solution.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise4/solution.sol)
111 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/Exercise-5.md:
--------------------------------------------------------------------------------
1 | # Exercise 5
2 |
3 | **Table of contents:**
4 |
5 | - [Exercise 5](#exercise-5)
6 | - [Setup](#setup)
7 | - [Context](#context)
8 | - [Goals](#goals)
9 | - [Hints](#hints)
10 | - [Solution](#solution)
11 |
12 | Join the team on Slack at: https://slack.empirehacking.nyc/ #ethereum
13 |
14 | ## Setup
15 |
16 | 1. Clone the repo: `git clone https://github.com/crytic/damn-vulnerable-defi-echidna`
17 | 2. Install the dependencies by running `yarn install`.
18 |
19 | ## Context
20 |
21 | The challenge is described here: https://www.damnvulnerabledefi.xyz/challenges/2.html. It is assumed that the reader is familiar with the challenge.
22 |
23 | ## Goals
24 |
25 | - Set up the testing environment with the correct contracts and necessary balances.
26 | - Analyze the "before" function in test/naive-receiver/naive-receiver.challenge.js to identify the required initial setup.
27 | - Add a property to check if the balance of the `FlashLoanReceiver` contract can change.
28 | - Create a `config.yaml` with the necessary configuration option(s).
29 | - Once Echidna finds the bug, fix the issue and re-test your property with Echidna.
30 |
31 | The following contracts are relevant:
32 |
33 | - `contracts/naive-receiver/FlashLoanReceiver.sol`
34 | - `contracts/naive-receiver/NaiveReceiverLenderPool.sol`
35 |
36 | ## Hints
37 |
38 | It is recommended to first attempt without reading the hints. The hints can be found in the [`hints` branch](https://github.com/crytic/damn-vulnerable-defi-echidna/tree/hints).
39 |
40 | - Remember that you might need to supply the test contract with Ether. Read more in [the Echidna wiki](https://github.com/crytic/echidna/wiki/Config) and check [the default config setup](https://github.com/crytic/echidna/blob/master/tests/solidity/basic/default.yaml).
41 | - The invariant to look for is that "the balance of the receiver contract cannot decrease."
42 | - Learn about the [allContracts optio](../basic/common-testing-approaches.md#external-testing).
43 | - A template is provided in [contracts/naive-receiver/NaiveReceiverEchidna.sol](https://github.com/crytic/damn-vulnerable-defi-echidna/blob/hints/contracts/naive-receiver/NaiveReceiverEchidna.sol).
44 | - A config file is provided in [naivereceiver.yaml](https://github.com/crytic/damn-vulnerable-defi-echidna/blob/hints/naivereceiver.yaml).
45 |
46 | ## Solution
47 |
48 | The solution can be found in the [`solutions` branch](https://github.com/crytic/damn-vulnerable-defi-echidna/blob/solutions/contracts/naive-receiver/NaiveReceiverEchidna.sol).
49 |
50 | [ctf]: https://www.damnvulnerabledefi.xyz/
51 |
52 |
53 | Solution Explained (spoilers ahead)
54 |
55 | The goal of the naive receiver challenge is to realize that any user can request a flash loan for `FlashLoanReceiver`, even if the user has no Ether.
56 |
57 | Echidna discovers this by calling `NaiveReceiverLenderPool.flashLoan()` with the address of `FlashLoanReceiver` and any arbitrary amount.
58 |
59 | See the example output from Echidna below:
60 |
61 | ```bash
62 | echidna . --contract NaiveReceiverEchidna --config naivereceiver.yaml
63 | ...
64 |
65 | echidna_test_contract_balance: failed!💥
66 | Call sequence:
67 | flashLoan(0x62d69f6867a0a084c6d313943dc22023bc263691,353073667)
68 |
69 | ...
70 | ```
71 |
72 |
73 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/Exercise-6.md:
--------------------------------------------------------------------------------
1 | # Exercise 6
2 |
3 | **Table of Contents:**
4 |
5 | - [Exercise 6](#exercise-6)
6 | - [Setup](#setup)
7 | - [Context](#context)
8 | - [Goals](#goals)
9 | - [Hints](#hints)
10 | - [Solution](#solution)
11 |
12 | Join the team on Slack at: https://slack.empirehacking.nyc/ #ethereum
13 |
14 | ## Setup
15 |
16 | 1. Clone the repository: `git clone https://github.com/crytic/damn-vulnerable-defi-echidna`
17 | 2. Install the dependencies with `yarn install`.
18 |
19 | ## Context
20 |
21 | The challenge is described here: https://www.damnvulnerabledefi.xyz/challenges/1.html. We assume that the reader is familiar with it.
22 |
23 | ## Goals
24 |
25 | - Set up the testing environment with the appropriate contracts and necessary balances.
26 | - Analyze the "before" function in `test/unstoppable/unstoppable.challenge.js` to identify the initial setup required.
27 | - Add a property to check whether `UnstoppableLender` can always provide flash loans.
28 | - Create a `config.yaml` file with the required configuration option(s).
29 | - Once Echidna finds the bug, fix the issue, and retry your property with Echidna.
30 |
31 | Only the following contracts are relevant:
32 |
33 | - `contracts/DamnValuableToken.sol`
34 | - `contracts/unstoppable/UnstoppableLender.sol`
35 | - `contracts/unstoppable/ReceiverUnstoppable.sol`
36 |
37 | ## Hints
38 |
39 | We recommend trying without reading the following hints first. The hints are in the [`hints` branch](https://github.com/crytic/damn-vulnerable-defi-echidna/tree/hints).
40 |
41 | - The invariant we are looking for is "Flash loans can always be made".
42 | - Read what the [allContracts option](../basic/common-testing-approaches.md#external-testing) is.
43 | - The `receiveTokens` callback function must be implemented.
44 | - A template is provided in [contracts/unstoppable/UnstoppableEchidna.sol](https://github.com/crytic/damn-vulnerable-defi-echidna/blob/hints/contracts/unstoppable/UnstoppableEchidna.sol).
45 | - A configuration file is provided in [unstoppable.yaml](https://github.com/crytic/damn-vulnerable-defi-echidna/blob/hints/unstoppable.yaml).
46 |
47 | ## Solution
48 |
49 | This solution can be found in the [`solutions` branch](https://github.com/crytic/damn-vulnerable-defi-echidna/blob/solutions/contracts/unstoppable/UnstoppableEchidna.sol).
50 |
51 | [ctf]: https://www.damnvulnerabledefi.xyz/
52 |
53 |
54 | Solution Explained (spoilers ahead)
55 |
56 | Note: Please ensure that you have placed `solution.sol` (or `UnstoppableEchidna.sol`) in `contracts/unstoppable`.
57 |
58 | The goal of the unstoppable challenge is to recognize that `UnstoppableLender` has two modes of tracking its balance: `poolBalance` and `damnValuableToken.balanceOf(address(this))`.
59 |
60 | `poolBalance` is increased when someone calls `depositTokens()`.
61 |
62 | However, a user can call `damnValuableToken.transfer()` directly and increase the `balanceOf(address(this))` without increasing `poolBalance`.
63 |
64 | Now, the two balance trackers are out of sync.
65 |
66 | When Echidna calls `pool.flashLoan(10)`, the assertion `assert(poolBalance == balanceBefore)` in `UnstoppableLender` will fail, and the pool can no longer provide flash loans.
67 |
68 | See the example output below from Echidna:
69 |
70 | ```bash
71 | echidna . --contract UnstoppableEchidna --config unstoppable.yaml
72 |
73 | ...
74 |
75 | echidna_testFlashLoan: failed!💥
76 | Call sequence:
77 | transfer(0x62d69f6867a0a084c6d313943dc22023bc263691,1296000)
78 |
79 | ...
80 | ```
81 |
82 |
83 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/Exercise-7.md:
--------------------------------------------------------------------------------
1 | # Exercise 7
2 |
3 | **Table of contents:**
4 |
5 | - [Exercise 7](#exercise-7)
6 | - [Setup](#setup)
7 | - [Goals](#goals)
8 | - [Solution](#solution)
9 |
10 | Join the team on Slack at: https://slack.empirehacking.nyc/ #ethereum
11 |
12 | ## Setup
13 |
14 | 1. Clone the repository: `git clone https://github.com/crytic/damn-vulnerable-defi-echidna`
15 | 2. Install dependencies using `yarn install`.
16 | 3. Analyze the `before` function in `test/side-entrance/side-entrance.challenge.js` to determine the initial setup requirements.
17 | 4. Create a contract to be used for property testing with Echidna.
18 |
19 | No skeleton will be provided for this exercise.
20 |
21 | ## Goals
22 |
23 | - Set up the testing environment with appropriate contracts and necessary balances.
24 | - Add a property to check if the balance of the `SideEntranceLenderPool` contract has changed.
25 | - Create a `config.yaml` with the required configuration option(s).
26 | - After Echidna discovers the bug, fix the issue and test your property with Echidna again.
27 |
28 | Hint: To become familiar with the workings of the target contract, try manually executing a flash loan.
29 |
30 | ## Solution
31 |
32 | The solution can be found in [solution.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/echidna/exercises/exercise7/solution.sol).
33 |
34 | [ctf]: https://www.damnvulnerabledefi.xyz/
35 |
36 |
37 | Solution Explained (spoilers ahead)
38 |
39 | The goal of the side entrance challenge is to realize that the contract's ETH balance accounting is misconfigured. The `balanceBefore` variable tracks the contract's balance before the flash loan, while `address(this).balance` tracks the balance after the flash loan. As a result, you can use the deposit function to repay your flash loan while maintaining the notion that the contract's total ETH balance hasn't changed (i.e., `address(this).balance >= balanceBefore`). However, since you now own the deposited ETH, you can also withdraw it and drain all the funds from the contract.
40 |
41 | For Echidna to interact with the `SideEntranceLenderPool`, it must be deployed first. Deploying and funding the pool from the Echidna property testing contract won't work, as the funding transaction's `msg.sender` will be the contract itself. This means that the Echidna contract will own the funds, allowing it to remove them by calling `withdraw()` without exploiting the vulnerability.
42 |
43 | To avoid the above issue, create a simple factory contract that deploys the pool without setting the Echidna property testing contract as the owner of the funds. This factory will have a public function that deploys a `SideEntranceLenderPool`, funds it with the given amount, and returns its address. Since the Echidna testing contract does not own the funds, it cannot call `withdraw()` to empty the pool.
44 |
45 | With the challenge properly set up, instruct Echidna to execute a flash loan. By using the `setEnableWithdraw` and `setEnableDeposit`, Echidna will search for functions to call within the flash loan callback to attempt to break the `testPoolBalance` property.
46 |
47 | Echidna will eventually discover that if (1) `deposit` is used to repay the flash loan and (2) `withdraw` is called immediately afterward, the `testPoolBalance` property fails.
48 |
49 | Example Echidna output:
50 |
51 | ```
52 | echidna . --contract EchidnaSideEntranceLenderPool --config config.yaml
53 | ...
54 | testPoolBalance(): failed!💥
55 | Call sequence:
56 | execute() Value: 0x103
57 | setEnableDeposit(true,256)
58 | flashLoan(1)
59 | setEnableWithdraw(true)
60 | setEnableDeposit(false,0)
61 | execute()
62 | testPoolBalance()
63 | ...
64 | ```
65 |
66 |
67 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/README.md:
--------------------------------------------------------------------------------
1 | # Exercises
2 |
3 | - [Exercise 1](./Exercise-1.md): Test token balances
4 | - [Exercise 2](./Exercise-2.md): Test access control
5 | - [Exercise 3](./Exercise-3.md): Test with custom initialization
6 | - [Exercise 4](./Exercise-4.md): Test using `assert`
7 | - [Exercise 5](./Exercise-5.md): Solve Damn Vulnerable DeFi - Naive Receiver
8 | - [Exercise 6](./Exercise-6.md): Solve Damn Vulnerable DeFi - Unstoppable
9 | - [Exercise 7](./Exercise-7.md): Solve Damn Vulnerable DeFi - Side Entrance
10 | - [Exercise 8](./Exercise-8.md): Solve Damn Vulnerable DeFi - The Rewarder
11 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise1/medusa.json:
--------------------------------------------------------------------------------
1 | {
2 | "fuzzing": {
3 | "testing": {
4 | "propertyTesting": {
5 | "enabled": true,
6 | "testPrefixes": ["echidna_"]
7 | }
8 | }
9 | },
10 | "compilation": {
11 | "platform": "crytic-compile",
12 | "platformConfig": {
13 | "target": ".",
14 | "solcVersion": "",
15 | "exportDirectory": "",
16 | "args": []
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise1/solution.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./token.sol";
5 |
6 | /// @dev Run the solution with
7 | /// ```
8 | /// solc-select use 0.8.0
9 | /// echidna program-analysis/echidna/exercises/exercise1/solution.sol
10 | /// ```
11 | contract TestToken is Token {
12 | address echidna = msg.sender;
13 |
14 | constructor() {
15 | balances[echidna] = 10_000;
16 | }
17 |
18 | function echidna_test_balance() public view returns (bool) {
19 | return balances[echidna] <= 10000;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise1/template.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./token.sol";
5 |
6 | /// @dev Run the template with
7 | /// ```
8 | /// solc-select use 0.8.0
9 | /// echidna program-analysis/echidna/exercises/exercise1/template.sol
10 | /// ```
11 | contract TestToken is Token {
12 | address echidna = tx.origin;
13 |
14 | constructor() {
15 | balances[echidna] = 10_000;
16 | }
17 |
18 | function echidna_test_balance() public view returns (bool) {
19 | // TODO: add the property
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise1/token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract Ownable {
5 | address public owner = msg.sender;
6 |
7 | modifier onlyOwner() {
8 | require(msg.sender == owner, "Ownable: Caller is not the owner.");
9 | _;
10 | }
11 | }
12 |
13 | contract Pausable is Ownable {
14 | bool private _paused;
15 |
16 | function paused() public view returns (bool) {
17 | return _paused;
18 | }
19 |
20 | function pause() public onlyOwner {
21 | _paused = true;
22 | }
23 |
24 | function resume() public onlyOwner {
25 | _paused = false;
26 | }
27 |
28 | modifier whenNotPaused() {
29 | require(!_paused, "Pausable: Contract is paused.");
30 | _;
31 | }
32 | }
33 |
34 | contract Token is Ownable, Pausable {
35 | mapping(address => uint256) public balances;
36 |
37 | function transfer(address to, uint256 value) public whenNotPaused {
38 | // unchecked to save gas
39 | unchecked {
40 | balances[msg.sender] -= value;
41 | balances[to] += value;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise2/medusa.json:
--------------------------------------------------------------------------------
1 | {
2 | "fuzzing": {
3 | "testing": {
4 | "propertyTesting": {
5 | "enabled": true,
6 | "testPrefixes": ["echidna_"]
7 | }
8 | }
9 | },
10 | "compilation": {
11 | "platform": "crytic-compile",
12 | "platformConfig": {
13 | "target": ".",
14 | "solcVersion": "",
15 | "exportDirectory": "",
16 | "args": []
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise2/solution.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./token.sol";
5 |
6 | /// @dev Run the solution with
7 | /// ```
8 | /// solc-select use 0.8.0
9 | /// echidna program-analysis/echidna/exercises/exercise1/solution.sol
10 | /// ```
11 | contract TestToken is Token {
12 | constructor() {
13 | pause(); // pause the contract
14 | owner = address(0); // lose ownership
15 | }
16 |
17 | function echidna_no_transfer() public view returns (bool) {
18 | return paused() == true;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise2/template.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./token.sol";
5 |
6 | /// @dev Run the template with
7 | /// ```
8 | /// solc-select use 0.8.0
9 | /// echidna program-analysis/echidna/exercises/exercise2/template.sol
10 | /// ```
11 | contract TestToken is Token {
12 | constructor() {
13 | pause(); // pause the contract
14 | owner = address(0); // lose ownership
15 | }
16 |
17 | function echidna_cannot_be_unpause() public view returns (bool) {
18 | // TODO: add the property
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise2/token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract Ownable {
5 | address public owner = msg.sender;
6 |
7 | function Owner() public {
8 | owner = msg.sender;
9 | }
10 |
11 | modifier onlyOwner() {
12 | require(owner == msg.sender);
13 | _;
14 | }
15 | }
16 |
17 | contract Pausable is Ownable {
18 | bool private _paused;
19 |
20 | function paused() public view returns (bool) {
21 | return _paused;
22 | }
23 |
24 | function pause() public onlyOwner {
25 | _paused = true;
26 | }
27 |
28 | function resume() public onlyOwner {
29 | _paused = false;
30 | }
31 |
32 | modifier whenNotPaused() {
33 | require(!_paused, "Pausable: Contract is paused.");
34 | _;
35 | }
36 | }
37 |
38 | contract Token is Ownable, Pausable {
39 | mapping(address => uint256) public balances;
40 |
41 | function transfer(address to, uint256 value) public whenNotPaused {
42 | balances[msg.sender] -= value;
43 | balances[to] += value;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise3/medusa.json:
--------------------------------------------------------------------------------
1 | {
2 | "fuzzing": {
3 | "testing": {
4 | "propertyTesting": {
5 | "enabled": true,
6 | "testPrefixes": ["echidna_"]
7 | }
8 | }
9 | },
10 | "compilation": {
11 | "platform": "crytic-compile",
12 | "platformConfig": {
13 | "target": ".",
14 | "solcVersion": "",
15 | "exportDirectory": "",
16 | "args": []
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise3/mintable.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./token.sol";
5 |
6 | contract MintableToken is Token {
7 | int256 public totalMinted;
8 | int256 public totalMintable;
9 |
10 | constructor(int256 totalMintable_) {
11 | totalMintable = totalMintable_;
12 | }
13 |
14 | function mint(uint256 value) public onlyOwner {
15 | require(int256(value) + totalMinted < totalMintable);
16 | totalMinted += int256(value);
17 |
18 | balances[msg.sender] += value;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise3/solution.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./mintable.sol";
5 |
6 | /// @dev Run the solution with
7 | /// ```
8 | /// solc-select use 0.8.0
9 | /// echidna program-analysis/echidna/exercises/exercise3/solution.sol --contract TestToken
10 | /// ```
11 | contract TestToken is MintableToken {
12 | address echidna = msg.sender;
13 |
14 | constructor() MintableToken(10_000) {
15 | owner = echidna;
16 | }
17 |
18 | function echidna_test_balance() public view returns (bool) {
19 | return balances[msg.sender] <= 10_000;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise3/template.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./mintable.sol";
5 |
6 | /// @dev Run the template with
7 | /// ```
8 | /// solc-select use 0.8.0
9 | /// echidna program-analysis/echidna/exercises/exercise3/template.sol --contract TestToken
10 | /// ```
11 | contract TestToken is MintableToken {
12 | address echidna = msg.sender;
13 |
14 | // TODO: update the constructor
15 | constructor(int256 totalMintable) MintableToken(totalMintable) {}
16 |
17 | function echidna_test_balance() public view returns (bool) {
18 | // TODO: add the property
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise3/token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | /// @notice The issues from exercise 1 and 2 are fixed.
5 |
6 | contract Ownable {
7 | address public owner = msg.sender;
8 |
9 | modifier onlyOwner() {
10 | require(msg.sender == owner, "Ownable: Caller is not the owner.");
11 | _;
12 | }
13 | }
14 |
15 | contract Pausable is Ownable {
16 | bool private _paused;
17 |
18 | function paused() public view returns (bool) {
19 | return _paused;
20 | }
21 |
22 | function pause() public onlyOwner {
23 | _paused = true;
24 | }
25 |
26 | function resume() public onlyOwner {
27 | _paused = false;
28 | }
29 |
30 | modifier whenNotPaused() {
31 | require(!_paused, "Pausable: Contract is paused.");
32 | _;
33 | }
34 | }
35 |
36 | contract Token is Ownable, Pausable {
37 | mapping(address => uint256) public balances;
38 |
39 | function transfer(address to, uint256 value) public whenNotPaused {
40 | balances[msg.sender] -= value;
41 | balances[to] += value;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise4/config.yaml:
--------------------------------------------------------------------------------
1 | testMode: assertion
2 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise4/medusa.json:
--------------------------------------------------------------------------------
1 | {
2 | "fuzzing": {
3 | "workers": 10,
4 | "workerResetLimit": 50,
5 | "timeout": 0,
6 | "testLimit": 0,
7 | "callSequenceLength": 100,
8 | "corpusDirectory": "",
9 | "coverageEnabled": true,
10 | "targetContracts": [],
11 | "targetContractsBalances": [],
12 | "constructorArgs": {},
13 | "deployerAddress": "0x30000",
14 | "senderAddresses": ["0x10000", "0x20000", "0x30000"],
15 | "blockNumberDelayMax": 60480,
16 | "blockTimestampDelayMax": 604800,
17 | "blockGasLimit": 125000000,
18 | "transactionGasLimit": 12500000,
19 | "testing": {
20 | "stopOnFailedTest": true,
21 | "stopOnFailedContractMatching": false,
22 | "stopOnNoTests": true,
23 | "testAllContracts": false,
24 | "traceAll": false,
25 | "assertionTesting": {
26 | "enabled": true,
27 | "testViewMethods": false,
28 | "panicCodeConfig": {
29 | "failOnCompilerInsertedPanic": false,
30 | "failOnAssertion": true,
31 | "failOnArithmeticUnderflow": false,
32 | "failOnDivideByZero": false,
33 | "failOnEnumTypeConversionOutOfBounds": false,
34 | "failOnIncorrectStorageAccess": false,
35 | "failOnPopEmptyArray": false,
36 | "failOnOutOfBoundsArrayAccess": false,
37 | "failOnAllocateTooMuchMemory": false,
38 | "failOnCallUninitializedVariable": false
39 | }
40 | },
41 | "propertyTesting": {
42 | "enabled": true,
43 | "testPrefixes": ["property_"]
44 | },
45 | "optimizationTesting": {
46 | "enabled": true,
47 | "testPrefixes": ["optimize_"]
48 | }
49 | },
50 | "chainConfig": {
51 | "codeSizeCheckDisabled": true,
52 | "cheatCodes": {
53 | "cheatCodesEnabled": true,
54 | "enableFFI": false
55 | }
56 | }
57 | },
58 | "compilation": {
59 | "platform": "crytic-compile",
60 | "platformConfig": {
61 | "target": ".",
62 | "solcVersion": "",
63 | "exportDirectory": "",
64 | "args": []
65 | }
66 | },
67 | "logging": {
68 | "level": "info",
69 | "logDirectory": "",
70 | "noColor": false
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise4/solution.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./token.sol";
5 |
6 | /// @dev Run the solution with
7 | /// ```
8 | /// solc-select use 0.8.0
9 | /// echidna program-analysis/echidna/exercises/exercise4/solution.sol --contract TestToken --test-mode assertion
10 | /// ```
11 | contract TestToken is Token {
12 | function transfer(address to, uint256 value) public override {
13 | uint256 oldBalanceFrom = balances[msg.sender];
14 | uint256 oldBalanceTo = balances[to];
15 |
16 | super.transfer(to, value);
17 |
18 | assert(balances[msg.sender] <= oldBalanceFrom);
19 | assert(balances[to] >= oldBalanceTo);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise4/template.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | import "./token.sol";
5 |
6 | /// @dev Run the template with
7 | /// ```
8 | /// solc-select use 0.8.0
9 | /// echidna program-analysis/echidna/exercises/exercise4/template.sol --contract TestToken --test-mode assertion
10 | /// ```
11 | /// or by providing a config
12 | /// ```
13 | /// echidna program-analysis/echidna/exercises/exercise4/template.sol --contract TestToken --config program-analysis/echidna/exercises/exercise4/config.yaml
14 | /// ```
15 | contract TestToken is Token {
16 | function transfer(address to, uint256 value) public {
17 | // TODO: include `assert(condition)` statements that
18 | // detect a breaking invariant on a transfer.
19 | // Hint: you may use the following to wrap the original function.
20 | super.transfer(to, value);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise4/token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.0;
3 |
4 | contract Ownable {
5 | address public owner = msg.sender;
6 |
7 | function transferOwnership(address newOwner) public onlyOwner {
8 | owner = newOwner;
9 | }
10 |
11 | modifier onlyOwner() {
12 | require(msg.sender == owner, "Ownable: Caller is not the owner.");
13 | _;
14 | }
15 | }
16 |
17 | contract Pausable is Ownable {
18 | bool private _paused;
19 |
20 | function paused() public view returns (bool) {
21 | return _paused;
22 | }
23 |
24 | function pause() public onlyOwner {
25 | _paused = true;
26 | }
27 |
28 | function resume() public onlyOwner {
29 | _paused = false;
30 | }
31 |
32 | modifier whenNotPaused() {
33 | require(!_paused, "Pausable: Contract is paused.");
34 | _;
35 | }
36 | }
37 |
38 | contract Token is Ownable, Pausable {
39 | mapping(address => uint256) public balances;
40 |
41 | function transfer(address to, uint256 value) public virtual whenNotPaused {
42 | // unchecked to save gas
43 | unchecked {
44 | balances[msg.sender] -= value;
45 | balances[to] += value;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise7/config.yaml:
--------------------------------------------------------------------------------
1 | testMode: assertion
2 | balanceContract: 1000000000000000000000
3 | testLimit: 100000000000
4 |
5 | deployer: "0x10000"
6 | psender: "0x10000"
7 | contractAddr: "0x10000"
8 | sender: ["0x10000"]
9 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise7/example.hardhat.config.ts:
--------------------------------------------------------------------------------
1 | require("@nomiclabs/hardhat-waffle");
2 | require("@openzeppelin/hardhat-upgrades");
3 | require("hardhat-dependency-compiler");
4 |
5 | module.exports = {
6 | networks: {
7 | hardhat: {
8 | allowUnlimitedContractSize: true,
9 | },
10 | localhost: {
11 | url: "http://127.0.0.1:8545",
12 | },
13 | },
14 | solidity: {
15 | compilers: [
16 | { version: "0.8.7" },
17 | { version: "0.7.6" },
18 | { version: "0.6.6" },
19 | ],
20 | },
21 | /*
22 | dependencyCompiler: {
23 | paths: [
24 | '@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol',
25 | '@gnosis.pm/safe-contracts/contracts/proxies/GnosisSafeProxyFactory.sol',
26 | ],
27 | }*/
28 | };
29 |
--------------------------------------------------------------------------------
/program-analysis/echidna/exercises/exercise7/solution.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.4;
3 |
4 | import "./side-entrance/SideEntranceLenderPool.sol";
5 |
6 | contract PoolDeployer {
7 | function deployNewPool() public payable returns (SideEntranceLenderPool) {
8 | SideEntranceLenderPool p;
9 | p = new SideEntranceLenderPool();
10 | p.deposit{value: msg.value}();
11 |
12 | return p;
13 | }
14 | }
15 |
16 | contract SideEntranceEchidna is IFlashLoanEtherReceiver {
17 | SideEntranceLenderPool pool;
18 |
19 | uint256 initialPoolBalance;
20 |
21 | bool enableWithdraw;
22 | bool enableDeposit;
23 | uint256 depositAmount;
24 |
25 | constructor() payable {
26 | require(msg.value == 1000 ether);
27 |
28 | PoolDeployer p = new PoolDeployer();
29 |
30 | pool = p.deployNewPool{value: 1000 ether}();
31 | initialPoolBalance = address(pool).balance;
32 | }
33 |
34 | receive() external payable {}
35 |
36 | function setEnableWithdraw(bool _enabled) public {
37 | enableWithdraw = _enabled;
38 | }
39 |
40 | function setEnableDeposit(bool _enabled, uint256 _amount) public {
41 | enableDeposit = _enabled;
42 | depositAmount = _amount;
43 | }
44 |
45 | function execute() external payable override {
46 | if (enableWithdraw) {
47 | pool.withdraw();
48 | }
49 | if (enableDeposit) {
50 | pool.deposit{value: depositAmount}();
51 | }
52 | }
53 |
54 | function flashLoan(uint256 _amount) public {
55 | pool.flashLoan(_amount);
56 | }
57 |
58 | function testPoolBalance() public view {
59 | assert(address(pool).balance >= initialPoolBalance);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/program-analysis/echidna/fuzzing_tips.md:
--------------------------------------------------------------------------------
1 | # Fuzzing Tips
2 |
3 | The following tips will help enhance the efficiency of Echidna when fuzzing:
4 |
5 | - **Use `%` to filter the range of input values**. Refer to [Filtering inputs](#filtering-inputs) for more information.
6 | - **Use push/pop when dealing with dynamic arrays**. See [Handling dynamic arrays](#handling-dynamic-arrays) for details.
7 |
8 | ## Filtering Inputs
9 |
10 | Using `%` is more efficient for filtering input values than adding `require` or `if` statements. For instance, when fuzzing an `operation(uint256 index, ..)` with `index` expected to be less than `10**18`, use the following:
11 |
12 | ```solidity
13 | function operation(uint256 index) public {
14 | index = index % 10 ** 18;
15 | // ...
16 | }
17 | ```
18 |
19 | Using `require(index <= 10**18)` instead would result in many generated transactions reverting, which would slow down the fuzzer.
20 |
21 | To define a minimum and maximum range, you can adapt the code like this:
22 |
23 | ```solidity
24 | function operation(uint256 balance) public {
25 | balance = MIN_BALANCE + (balance % (MAX_BALANCE - MIN_BALANCE));
26 | // ...
27 | }
28 | ```
29 |
30 | This ensures that the `balance` value stays between `MIN_BALANCE` and `MAX_BALANCE`, without discarding any generated transactions. While this speeds up the exploration process, it might prevent some code paths from being tested. To address this issue, you can provide two functions:
31 |
32 | ```solidity
33 | function operation(uint256 balance) public {
34 | // ...
35 | }
36 |
37 | function safeOperation(uint256 balance) public {
38 | balance = MIN_BALANCE + (balance % (MAX_BALANCE - MIN_BALANCE)); // safe balance
39 | // ...
40 | }
41 | ```
42 |
43 | Echidna can then use either of these functions, allowing it to explore both safe and unsafe usage of the input data.
44 |
45 | ## Handling Dynamic Arrays
46 |
47 | When using a dynamic array as input, Echidna restricts its size to 32 elements:
48 |
49 | ```solidity
50 | function operation(uint256[] calldata data) public {
51 | // ...
52 | }
53 | ```
54 |
55 | This is because deserializing dynamic arrays can be slow and may consume a significant amount of memory. Additionally, dynamic arrays can be difficult to mutate. However, Echidna includes specific mutators to remove/repeat elements or truncate elements, which it performs using the collected corpus. Generally, we recommend using `push(...)` and `pop()` functions to handle dynamic arrays used as inputs:
56 |
57 | ```solidity
58 | contract DataHandler {
59 | uint256[] data;
60 |
61 | function push(uint256 x) public {
62 | data.push(x);
63 | }
64 |
65 | function pop() public {
66 | data.pop();
67 | }
68 |
69 | function operation() public {
70 | // Use of `data`
71 | }
72 | }
73 | ```
74 |
75 | This approach works well for testing arrays with a small number of elements. However, it can introduce an exploration bias: since `push` and `pop` functions are selected with equal probability, the chances of creating large arrays (e.g., more than 64 elements) are very low. One workaround is to blacklist the `pop()` function during a brief campaign:
76 |
77 | ```
78 | filterFunctions: ["C.pop()"]
79 | ```
80 |
81 | This should suffice for small-scale testing. A more comprehensive solution involves [_swarm testing_](https://www.cs.utah.edu/~regehr/papers/swarm12.pdf), a technique that performs long testing campaigns with randomized configurations. In the context of Echidna, swarm testing is executed using different configuration files, which blacklist random contract functions before testing. We offer swarm testing and scalability through [echidna-parade](https://github.com/crytic/echidna-parade), our dedicated tool for fuzzing smart contracts. A tutorial on using echidna-parade can be found [here](./advanced/smart-contract-fuzzing-at-scale.md).
82 |
--------------------------------------------------------------------------------
/program-analysis/echidna/introduction/README.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Introductory materials for fuzzing and Echidna:
4 |
5 | - [Installation](./installation.md)
6 | - [Introduction to fuzzing](./fuzzing-introduction.md): A brief introduction to fuzzing
7 | - [How to test a property](./how-to-test-a-property.md): Testing a property with Echidna
8 |
--------------------------------------------------------------------------------
/program-analysis/echidna/introduction/fuzzing-introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction to Property-Based Fuzzing
2 |
3 | Echidna is a property-based fuzzer, which we have described in our previous blog posts ([1](https://blog.trailofbits.com/2018/03/09/echidna-a-smart-fuzzer-for-ethereum/), [2](https://blog.trailofbits.com/2018/05/03/state-machine-testing-with-echidna/), [3](https://blog.trailofbits.com/2020/03/30/an-echidna-for-all-seasons/)).
4 |
5 | ## Fuzzing
6 |
7 | Fuzzing is a well-known technique in the security community. It involves generating more or less random inputs to find bugs in a program. Fuzzers for traditional software (such as [AFL](http://lcamtuf.coredump.cx/afl/) or [LibFuzzer](https://llvm.org/docs/LibFuzzer.html)) are known to be efficient tools for bug discovery.
8 |
9 | Beyond purely random input generation, there are many techniques and strategies used for generating good inputs, including:
10 |
11 | - **Obtaining feedback from each execution and guiding input generation with it**. For example, if a newly generated input leads to the discovery of a new path, it makes sense to generate new inputs closer to it.
12 | - **Generating input with respect to a structural constraint**. For instance, if your input contains a header with a checksum, it makes sense to let the fuzzer generate input that validates the checksum.
13 | - **Using known inputs to generate new inputs**. If you have access to a large dataset of valid input, your fuzzer can generate new inputs from them, rather than starting from scratch for each generation. These are usually called _seeds_.
14 |
15 | ## Property-Based Fuzzing
16 |
17 | Echidna belongs to a specific family of fuzzers: property-based fuzzing, which is heavily inspired by [QuickCheck](https://en.wikipedia.org/wiki/QuickCheck). In contrast to a classic fuzzer that tries to find crashes, Echidna aims to break user-defined invariants.
18 |
19 | In smart contracts, invariants are Solidity functions that can represent any incorrect or invalid state that the contract can reach, including:
20 |
21 | - Incorrect access control: The attacker becomes the owner of the contract.
22 | - Incorrect state machine: Tokens can be transferred while the contract is paused.
23 | - Incorrect arithmetic: The user can underflow their balance and get unlimited free tokens.
24 |
--------------------------------------------------------------------------------
/program-analysis/echidna/introduction/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | Echidna can be installed either through Docker or by using the pre-compiled binary.
4 |
5 | ## MacOS
6 |
7 | To install Echidna on MacOS, simply run the following command:
8 |
9 | `brew install echidna`.
10 |
11 | ## Echidna via Docker
12 |
13 | To install Echidna using Docker, execute the following commands:
14 |
15 | ```bash
16 | docker pull trailofbits/eth-security-toolbox
17 | docker run -it -v "$PWD":/home/training trailofbits/eth-security-toolbox
18 | ```
19 |
20 | _The last command runs the eth-security-toolbox in a Docker container, which will have access to your current directory. This allows you to modify the files on your host machine and run the tools on those files within the container._
21 |
22 | Inside Docker, execute the following commands:
23 |
24 | ```bash
25 | solc-select use 0.8.0
26 | cd /home/training
27 | ```
28 |
29 | ## Binary
30 |
31 | You can find the latest released binary here:
32 |
33 | [https://github.com/crytic/echidna/releases/latest](https://github.com/crytic/echidna/releases/latest)
34 |
35 | It's essential to use the correct solc version to ensure that these exercises work as expected. We have tested them using version 0.8.0.
36 |
--------------------------------------------------------------------------------
/program-analysis/manticore/README.md:
--------------------------------------------------------------------------------
1 | # Manticore Tutorial
2 |
3 | The aim of this tutorial is to show how to use Manticore to automatically find bugs in smart contracts.
4 |
5 | The first part introduces a set of the basic features of Manticore: running under Manticore and manipulating smart contracts through API, getting throwing path, adding constraints.
6 | The second part is exercise to solve.
7 |
8 | **Table of contents:**
9 |
10 | - [Installation](#installation)
11 | - [Introduction to symbolic execution](./symbolic-execution-introduction.md): Brief introduction to symbolic execution
12 | - [Running under Manticore](./running-under-manticore.md): How to use Manticore's API to run a contract
13 | - [Getting throwing paths](./getting-throwing-paths.md): How to use Manticore's API to get specific paths
14 | - [Adding constraints](./adding-constraints.md): How to use Manticore's API to add paths' constraints
15 | - [Exercises](./exercises)
16 |
17 | Join the team on Slack at: https://slack.empirehacking.nyc/ #ethereum, #manticore
18 |
19 | ## Installation
20 |
21 | Manticore requires >= python 3.6. It can be installed through pip or using docker.
22 |
23 | ## Manticore through docker
24 |
25 | ```bash
26 | docker pull trailofbits/eth-security-toolbox
27 | docker run -it -v "$PWD":/home/training trailofbits/eth-security-toolbox
28 | ```
29 |
30 | _The last command runs eth-security-toolbox in a docker that has access to your current directory. You can change the files from your host, and run the tools on the files from the docker_
31 |
32 | Inside docker, run:
33 |
34 | ```bash
35 | solc-select 0.5.11
36 | cd /home/trufflecon/
37 | ```
38 |
39 | ### Manticore through pip
40 |
41 | ```bash
42 | pip3 install --user manticore
43 | ```
44 |
45 | solc 0.5.11 is recommended.
46 |
47 | ### Running a script
48 |
49 | To run a python script with python 3:
50 |
51 | ```bash
52 | python3 script.py
53 | ```
54 |
--------------------------------------------------------------------------------
/program-analysis/manticore/examples/example.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.5.0;
3 |
4 | contract Simple {
5 | function f(uint256 a) public payable {
6 | if (a == 65) {
7 | revert();
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/program-analysis/manticore/examples/example_constraint.py:
--------------------------------------------------------------------------------
1 | from manticore.ethereum import ManticoreEVM
2 |
3 | ETHER = 10**18
4 |
5 | m = ManticoreEVM()
6 |
7 | user_account = m.create_account(balance=1000*ETHER)
8 |
9 | with open('example.sol') as f:
10 | contract_account = m.solidity_create_contract(f, owner=user_account)
11 |
12 | symbolic_var = m.make_symbolic_value()
13 | contract_account.f(symbolic_var)
14 |
15 | no_bug_found = True
16 |
17 | ## Check if an execution ends with a REVERT or INVALID
18 | for state in m.terminated_states:
19 | last_tx = state.platform.transactions[-1]
20 | if last_tx.result in ['REVERT', 'INVALID']:
21 | # we do not consider the path were a == 65
22 | condition = symbolic_var != 65
23 | if m.generate_testcase(state, name="BugFound", only_if=condition):
24 | print(f'Bug found, results are in {m.workspace}')
25 | no_bug_found = False
26 |
27 | if no_bug_found:
28 | print(f'No bug found')
29 |
30 |
31 |
--------------------------------------------------------------------------------
/program-analysis/manticore/examples/example_run.py:
--------------------------------------------------------------------------------
1 | from manticore.ethereum import ManticoreEVM
2 |
3 | m = ManticoreEVM()
4 |
5 | ETHER = 10**18
6 |
7 | user_account = m.create_account(balance=1000*ETHER)
8 | with open('example.sol') as f:
9 | contract_account = m.solidity_create_contract(f, owner=user_account)
10 |
11 | symbolic_var = m.make_symbolic_value()
12 | contract_account.f(symbolic_var)
13 |
14 | print("Results are in {}".format(m.workspace))
15 | m.finalize() # stop the exploration
16 |
--------------------------------------------------------------------------------
/program-analysis/manticore/examples/example_throw.py:
--------------------------------------------------------------------------------
1 | from manticore.ethereum import ManticoreEVM
2 |
3 | ETHER = 10**18
4 |
5 | m = ManticoreEVM()
6 |
7 | with open('example.sol') as f:
8 | source_code = f.read()
9 |
10 | user_account = m.create_account(balance=1000*ETHER)
11 | contract_account = m.solidity_create_contract(source_code,
12 | owner=user_account)
13 |
14 | symbolic_var = m.make_symbolic_value()
15 | contract_account.f(symbolic_var)
16 |
17 | ## Check if an execution ends with a REVERT or INVALID
18 | for state in m.terminated_states:
19 | last_tx = state.platform.transactions[-1]
20 | if last_tx.result in ['REVERT', 'INVALID']:
21 | print('Throw found {}'.format(m.workspace))
22 | m.generate_testcase(state, 'ThrowFound')
23 |
--------------------------------------------------------------------------------
/program-analysis/manticore/examples/suicidal.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.5.0;
3 |
4 | contract Suicidal {
5 | function backdoor() public {
6 | selfdestruct(msg.sender);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/README.md:
--------------------------------------------------------------------------------
1 | # Manticore Exercises
2 |
3 | - [Example](./example.md): Arithmetic overflow
4 | - [Exercise 1](./exercise1.md): Arithmetic rounding
5 | - [Exercise 2](./exercise2.md): Arithmetic overflow through multiple transactions
6 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/example.md:
--------------------------------------------------------------------------------
1 | # Example: Arithmetic overflow
2 |
3 | This scenario is given as an example. You can follow its structure to solve the exercises.
4 |
5 | [`my_token.py`](example/my_token.py) uses Manticore to find for an attacker to generate tokens during a transfer on Token ([my_token.sol](example/my_token.sol)).
6 |
7 | ## Proposed scenario
8 |
9 | We use the pattern initialization, exploration and property for our scripts.
10 |
11 | ## Initialization
12 |
13 | - Create one user account
14 | - Create the contract account
15 |
16 | ## Exploration
17 |
18 | - Call balances on the user account
19 | - Call transfer with symbolic destination and value
20 | - Call balances on the user account
21 |
22 | ## Property
23 |
24 | - Check if the user can have more token after the transfer than before.
25 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/example/my_token.py:
--------------------------------------------------------------------------------
1 | from manticore.ethereum import ManticoreEVM, ABI
2 | from manticore.core.smtlib import Operators, solver
3 |
4 | ###### Initialization ######
5 |
6 | ETHER = 10**18
7 |
8 | m = ManticoreEVM()
9 | with open('my_token.sol') as f:
10 | source_code = f.read()
11 |
12 | # Create one user account
13 | # And deploy the contract
14 | user_account = m.create_account(balance=1000*ETHER)
15 | contract_account = m.solidity_create_contract(source_code, owner=user_account, balance=0)
16 |
17 | ###### Exploration ######
18 |
19 | # - Call balances[user]
20 | # - Call transfer with symbolic values
21 | # - Call balances[user]
22 | # Later we will compare the result of the first call to balances to the second
23 | contract_account.balances(user_account)
24 |
25 | symbolic_val = m.make_symbolic_value()
26 | symbolic_to = m.make_symbolic_value()
27 | # Transfer is called with symbolic values
28 | # Manticore will fork and create state at each condition executed
29 | contract_account.transfer(symbolic_to, symbolic_val)
30 |
31 | contract_account.balances(user_account)
32 |
33 | # Check of properties ######
34 |
35 | bug_found = False
36 | # Explore all the forks
37 | for state in m.ready_states:
38 |
39 | # state.plateform.transactions returns the list of transactions
40 | # state.plateform.transactions[0] is the contract creation
41 | # state.plateform.transactions[1] is the first transaction
42 | # state.plateform.transactions[-1] is the last transaction
43 |
44 | balance_before = state.platform.transactions[1].return_data
45 | balance_before = ABI.deserialize("uint", balance_before)
46 |
47 | balance_after = state.platform.transactions[-1].return_data
48 | balance_after = ABI.deserialize("uint", balance_after)
49 |
50 | # Check if it is possible to have balance_after > balance_before
51 | condition = Operators.UGT(balance_after, balance_before)
52 | if m.generate_testcase(state, name="BugFound", only_if=condition):
53 | print("Bug found! see {}".format(m.workspace))
54 | bug_found = True
55 |
56 | if not bug_found:
57 | print('No bug were found')
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/example/my_token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.5.0;
3 |
4 | contract Token {
5 | mapping(address => uint256) public balances;
6 |
7 | constructor() public {
8 | balances[msg.sender] = 100;
9 | }
10 |
11 | function transfer(address to, uint256 val) public {
12 | // check for overflow
13 | if (balances[msg.sender] >= balances[to]) {
14 | balances[msg.sender] -= val;
15 | balances[to] += val;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/exercise1.md:
--------------------------------------------------------------------------------
1 | # Exercise 1 : Arithmetic rounding
2 |
3 | Use Manticore to find an input allowing an attacker to generate free tokens in [token.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/manticore/exercises/exercise1/token.sol).
4 | Propose a fix of the contract, and test your fix using your Manticore script.
5 |
6 | ## Proposed scenario
7 |
8 | Follow the pattern initialization, exploration and property for the script.
9 |
10 | ## Initialization
11 |
12 | - Create one account
13 | - Create the contract account
14 |
15 | ## Exploration
16 |
17 | - Call `is_valid_buy` with two symbolic values for tokens_amount and wei_amount
18 |
19 | ## Property
20 |
21 | - An attack is found if on a state alive `wei_amount == 0 and tokens_amount >= 1`
22 |
23 | ## Hints
24 |
25 | - `m.ready_states` returns the list of state alive
26 | - `Operators.AND(a, b)` allows to create and AND condition
27 | - You can use the template proposed in [template.py](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/manticore/exercises/exercise1/template.py)
28 |
29 | ## Solution
30 |
31 | [solution.py](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/manticore/exercises/exercise1/solution.py)
32 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/exercise1/solution.py:
--------------------------------------------------------------------------------
1 | from manticore.ethereum import ManticoreEVM
2 | from manticore.ethereum.abi import ABI
3 | from manticore.core.smtlib import Operators
4 |
5 | ETHER = 10**18
6 |
7 | m = ManticoreEVM() # initiate the blockchain
8 | # Init
9 | user_account = m.create_account(1*ETHER)
10 | with open('token.sol', 'r') as f:
11 | contract_account = m.solidity_create_contract(f, owner=user_account)
12 |
13 | # Exploration
14 | tokens_amount = m.make_symbolic_value()
15 | wei_amount = m.make_symbolic_value()
16 |
17 | contract_account.is_valid_buy(tokens_amount, wei_amount)
18 |
19 | # Property
20 | for state in m.ready_states:
21 |
22 | condition = Operators.AND(wei_amount == 0, tokens_amount >= 1)
23 |
24 | if m.generate_testcase(state, name="BugFound", only_if=condition):
25 | print(f'Bug found, results are in {m.workspace}')
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/exercise1/template.py:
--------------------------------------------------------------------------------
1 | from manticore.ethereum import ManticoreEVM
2 | from manticore.ethereum.abi import ABI
3 | from manticore.core.smtlib import Operators
4 |
5 | ETHER = 10**18
6 |
7 | m = ManticoreEVM() # initiate the blockchain
8 | # Init
9 | user_account = m.create_account(1*ETHER)
10 | with open('token.sol', 'r') as f:
11 | contract_account = m.solidity_create_contract(f, owner=user_account)
12 |
13 | # Exploration
14 |
15 | tokens_amount = m.make_symbolic_value()
16 | wei_amount = m.make_symbolic_value()
17 |
18 | contract_account.is_valid_buy(tokens_amount, wei_amount)
19 |
20 |
21 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/exercise1/token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.5.0;
3 |
4 | contract CryticCoin {
5 | mapping(address => uint256) balances;
6 | uint256 decimals = 1 ** 18;
7 | uint256 MAX_SUPPLY = 100 ether;
8 |
9 | event Mint(address indexed destination, uint256 amount);
10 |
11 | /// @notice Allow users to buy token. 1 ether = 10 tokens
12 | /// @param tokens The numbers of token to buy
13 | /// @dev Users can send more ether than token to be bought, to give gifts to the team.
14 | function buy(uint256 tokens) public payable {
15 | _valid_buy(tokens, msg.value);
16 | _mint(msg.sender, tokens);
17 | }
18 |
19 | /// @notice Check if a buy is valid
20 | /// @param tokens_amount tokens amount
21 | /// @param wei_amount wei amount
22 | function is_valid_buy(uint256 tokens_amount, uint256 wei_amount) external view returns (bool) {
23 | _valid_buy(tokens_amount, wei_amount);
24 | return true;
25 | }
26 |
27 | /// @notice Mint tokens
28 | /// @param addr The address holding the new token
29 | /// @param value The amount of token to be minted
30 | /// @dev This function performed no check on the caller. Must stay internal
31 | function _mint(address addr, uint256 value) internal {
32 | balances[addr] = safeAdd(balances[addr], value);
33 | emit Mint(addr, value);
34 | }
35 |
36 | /// @notice Compute the amount of token to be minted. 1 ether = 10 tokens
37 | /// @param desired_tokens The number of tokens to buy
38 | /// @param wei_sent The ether value to be converted into token
39 | function _valid_buy(uint256 desired_tokens, uint256 wei_sent) internal view {
40 | uint256 required_wei_sent = (desired_tokens / 10) * decimals;
41 | require(wei_sent >= required_wei_sent);
42 | }
43 |
44 | /// @notice Add two values. Revert if overflow
45 | function safeAdd(uint256 a, uint256 b) internal pure returns (uint256) {
46 | if (a + b <= a) {
47 | revert();
48 | }
49 | return a + b;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/exercise2.md:
--------------------------------------------------------------------------------
1 | # Exercise 2 : Arithmetic overflow through multiple transactions
2 |
3 | Use Manticore to find if an overflow is possible in Overflow.add. Propose a fix of the contract, and test your fix using your Manticore script.
4 |
5 | ## Proposed scenario
6 |
7 | Follow the pattern initialization, exploration and property for the script.
8 |
9 | ## Initialization
10 |
11 | - Create one user account
12 | - Create the contract account
13 |
14 | ## Exploration
15 |
16 | - Call two times `add` with two symbolic values
17 | - Call `sellerBalance()`
18 |
19 | ## Property
20 |
21 | - Check if it is possible for the value returned by sellerBalance() to be lower than the first input.
22 |
23 | ## Hints
24 |
25 | - The value returned by the last transaction can be accessed through:
26 |
27 | ```python
28 | state.platform.transactions[-1].return_data
29 | ```
30 |
31 | - The data returned needs to be deserialized:
32 |
33 | ```python
34 | data = ABI.deserialize("uint256", data)
35 | ```
36 |
37 | - You can use the template proposed in [template.py](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/manticore/exercises/exercise2/template.py)
38 |
39 | ## Solution
40 |
41 | [solution.py](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/manticore/exercises/exercise2/solution.py).
42 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/exercise2/overflow.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.5.0;
3 |
4 | contract Overflow {
5 | uint256 public sellerBalance = 0;
6 |
7 | function add(uint256 value) public returns (bool) {
8 | sellerBalance += value; // complicated math, possible overflow
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/exercise2/solution.py:
--------------------------------------------------------------------------------
1 | from manticore.ethereum import ManticoreEVM
2 | from manticore.core.smtlib import Operators, solver
3 | from manticore.ethereum.abi import ABI
4 |
5 | ETHER = 10**18
6 |
7 | m = ManticoreEVM() # initiate the blockchain
8 |
9 | # Generate the accounts
10 | user_account = m.create_account(balance=1000*ETHER)
11 | with open('overflow.sol') as f:
12 | contract_account = m.solidity_create_contract(f, owner=user_account)
13 |
14 | #First add won't overflow uint256 representation
15 | value_0 = m.make_symbolic_value()
16 | contract_account.add(value_0, caller=user_account)
17 | #Potential overflow
18 | value_1 = m.make_symbolic_value()
19 | contract_account.add(value_1, caller=user_account)
20 | contract_account.sellerBalance(caller=user_account)
21 |
22 | for state in m.ready_states:
23 | # Check if input0 > sellerBalance
24 |
25 | # last_return is the data returned
26 | sellerBalance_tx = state.platform.transactions[-1]
27 | # retrieve last_return and input0 in a similar format
28 | seller_balance = ABI.deserialize("uint", sellerBalance_tx.return_data)
29 |
30 | condition = Operators.UGT(value_0, seller_balance)
31 |
32 | if m.generate_testcase(state, name="BugFound", only_if=condition):
33 | print(f'Bug found, results are in {m.workspace}')
34 |
--------------------------------------------------------------------------------
/program-analysis/manticore/exercises/exercise2/template.py:
--------------------------------------------------------------------------------
1 | from manticore.ethereum import ManticoreEVM
2 | from manticore.core.smtlib import Operators, solver
3 | from manticore.ethereum.abi import ABI
4 |
5 | ETHER = 10**18
6 |
7 | m = ManticoreEVM() # initiate the blockchain
8 |
9 | # Generate the accounts
10 | user_account = m.create_account(balance=1000*ETHER)
11 | with open('overflow.sol') as f:
12 | contract_account = m.solidity_create_contract(f, owner=user_account)
13 |
14 | #First add won't overflow uint256 representation
15 | value_0 = m.make_symbolic_value()
16 | contract_account.add(value_0, caller=user_account)
17 | #Potential overflow
18 | value_1 = m.make_symbolic_value()
19 | contract_account.add(value_1, caller=user_account)
20 | contract_account.sellerBalance(caller=user_account)
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/program-analysis/manticore/scripts/gh_action_test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | test_example(){
4 | cd examples
5 |
6 | python $1
7 | if [ $? -ne 0 ]
8 | then
9 | echo "$1 failed"
10 | exit -1
11 | fi
12 |
13 | echo "$1 passed"
14 | cd ..
15 | }
16 |
17 |
18 | test_exercise_example(){
19 | cd "exercises/example"
20 |
21 | python my_token.py > results.txt
22 | if [ $? -ne 0 ]
23 | then
24 | echo "my_token.py failed"
25 | exit -1
26 | fi
27 |
28 | grep "Bug found" results.txt
29 | if [ $? -ne 0 ]
30 | then
31 | echo "Bug not found"
32 | echo "my_token.py failed"
33 | exit -1
34 | fi
35 |
36 | echo "my_token.py passed"
37 | cd ../..
38 | }
39 |
40 |
41 | test_exercise(){
42 | cd "exercises/exercise$1"
43 |
44 | python solution.py > results.txt
45 | if [ $? -ne 0 ]
46 | then
47 | echo "exercise $1 failed"
48 | exit -1
49 | fi
50 |
51 | grep "Bug found" results.txt
52 | if [ $? -ne 0 ]
53 | then
54 | "Bug not found"
55 | echo "exercise $1 failed"
56 | exit -1
57 | fi
58 |
59 | echo "exercise $1 passed"
60 | cd ../..
61 | }
62 |
63 |
64 |
65 | pip install manticore==0.3.5 crytic-compile==0.1.13
66 |
67 | cd program-analysis/manticore
68 |
69 | sudo add-apt-repository ppa:sri-csl/formal-methods -y
70 | sudo apt-get update
71 | sudo apt-get install yices2
72 |
73 | test_example example_run.py
74 | test_example example_throw.py
75 | test_example example_constraint.py
76 |
77 | test_exercise_example
78 |
79 | test_exercise 1
80 | test_exercise 2
81 |
82 | echo "Manticore tests passed"
83 |
--------------------------------------------------------------------------------
/program-analysis/manticore/symbolic-execution-introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction to dynamic symbolic execution
2 |
3 | [Manticore]() is a dynamic symbolic execution tool, we described in our previous blogposts ([1](https://blog.trailofbits.com/2017/04/27/manticore-symbolic-execution-for-humans/), [2](https://blog.trailofbits.com/2017/05/15/magic-with-manticore/),[3](https://blog.trailofbits.com/2017/05/15/magic-with-manticore/)).
4 |
5 | ## Dynamic Symbolic Execution in a Nutshell
6 |
7 | Dynamic symbolic execution (DSE) is a program analysis technique that explores a state space with a high degree of semantic awareness. This technique is based on the discovery of "program paths", represented as mathematical formulas called `path predicates`. Conceptually, this technique operates on path predicates in two steps:
8 |
9 | 1. They are constructed using constraints on the program's input.
10 | 2. They are used to generate program inputs that will cause the associated paths to execute.
11 |
12 | This approach produces no false positives in the sense that all identified program states can be triggered during concrete execution. For example, if the analysis finds an integer overflow, it is guaranteed to be reproducible.
13 |
14 | ### Path Predicate Example
15 |
16 | To get an insigh of how DSE works, consider the following example:
17 |
18 | ```solidity
19 | function f(uint256 a) {
20 | if (a == 65) {
21 | // A bug is present
22 | }
23 | }
24 | ```
25 |
26 | As `f()` contains two paths, a DSE will construct two differents path predicates:
27 |
28 | - Path 1: `a == 65`
29 | - Path 2: `Not (a == 65)`
30 |
31 | Each path predicate is a mathematical formula that can be given to a so-called `SMT solver`, which will try to solve the equation. For `Path 1`, the solver will say that the path can be explored with `a = 65`. For `Path 2`, the solver can give `a` any value other than 65, for example `a = 0`.
32 |
33 | ### Verifying properties
34 |
35 | Manticore allows a full control over all the execution of each path. As a result, it allows to add arbirtray contraints to almost anything. This control allows for the creation of properties on the contract.
36 |
37 | Consider the following example:
38 |
39 | ```solidity
40 | function unsafe_add(uint256 a, uint256 b) returns (uint256 c) {
41 | c = a + b; // no overflow protection
42 | return c;
43 | }
44 | ```
45 |
46 | Here there is only one path to explore in the function:
47 |
48 | - Path 1: `c = a + b`
49 |
50 | Using Manticore, you can check for overflow, and add constraitns to the path predicate:
51 |
52 | - `c = a + b AND (c < a OR c < b)`
53 |
54 | If it is possible to find a valuation of `a` and `b` for which the path predicate above is feasible, it means that you have found an overflow. For example the solver can generate the input `a = 10 , b = MAXUINT256`.
55 |
56 | If you consider a fixed version:
57 |
58 | ```solidity
59 | function safe_add(uint256 a, uint256 b) returns (uint256 c) {
60 | c = a + b;
61 | require(c >= a);
62 | require(c >= b);
63 | return c;
64 | }
65 | ```
66 |
67 | The associated formula with overflow check would be:
68 |
69 | - `c = a + b AND (c >= a) AND (c=>b) AND (c < a OR c < b)`
70 |
71 | This formula cannot be solved; in other words this is a **proof** that in `safe_add`, `c` will always increase.
72 |
73 | DSE is thereby a powerful tool, that can verify arbitrary constraints on your code.
74 |
--------------------------------------------------------------------------------
/resources/README.md:
--------------------------------------------------------------------------------
1 | # Ressources
2 |
3 | General ressources
4 |
5 | - [Security contact](./contact.md)
6 | - [Blog posts](./tob_blogposts.md)
7 |
--------------------------------------------------------------------------------
/static/TOB_Black.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/static/custom.css:
--------------------------------------------------------------------------------
1 | .sidebar .sidebar-scrollbox .sidebar-book-logo img {
2 | display: block;
3 | margin-left: auto;
4 | margin-right: auto;
5 | width: 50%;
6 | max-width: max-content;
7 | }
8 |
9 | /* program-analysis/echidna/configuration.md */
10 | h1#configuration-options ~ div > table {
11 | margin: 0;
12 | }
13 |
14 | /* light logo in dark mode themes */
15 | .coal .sidebar-book-logo img,
16 | .navy .sidebar-book-logo img,
17 | .ayu .sidebar-book-logo img,
18 | .rust .sidebar-book-logo img {
19 | filter: invert(1);
20 | }
21 |
22 | /* Reduce medusa logo's size */
23 | #content img[alt="medusa_logo"] {
24 | width: 50%;
25 | margin: 0 auto;
26 | display: block;
27 | }
28 |
--------------------------------------------------------------------------------
/static/script.js:
--------------------------------------------------------------------------------
1 | let script = document.createElement("script");
2 | script.src = "//js.hs-scripts.com/22554992.js";
3 | document.body.append(script);
4 |
5 | let script2 = document.createElement("script");
6 | script2.src = "https://plausible.gateway.trailofbits.com/js/script.js";
7 | script2.setAttribute("data-domain", "secure-contracts.com");
8 | script2.setAttribute("defer", "defer");
9 | document.body.append(script2);
10 |
--------------------------------------------------------------------------------