Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/slither.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ jobs:
- name: Install dependencies
run: |
pip install solc-select
solc-select install 0.5.11
solc-select use 0.5.11
- name: Run Tests
run: |
bash program-analysis/slither/scripts/gh_action_test.sh
1 change: 1 addition & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,5 @@
- [API](./program-analysis/slither/api.md)
- [Exercise 1](./program-analysis/slither/exercise1.md)
- [Exercise 2](./program-analysis/slither/exercise2.md)
- [Exercise 3](./program-analysis/slither/exercise3.md)
- [Resources](./resources/tob_blogposts.md)
3 changes: 2 additions & 1 deletion program-analysis/slither/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ Once you feel confident with the material in this README, proceed to the exercis

- [Exercise 1](./exercise1.md): Function override protection
- [Exercise 2](./exercise2.md): Check for access controls
- [Exercise 3](./exercise3.md): Find variable used in conditional statements

Watch Slither's [code walkthrough](https://www.youtube.com/watch?v=EUl3UlYSluU) to learn about its code structure.
Watch Slither's [code walkthrough](https://www.youtube.com/watch?v=EUl3UlYSluU), or [API walkthrough](https://www.youtube.com/watch?v=Ijf0pellvgw) to learn about its code structure.

## Installation

Expand Down
212 changes: 188 additions & 24 deletions program-analysis/slither/api.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,207 @@
## API Basics
# API Basics

Slither has an API that allows you to explore basic attributes of contracts and their functions.

On a high level there are 6 layers:

- `Slither` - main slither object
- `SlitherCompilationUnit` - group of files used by one call to solc
- `Contract` - contract level
- `Function` - function level
- `Node` - control flow graph
- `SlithrIR` - intermediate representation

Watch our [API walkthrough](https://www.youtube.com/watch?v=Ijf0pellvgw) for more details

## Slither object

To load a codebase:

```python
from slither import Slither
slither = Slither('/path/to/project')
```

### Exploring Contracts and Functions
To load a contract deployed:

A `Slither` object has:
```python
from slither import Slither
slither = Slither('0x..') # assuming the code is verified on etherscan
```

Use `etherscan_api_key` to provide an [Etherscan API KEY](https://docs.etherscan.io/getting-started/viewing-api-usage-statistics)

```python
slither = Slither('0x..', etherscan_api_key='..')
```

You can retrieve the list of compilation units with:

- `sl.compilation_units # array of SlitherCompilationUnit`

## SlitherCompilationUnit object

- ~ group of files used by one call to solc
- Most targets have 1 compilation, but not always true
- Partial compilation for optimization
- Multiple solc version used
- Etc..
- Why compilation unit matters?
- Some APIs might be not intuitive
- Ex: looking for a contract based on the name?
- Can have multiple contracts
- For hacking you can (probably) use the first compilation unit
- `compilation_unit = sl.compilation_units[0]`

A [`SlitherCompilationUnit`](https://github.com/crytic/slither/blob/master/slither/core/compilation_unit.py) has:

- `contracts (list(Contract))`: A list of contracts
- `contracts_derived (list(Contract))`: A list of contracts that are not inherited by another contract (a subset of contracts)
- `get_contract_from_name (str)`: Returns a list of contracts matching the name
- `[structures | enums | events | variables | functions]_top_level`: Top level object

Example

```python
from slither import Slither
sl = Slither("0xdac17f958d2ee523a2206206994597c13d831ec7")
compilation_unit = sl.compilation_units[0]

# Print all the contracts from the USDT address
print([str(c) for c in compilation_unit.contracts])

# Print the most derived contracts from the USDT address
print([str(c) for c in compilation_unit.contracts_derived])
```

```bash
% python test.py
['SafeMath', 'Ownable', 'ERC20Basic', 'ERC20', 'BasicToken', 'StandardToken', 'Pausable', 'BlackList', 'UpgradedStandardToken', 'TetherToken']

['SafeMath', 'UpgradedStandardToken', 'TetherToken']
```

## Contract Object

A [`Contract`](https://github.com/crytic/slither/blob/master/slither/core/declarations/contract.py) object has:

- `name: str`: The name of the contract
- `functions: list[Function]`: A list of functions
- `modifiers: list[Modifier]`: A list of modifiers
- `all_functions_called: list[Function/Modifier]`: A list of all internal functions reachable by the contract
- `inheritance: list[Contract]`: A list of inherited contracts (c3 linearization order)
- `derived_contracts: list[Contract]`: contracts derived from it
- `get_function_from_signature(str): Function`: Returns a Function from its signature
- `get_modifier_from_signature(str): Modifier`: Returns a Modifier from its signature
- `get_state_variable_from_name(str): StateVariable`: Returns a StateVariable from its name
- `state_variables: List[StateVariable]`: list of accessible variables
- `state_variables_ordered: List[StateVariable]`: all variable ordered by declaration

Example

```python
from slither import Slither
sl = Slither("0xdac17f958d2ee523a2206206994597c13d831ec7")
compilation_unit = sl.compilation_units[0]

A `Contract` object has:

- `name (str)`: The name of the contract
- `functions (list(Function))`: A list of functions
- `modifiers (list(Modifier))`: A list of modifiers
- `all_functions_called (list(Function/Modifier))`: A list of all internal functions reachable by the contract
- `inheritance (list(Contract))`: A list of inherited contracts
- `get_function_from_signature (str)`: Returns a Function from its signature
- `get_modifier_from_signature (str)`: Returns a Modifier from its signature
- `get_state_variable_from_name (str)`: Returns a StateVariable from its name

A `Function` or a `Modifier` object has:

- `name (str)`: The name of the function
- `contract (contract)`: The contract where the function is declared
- `nodes (list(Node))`: A list of nodes composing the CFG of the function/modifier
- `entry_point (Node)`: The entry point of the CFG
- `variables_read (list(Variable))`: A list of variables read
- `variables_written (list(Variable))`: A list of variables written
- `state_variables_read (list(StateVariable))`: A list of state variables read (a subset of `variables_read`)
- `state_variables_written (list(StateVariable))`: A list of state variables written (a subset of `variables_written`)
# Print all the state variables of the USDT token
contract = compilation_unit.get_contract_from_name("TetherToken")[0]
print([str(v) for v in contract.state_variables])
```

```bash
% python test.py
['owner', 'paused', '_totalSupply', 'balances', 'basisPointsRate', 'maximumFee', 'allowed', 'MAX_UINT', 'isBlackListed', 'name', 'symbol', 'decimals', 'upgradedAddress', 'deprecated']
```

## Function object

A [`Function`](https://github.com/crytic/slither/blob/master/slither/core/declarations/function.py) or a `Modifier` object has:

- `name: str`: The name of the function
- `contract: Contract`: The contract where the function is declared
- `nodes: list[Node]`: A list of nodes composing the CFG of the function/modifier
- `entry_point: Node`: The entry point of the CFG
- `[state |local]_variable_[read |write]: list[StateVariable]`: A list of local/state variables read/write
- All can be prefixed by “all\_” for recursive lookup
- Ex: `all_state_variable_read`: return all the state variables read in internal calls
- `slithir_operations: List[Operation]`: list of IR operations

```python
from slither import Slither
sl = Slither("0xdac17f958d2ee523a2206206994597c13d831ec7")
compilation_unit = sl.compilation_units[0]
contract = compilation_unit.get_contract_from_name("TetherToken")[0]

transfer = contract.get_function_from_signature("transfer(address,uint256)")

# Print all the state variables read by the transfer function
print([str(v) for v in transfer.state_variables_read])
# Print all the state variables read by the transfer function and its internal calls
print([str(v) for v in transfer.all_state_variables_read])
```

```bash
% python test.py
['deprecated', 'isBlackListed', 'upgradedAddress']
['owner', 'basisPointsRate', 'deprecated', 'paused', 'isBlackListed', 'maximumFee', 'upgradedAddress', 'balances']
```

## Node object

[Node](https://github.com/crytic/slither/blob/master/slither/core/cfg/node.py)

To explore the nodes:

- If order does not matter
- `for node in function.nodes`
- If order matters, walk through the nodes

```python
def visit_node(node: Node, visited: List[Node]):

if node in visited:
return
visited += [node]

# custom action
for son in node.sons:
visit_node(son, visited)
```

- If need to iterate more than once (advanced usages)
- Bound the iteration X times
- Create a fix-point - abstract interpretation style analysis

## SlithIR

- [slither/slithir](https://github.com/crytic/slither/tree/master/slither/slithir)
- Every IR operation has its own methods
- Check if an operation is of a type:
- `isinstance(ir, TYPE)`
- Ex: `isinstance(ir, Call)`
- Check if the operation is an addition
- `isinstance(ir, Binary) & ir.type == BinaryType.ADDITION`
- Check if the operation is a call to MyContract
- `isinstance(ir, HighLevelCall) & ir.destination == MyContract`

```python
from slither import Slither
sl = Slither("0xdac17f958d2ee523a2206206994597c13d831ec7")
compilation_unit = sl.compilation_units[0]
contract = compilation_unit.get_contract_from_name("TetherToken")[0]
totalSupply = contract.get_function_from_signature("totalSupply()")

# Print the external call made in the totalSupply function
for ir in totalSupply.slithir_operations:
if isinstance(ir, HighLevelCall):
print(f"External call found {ir} ({ir.node.source_mapping})")
```

```bash
% python test.py
External call found HIGH_LEVEL_CALL, […] (...TetherToken.sol#339)
```

### Example: Print Basic Information

Expand Down
6 changes: 3 additions & 3 deletions program-analysis/slither/examples/coin.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.5.0;
pragma solidity ^0.8.0;

contract Coin {
address owner = msg.sender;

mapping(address => uint256) balances;

// _mint must not be overriden
function _mint(address dst, uint256 val) internal {
function _mint(address dst, uint256 val) internal virtual {
require(msg.sender == owner);
balances[dst] += val;
}
Expand All @@ -20,7 +20,7 @@ contract Coin {
contract MyCoin is Coin {
event Mint(address, uint256);

function _mint(address dst, uint256 val) internal {
function _mint(address dst, uint256 val) internal override {
balances[dst] += val;
emit Mint(dst, val);
}
Expand Down
8 changes: 5 additions & 3 deletions program-analysis/slither/exercise1.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
# Exercise 1: Function Overridden Protection

The goal is to create a script that fills in a missing feature of Solidity: function overriding protection.
The goal is to create a script that performs a feature that was not present in previous version of Solidity: function overriding protection.

[exercises/exercise1/coin.sol](exercises/exercise1/coin.sol) contains a function that must never be overridden:
[exercises/exercise1/coin.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/slither/exercises/exercise1/coin.sol) contains a function that must never be overridden:

```solidity
_mint(address dst, uint256 val)
```

Use Slither to ensure that no contract inheriting Coin overrides this function.

Use `solc-select install 0.5.0 && solc-select use 0.5.0` to switch to solc 0.5.0

## Proposed Algorithm

```
Expand All @@ -28,4 +30,4 @@ Get the Coin contract

## Solution

See [exercises/exercise1/solution.py](exercises/exercise1/solution.py).
See [exercises/exercise1/solution.py](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/slither/exercises/exercise1/solution.py).
4 changes: 2 additions & 2 deletions program-analysis/slither/exercise2.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Exercise 2: Access Control

The [exercises/exercise2/coin.sol](exercises/exercise2/coin.sol) file contains an access control implementation with the `onlyOwner` modifier. A common mistake is forgetting to add the modifier to a crucial function. In this exercise, we will use Slither to implement a conservative access control approach.
The [exercises/exercise2/coin.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/slither/exercises/exercise2/coin.sol) file contains an access control implementation with the `onlyOwner` modifier. A common mistake is forgetting to add the modifier to a crucial function. In this exercise, we will use Slither to implement a conservative access control approach.

Our goal is to create a script that ensures all public and external functions call `onlyOwner`, except for the functions on the whitelist.

Expand All @@ -18,4 +18,4 @@ Explore all the functions

## Solution

Refer to [exercises/exercise2/solution.py](exercises/exercise2/solution.py) for the solution.
Refer to [exercises/exercise2/solution.py](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/slither/exercises/exercise2/solution.py) for the solution.
13 changes: 13 additions & 0 deletions program-analysis/slither/exercise3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Exercise 3: Find function that use a given variable in a condition

The [exercises/exercise3/find.sol](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/slither/exercises/exercise3/find.sol) file contains a contract that use `my_variable` variable in multiple locations.

Our goal is to create a script that list all the functions that use `my_variable` in a conditional or require statement.

## Proposed Approach

Explore all the helpers provided by [`Function`](https://github.com/crytic/slither/blob/master/slither/core/declarations/function.py) object to find an easy way to reach the goal

## Solution

Refer to [exercises/exercise3/solution.py](https://github.com/crytic/building-secure-contracts/tree/master/program-analysis/slither/exercises/exercise3/solution.py) for the solution.
2 changes: 1 addition & 1 deletion program-analysis/slither/exercises/exercise2/coin.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.5.0;
pragma solidity ^0.8.0;

contract Owned {
address public owner;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The function using "a" in condition are ['condition', 'call_require']
15 changes: 15 additions & 0 deletions program-analysis/slither/exercises/exercise3/find.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
contract Find {
uint my_variable;

function condition() public {
if (my_variable == 0) {}
}

function call_require() public {
require(my_variable == 0);
}

function read_and_write() public {
my_variable = my_variable + 1;
}
}
20 changes: 20 additions & 0 deletions program-analysis/slither/exercises/exercise3/solution.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from slither.slither import Slither

slither = Slither('find.sol')
find = slither.get_contract_from_name('Find')[0]

assert find

# Get the variable
my_variable = find.get_state_variable_from_name("my_variable")
assert my_variable


function_using_a_as_condition = [
f
for f in find.functions
if f.is_reading_in_conditional_node(my_variable) or f.is_reading_in_require_or_assert(my_variable)
]

# Print the result
print(f'The function using "a" in condition are {[f.name for f in function_using_a_as_condition]}')
Loading