Skip to content

Commit

Permalink
feat: mvp safe liveness module (#27)
Browse files Browse the repository at this point in the history
Includes all the:
- Smart contracts logic
- Close to 100% unit test coverage
- Integration tests
- Deployment scripts
- Documentation
  • Loading branch information
0xOneTony authored Nov 28, 2023
2 parents b413650 + dc0d88a commit e3e0f0f
Show file tree
Hide file tree
Showing 65 changed files with 4,593 additions and 2,145 deletions.
20 changes: 17 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
MAINNET_RPC=
GOERLI_RPC=
OPTIMISM_RPC=
OPTIMISM_GOERLI_RPC=

# Deployer private key for the Integrations Tests
MAINNET_DEPLOYER_PK=
# Searcher PK for Integration Tests
SEARCHER_PK=

GOERLI_RPC=
GOERLI_DEPLOYER_PK=
# Mainnet rpc for the Integration Tests, should be the ganache url
MAINNET_INTEGRATION_TESTS_RPC=

## For deployment scripts
DEPLOYER_MAINNNET_PRIVATE_KEY=
DEPLOYER_GOERLI_PRIVATE_KEY=
DEPLOYER_OPTIMISM_PRIVATE_KEY=
DEPLOYER_OPTIMISM_GOERLI_PRIVATE_KEY=
STORAGE_MIRROR_ADDRESS=

ETHERSCAN_API_KEY=
ETHERSCAN_API_KEY=
1 change: 0 additions & 1 deletion .github/CODEOWNERS

This file was deleted.

3 changes: 3 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 🤖 Linear

Closes SAF-XXX
21 changes: 16 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ concurrency:

jobs:
forge:
name: Run Unit and E2E Tests
name: Run Unit and Integration Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
Expand All @@ -27,19 +27,30 @@ jobs:
- name: Install dependencies
run: yarn --frozen-lockfile --network-concurrency 1

- name: Precompile using 0.8.14 and via-ir=false
- name: setup python
uses: actions/setup-python@v4
with:
python-version: '3.10' # install the python version needed

- name: install python packages
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Precompile using 0.8.19 and via-ir=false
run: yarn build

- name: "Create env file"
run: |
touch .env
echo MAINNET_RPC="${{ secrets.MAINNET_RPC }}" >> .env
echo GOERLI_RPC="${{ secrets.GOERLI_RPC }}" >> .env
echo MAINNET_INTEGRATION_TESTS_RPC="${{ secrets.MAINNET_INTEGRATION_TESTS_RPC }}" >> .env
echo SEARCHER_PK="${{ secrets.SEARCHER_PK }}" >> .env
echo MAINNET_DEPLOYER_PK="${{ secrets.MAINNET_DEPLOYER_PK }}" >> .env
cat .env
- name: Run tests
shell: bash
run: yarn test
run: yarn test:integration-workflow

forge-optimized:
name: Run Optimized Unit Tests
Expand Down
13 changes: 12 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,18 @@ out-via-ir

# Keep related abi
out/*/*
!out/Greeter.sol/*

# Keep the latest deployment only
broadcast/*/*/*

# Docs build files
docs/book
docs/src/solidity/interfaces
docs/src/static

# Cache for the python scripts
proofs/__pycache__/*

# Deployments for Integration tests
solidity/scripts/deployments/*.json
proofs/proof.json
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# 1. Build the contracts
# 2. Stage build output
# 2. Lint and stage style improvements
yarn build && git add out && npx lint-staged
yarn build && npx lint-staged
3 changes: 2 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"const-name-snakecase": "off",
"no-inline-assembly": "off",
"no-empty-blocks": "off",
"func-named-parameters": ["error", 4],
"private-vars-leading-underscore": ["warn", { "strict": false }],
"defi-wonderland/non-state-vars-leading-underscore": ["warn"],
"defi-wonderland/contract-data-order": ["warn"],
Expand All @@ -25,4 +26,4 @@
"defi-wonderland/wonder-var-name-mixedcase": ["warn"],
"avoid-low-level-calls": "off"
}
}
}
32 changes: 32 additions & 0 deletions .solhint.tests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"extends": "solhint:recommended",
"plugins": ["defi-wonderland"],
"rules": {
"compiler-version": ["off"],
"constructor-syntax": "warn",
"quotes": ["error", "single"],
"func-visibility": ["warn", { "ignoreConstructors": true }],
"not-rely-on-time": "off",
"func-name-mixedcase": "off",
"var-name-mixedcase": "off",
"const-name-snakecase": "off",
"no-inline-assembly": "off",
"no-empty-blocks": "off",
"contract-name-camelcase": "off",
"event-name-camelcase": "off",
"func-named-parameters": "off",
"no-global-import": "off",
"max-states-count": "off",
"private-vars-leading-underscore": ["warn", { "strict": false }],
"defi-wonderland/non-state-vars-leading-underscore": ["warn"],
"defi-wonderland/contract-data-order": ["warn"],
"defi-wonderland/enum-name-camelcase": ["warn"],
"defi-wonderland/immutable-name-snakecase": ["warn"],
"defi-wonderland/interface-member-order": ["warn"],
"defi-wonderland/interface-starts-with-i": ["warn"],
"defi-wonderland/named-return-values": ["warn"],
"defi-wonderland/struct-name-camelcase": ["warn"],
"defi-wonderland/wonder-var-name-mixedcase": ["warn"],
"avoid-low-level-calls": "off"
}
}
3 changes: 3 additions & 0 deletions .solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
cache
out
134 changes: 64 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,107 +1,101 @@
<img src="https://raw.githubusercontent.com/defi-wonderland/brand/v1.0.0/external/solidity-foundry-boilerplate-banner.png" alt="wonderland banner" align="center" />
<br />
# Safe Liveness

<div align="center"><strong>Start your next Solidity project with Foundry in seconds</strong></div>
<div align="center">A highly scalable foundation focused on DX and best practices</div>
⚠️ The code has not been audited yet, tread with caution.

<br />
## Overview

## Features
Safe-Liveness is a module that will tackle the liveness problem, one of the main challenges faced by smart wallets to improve cross-chain user experience.

<dl>
<dt>Sample contracts</dt>
<dd>Basic Greeter contract with an external interface.</dd>
Unlike EOAs, smart wallets have configuration settings, which can cause synchronization problems across chains. Consequently, SAFEs on different chains function as separate contracts, even though they may share the same address and configuration parameters during deployment. This problem becomes critical when there’s a change in the owners’ list.

<dt>Foundry setup</dt>
<dd>Foundry configuration with multiple custom profiles and remappings.</dd>

<dt>Deployment scripts</dt>
<dd>Sample scripts to deploy contracts on both mainnet and testnet.</dd>

<dt>Sample e2e & unit tests</dt>
<dd>Example tests showcasing mocking, assertions and configuration for mainnet forking. As well it includes everything needed in order to check code coverage.</dd>

<dt>Linter</dt>
<dd>Simple and fast solidity linting thanks to forge fmt</a>.</dd>

<dt>Github workflows CI</dt>
<dd>Run all tests and see the coverage as you push your changes.</dd>
</dl>
We will create a module that can verify Safe ownership based on a storage proof, allowing you to easily broadcast any changes in your Safe to other chains.

## Setup

1. Install Foundry by following the instructions from [their repository](https://github.com/foundry-rs/foundry#installation).
2. Copy the `.env.example` file to `.env` and fill in the variables.
3. Install the dependencies by running: `yarn install`. In case there is an error with the commands, run `foundryup` and try them again.
This project uses [Foundry](https://book.getfoundry.sh/). To build it locally, run:

## Build

The default way to build the code is suboptimal but fast, you can run it via:

```bash
```sh
git clone [email protected]:defi-wonderland/safe-liveness.git
cd safe-liveness
yarn install
yarn build
```

In order to build a more optimized code ([via IR](https://docs.soliditylang.org/en/v0.8.15/ir-breaking-changes.html#solidity-ir-based-codegen-changes)), run:
## Integration Tests

```bash
yarn build:optimized
```
In order to run the integration tests you will need python setup to generate the proofs, ganache running and some enviroment variables.

## Running tests
1. Set up python and install requirements

Unit tests should be isolated from any externalities, while E2E usually run in a fork of the blockchain. In this boilerplate you will find example of both.
```sh
python -m pip install --upgrade pip
pip install -r requirements.txt
```

In order to run both unit and E2E tests, run:
2. Run ganache

```bash
yarn test
```sh
yarn ganache
```

In order to just run unit tests, run:
3. Set enviroment variables

```bash
yarn test:unit
```
`MAINNET_INTEGRATION_TESTS_RPC` should be the ganache endpoint
`MAINNET_DEPLOYER_PK` should be the deployer of the protocol and a safe owner
`SEARCHER_PK` should be the incentivized actor to verify

In order to run unit tests and run way more fuzzing than usual (5x), run:
4. Run the tests

```bash
yarn test:unit:deep
```sh
yarn test:integration
```

In order to just run e2e tests, run:
### Available Commands

```bash
yarn test:e2e
```
| Yarn Command | Description |
| ----------------------- | ---------------------------------------------------------- |
| `yarn build` | Compile all contracts. |
| `yarn coverage` | See `forge coverage` report. |
| `yarn deploy` | Deploy the contracts to Mainnet. |
| `yarn test` | Run all unit and integration tests. |
| `yarn test:unit` | Run unit tests. |
| `yarn test:integration` | Run integration tests. |
| `yarn deploy:mainnet` | Deploys Home Chain contracts to Mainnet |
| `yarn deploy:optimism` | Deploys Non-Home Chain contracts to Optimism |
| `yarn deploy:goerli` | Deploys Home Chain contracts to Goerli |
| `yarn deploy:optimismGoerli`| Deploys Non-Home Chain contracts to Optimism Goerli |
| `yarn docs:build` | Build the docs |
| `yarn docs:run` | Runs the docs, needs mdbook |
| `yarn ganache` | Spawn a ganache instance |

In order to check your current code coverage, run:

```bash
yarn coverage
```
## Smart Contracts

<br>
### Home Chain
- `UpdateStorageMirrorGuard`: This guard is responsible for calling the GuardCallbackModule when a change in the settings of a safe is executed.
- `GuardCallbackModule`: This contract is a module that is used to save the updated settings to the StorageMirror.
- `StorageMirror`: This contract is a storage of information about the safe’s settings. All safe’s settings changes should be mirrored in this contract and be saved. In the end, this contract’s storage root is gonna be used to see if a proposed update on the non-home chain is valid.

## Deploy & verify
### Non-Home Chain
- `BlockHeaderOracle`: This contract's purpose is to return the latest stored L1 block header and timestamp. Every X minutes a "magical" off-chain agent provides the latest block header and timestamp.
- `NeedsUpdateGuard`: This guard should prevent the safe from executing any transaction if an update is needed. An update is needed based on the owner's security settings that was inputed.
- `VerifierModule`: This contract is the verifier module that verifies the settings of a safe against the StorageMirror on the home chain.
- `StorageMirrorRootRegistry`: This contract should accept and store storageRoots of the StorageMirror contract in L1.

### Setup

Configure the `.env` variables.
## ⚠️ Warnings

### Goerli
The project is a PoC implementation and should be treated with caution. Bellow we describe some cases that should be taken into account before using the modules/guard.

```bash
yarn deploy:goerli
```
- `UpdateStorageMirrorGuard` for the PoC this guard is calling the `GuardCallbackModule` in every call. A possible improvement would be to decode the txData, on the guard `checkTransaction` pre-execute hook, and filter against certain function signatures that change the settings of a Safe to accurately catch the change.
- `NeedsUpdateGuard` this guard on the non-home chain can brick the user's safe, since it will block every tx, if their security settings expire. Also it's worth mentioning that before using the guard the safe owner must verify at least 1 set of settings using the VerifierModule in order for the guard to have a point of reference for the latest verified update.
- `VerifierModule` is executing a safeTx after the verification and update of their settings. This safeTx can become invalid since the signatures passed were created before the change of the settings, in this case the user(s) will need to re-sign the tx manually outside of the UI. A possible improvement would be to have a custom safe app that let's you sign even if you are not a "current owner" but are a "potential future owner" of the "soon-to-be-updated" settings
- `VerifierModule` makes the assumption that the address of the safe is the same on both the home chain, and non-home chain. The current implementation will not work if these addresses are different

### Mainnet
## Contributors

```bash
yarn deploy:mainnet
```
Safe-Liveness was built with ❤️ by [Wonderland](https://defi.sucks).

The deployments are stored in ./broadcast
Wonderland is a team of top Web3 researchers, developers, and operators who believe that the future needs to be open-source, permissionless, and decentralized.

See the [Foundry Book for available options](https://book.getfoundry.sh/reference/forge/forge-create.html).
[DeFi sucks](https://defi.sucks), but Wonderland is here to make it better.
38 changes: 38 additions & 0 deletions build-docs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash

# generate docs in a temporary directory
FOUNDRY_PROFILE=docs forge doc --out tmp/safe-liveness-technical-docs

# edit generated summary not to have container pages
# - [jobs](solidity/interfaces/jobs/README.md)
# should become
# - [jobs]()
# TODO

# edit generated summary titles to start with an uppercase letter
# - [jobs]()
# should become
# - [Jobs]()
# TODO

# edit the SUMMARY after the Interfaces section
# https://stackoverflow.com/questions/67086574/no-such-file-or-directory-when-using-sed-in-combination-with-find
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' -e '/\[Interfaces\]/q' docs/src/SUMMARY.md
else
sed -i -e '/\[Interfaces\]/q' docs/src/SUMMARY.md
fi
# copy the generated SUMMARY, from the tmp directory, without the first 5 lines
# and paste them after the Interfaces section on the original SUMMARY
tail -n +5 tmp/safe-liveness-technical-docs/src/SUMMARY.md >> docs/src/SUMMARY.md

# delete old generated interfaces docs
rm -rf docs/src/solidity/interfaces
# there are differences in cp and mv behavior between UNIX and macOS when it comes to non-existing directories
# creating the directory to circumvent them
mkdir -p docs/src/solidity/interfaces
# move new generated interfaces docs from tmp to original directory
cp -R tmp/safe-liveness-technical-docs/src/solidity/interfaces docs/src/solidity/

# delete tmp directory
rm -rf tmp
14 changes: 14 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!-- TODO: add links to oracles.rip, to mirror and to twitter -->

# Overview

- [Smart Contracts]()

- [Addresses](content/smart-contracts/addresses.md)
- [Interfaces]()
- [IBlockHeaderOracle](solidity/interfaces/IBlockHeaderOracle.sol/interface.IBlockHeaderOracle.md)
- [IGuardCallbackModule](solidity/interfaces/IGuardCallbackModule.sol/interface.IGuardCallbackModule.md)
- [ISafe](solidity/interfaces/ISafe.sol/interface.ISafe.md)
- [IStorageMirror](solidity/interfaces/IStorageMirror.sol/interface.IStorageMirror.md)
- [IStorageMirrorRootRegistry](solidity/interfaces/IStorageMirrorRootRegistry.sol/interface.IStorageMirrorRootRegistry.md)
- [IVerifierModule](solidity/interfaces/IVerifierModule.sol/interface.IVerifierModule.md)
1 change: 1 addition & 0 deletions docs/src/content/smart-contracts/addresses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Addresses
Loading

0 comments on commit e3e0f0f

Please sign in to comment.