Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion DssExecLib.address

This file was deleted.

10 changes: 3 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
all :; DAPP_LIBRARIES=' lib/dss-exec-lib/src/DssExecLib.sol:DssExecLib:$(shell cat DssExecLib.address)' \
DAPP_BUILD_OPTIMIZE=0 DAPP_BUILD_OPTIMIZE_RUNS=200 \
DAPP_REMAPPINGS=$$(cat remappings.txt) \
dapp --use solc:0.8.16 build
all :; forge build
clean :; forge clean
# Usage example: make test match=SpellIsCast
test :; ./scripts/test-dssspell-forge.sh no-match="$(no-match)" match="$(match)" block="$(block)"
test-forge :; ./scripts/test-dssspell-forge.sh no-match="$(no-match)" match="$(match)" block="$(block)"
estimate :; ./scripts/estimate-deploy-gas.sh
deploy :; ./scripts/deploy.sh
estimate :; forge build --quiet; BYTECODE=$$(jq -r '.bytecode.object' out/DssSpell.sol/DssSpell.json); GAS=$$(cast estimate --create $$BYTECODE); echo "Estimated gas: $$GAS"
deploy :; ./scripts/deploy.py
deploy-info :; ./scripts/get-deploy-info.sh tx=$(tx)
verify :; ./scripts/verify.py DssSpell $(addr)
flatten :; forge flatten src/DssSpell.sol --output out/flat.sol
Expand Down
50 changes: 22 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,72 +8,66 @@ Staging repo for MakerDAO executive spells.
### Getting Started

```bash
$ git clone git@github.com:makerdao/spells-mainnet.git
$ dapp update
git clone --recurse-submodules git@github.com:sky-ecosystem/spells-mainnet
```

### Build

```bash
$ make
```

### Test (DappTools without Optimizations)

Set `ETH_RPC_URL` to a Mainnet node.

```bash
$ export ETH_RPC_URL=<Mainnet URL>
$ make test
make
```

### Test (Forge without Optimizations)

#### Prerequisites
1. [Install](https://www.rust-lang.org/tools/install) Rust.
2. [Install](https://github.com/gakonst/foundry#forge) Forge.

Install [Foundry](https://github.com/foundry-rs/foundry) latest stable version.

#### Operation
Set `ETH_RPC_URL` to a Mainnet node.

```bash
$ export ETH_RPC_URL=<Mainnet URL>
$ make test-forge
export ETH_RPC_URL=<Mainnet URL>
make test
```

### Deploy

Set `ETH_RPC_URL` to a Mainnet node and ensure `ETH_GAS_LIMIT` is set to a high enough number to deploy the contract.
Provide the following environment variables:
- `ETH_RPC_URL` - a Mainnet RPC URL
- `ETH_KEYSTORE` - a location to the keystore file, e.g. `~/.foundry/keystores/deploy`
- `ETHERSCAN_API_KEY` - an Etherscan API key for spell verification

Then run:

```bash
$ export ETH_RPC_URL=<Mainnet URL>
$ export ETH_GAS_LIMIT=5000000
$ export ETH_GAS_PRICE=$(seth --to-wei 100 "gwei")
$ make deploy
make deploy
```

A few helpful tips to estimate gas. You can use the following to get a
gas estimate for the deploy.
#### Estimating gas needed for deployment

Gas estimation is generally handled by Foundry automatically. However, manual limits can be specified as well, refer to the [`forge create` documentation](https://getfoundry.sh/forge/reference/create/).

You can use the following to get a gas estimate for the deploy:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: You can use the following to get a gas estimate for the deployment:


```bash
make all
make estimate
```

Once you have that, add another million gas as a buffer against
out-of-gas errors. Set ETH_GAS_LIMIT to this value.
out-of-gas errors. Set `ETH_GAS_LIMIT` to this value.

```bash
export ETH_GAS_LIMIT="$((<value from previous step> + 0))"
export ETH_GAS_LIMIT=$(bc <<< "$ETH_GAS_LIMIT + 1000000")
```

You should also check current gas prices on your favorite site
You can also check current gas prices on your favorite site
(e.g. https://ethgasstation.info/) and put that gwei value in the
ETH_GAS_PRICE line.
`ETH_GAS_PRICE`.
Comment on lines 66 to +67

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: (e.g. https://ethgasstation.info/) and set the ETH_GAS_PRICE environment variable accordingly


```bash
export ETH_GAS_PRICE=$(seth --to-wei 420 "gwei")
export ETH_GAS_PRICE=$(cast --to-wei 420 "gwei")
```

### Cast to tenderly
Expand Down
175 changes: 175 additions & 0 deletions scripts/deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#! /usr/bin/env python3
"""
Automates deployment of the DssSpell contract, updates config with deployment details,
runs verification and tests, and commits the resulting changes.
"""

import re
import os
import sys
import subprocess
import json

# Define static variables
CHAIN_ID = "1"
PATH_TO_SPELL = "src/DssSpell.sol"
SPELL_CONTRACT_NAME = "DssSpell"
PATH_TO_CONFIG = "src/test/config.sol"

# Check for uncommitted changes
git_status = subprocess.run(
["git", "status", "--porcelain"], stdout=subprocess.PIPE, text=True, check=True
).stdout.strip()
if git_status:
sys.exit(
"There are uncommitted changes in the repository. Please commit or stash them before running this script"
)

# Check env ETH_RPC_URL is set
ETH_RPC_URL = os.environ.get("ETH_RPC_URL")
if not ETH_RPC_URL:
sys.exit("Please set ETH_RPC_URL environment variable with RPC url")

# Check ETH_RPC_URL is correct
cast_chain_id = subprocess.run(
["cast", "chain-id"], stdout=subprocess.PIPE, text=True, check=True
).stdout.strip()
if cast_chain_id != CHAIN_ID:
sys.exit(
f'Please provide correct ETH_RPC_URL. Currently set to chain id "{cast_chain_id}", expected "{CHAIN_ID}"'
)
print(f"Using chain id {cast_chain_id}")

# Check env ETHERSCAN_API_KEY is set
ETHERSCAN_API_KEY = os.environ.get("ETHERSCAN_API_KEY")
if not ETHERSCAN_API_KEY:
sys.exit("Please set ETHERSCAN_API_KEY environment variable")

# Check env ETH_KEYSTORE is set
ETH_KEYSTORE = os.environ.get("ETH_KEYSTORE")
if not ETH_KEYSTORE:
# Use `cast wallet import --interactive "keystore_name"`
sys.exit("Please set ETH_KEYSTORE environment variable with path to the keystore")

# Build deploy command
deploy_cmd = [
"forge",
"create",
"--no-cache",
"--broadcast",
"--json",
"--keystore",
ETH_KEYSTORE,
# Last argument is the contract itself
f"{PATH_TO_SPELL}:{SPELL_CONTRACT_NAME}",
]

# Deploy the spell
print("Deploying a spell...")
deploy_logs = subprocess.run(
deploy_cmd, stdout=subprocess.PIPE, text=True, check=True
).stdout
print(deploy_logs)


# Helper
def parse_json(raw_data: str, error_type: str):
"""Parses the string as JSON"""
try:
return json.loads(raw_data)
except json.JSONDecodeError:
sys.exit(f"Could not parse {error_type} as JSON")


# Get spell address
deploy_data = parse_json(deploy_logs, "forge create output")
spell_address = deploy_data.get("deployedTo")
if not spell_address:
sys.exit("Could not find address of the deployed spell in the output")
print(f"Extracted spell address: {spell_address}")

# Get spell transaction
tx_hash = deploy_data.get("transactionHash")
if not tx_hash:
sys.exit("Could not find transaction hash in the output")
print(f"Extracted transaction hash: {tx_hash}")

# Get deployed contract block number
tx_block = subprocess.run(
["cast", "tx", tx_hash, "blockNumber"],
stdout=subprocess.PIPE,
text=True,
check=True,
).stdout.strip()
print(f"Fetched transaction block: {tx_block}")

# Get deployed contract timestamp
tx_timestamp = subprocess.run(
["cast", "block", "--field", "timestamp", tx_block],
stdout=subprocess.PIPE,
text=True,
check=True,
).stdout.strip()
print(f"Fetched transaction timestamp: {tx_timestamp}")

# Read config
with open(PATH_TO_CONFIG, "r", encoding="utf-8") as f:
config_content = f.read()

# Edit config
print(f'Editing config file "{PATH_TO_CONFIG}"...')
config_content = re.sub(
r"(\s*deployed_spell:\s*).*(,)",
r"\g<1>address(" + spell_address + r")\g<2>",
config_content,
)
config_content = re.sub(
r"(\s*deployed_spell_block:\s*).*(,)",
r"\g<1>" + tx_block + r"\g<2>",
config_content,
)
config_content = re.sub(
r"(\s*deployed_spell_created:\s*).*(,)",
r"\g<1>" + tx_timestamp + r"\g<2>",
config_content,
)

# Write back to config
with open(PATH_TO_CONFIG, "w", encoding="utf-8") as f:
f.write(config_content)

# Verify the contract
subprocess.run(
[
"make",
"verify",
f"addr={spell_address}",
],
check=True,
)

# Re-run the tests
print("Re-running the tests...")
test_logs = subprocess.run(
["make", "test"], capture_output=True, text=True, check=False
)
print(test_logs.stdout)

if test_logs.returncode != 0:
print(test_logs.stderr)
print("Ensure Tests PASS before commiting the `config.sol` changes!")
sys.exit(test_logs.returncode)

# Commit the changes
print("Commiting changes to the `config.sol`...")
subprocess.run(
[
"git",
"commit",
"-m",
"add deployed spell info",
"--",
PATH_TO_CONFIG,
],
check=True,
)
51 changes: 0 additions & 51 deletions scripts/deploy.sh

This file was deleted.

4 changes: 0 additions & 4 deletions scripts/estimate-deploy-gas.sh

This file was deleted.

5 changes: 0 additions & 5 deletions scripts/test-dssspell-forge.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ do
esac
done

DSS_EXEC_LIB=$(< DssExecLib.address)
echo "Using DssExecLib at: $DSS_EXEC_LIB"
export FOUNDRY_LIBRARIES="lib/dss-exec-lib/src/DssExecLib.sol:DssExecLib:$DSS_EXEC_LIB"
export FOUNDRY_OPTIMIZER=false
export FOUNDRY_OPTIMIZER_RUNS=200
export FOUNDRY_ROOT_CHAINID=1

TEST_ARGS=''
Expand Down
Loading