Skip to content

Commit 02b5cc6

Browse files
add HyperBurner contract, init library and tests
1 parent 3efecea commit 02b5cc6

16 files changed

Lines changed: 1186 additions & 1 deletion

.editorconfig

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# top-most EditorConfig file
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
end_of_line = lf
7+
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.{sol,sh,py}]
12+
indent_style = space
13+
indent_size = 4
14+
15+
[*.{js,ts,json,y{a,}ml}]
16+
indent_style = space
17+
indent_size = 2
18+
19+
[*.{diff,md}]
20+
indent_style = space
21+
indent_size = 4
22+
trim_trailing_whitespace = false
23+
24+
[foundry.toml]
25+
indent_style = space
26+
indent_size = 4

.github/workflows/test.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
workflow_dispatch:
6+
push:
7+
branches:
8+
- main
9+
10+
env:
11+
FOUNDRY_PROFILE: ci
12+
13+
jobs:
14+
foundry:
15+
name: Foundry project
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
with:
20+
submodules: recursive
21+
22+
- name: Install Foundry
23+
uses: foundry-rs/foundry-toolchain@v1
24+
with:
25+
version: stable
26+
27+
- name: Run Forge build
28+
run: |
29+
forge build --sizes
30+
id: build
31+
32+
- name: Run Forge tests
33+
run: |
34+
forge test -vvv
35+
id: test
36+
env:
37+
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
38+
39+
- name: Run Forge coverage
40+
run: |
41+
forge coverage
42+
id: coverage
43+
env:
44+
MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
45+
46+
natspec:
47+
name: Natspec check
48+
runs-on: ubuntu-latest
49+
steps:
50+
- uses: actions/checkout@v4
51+
with:
52+
submodules: recursive
53+
- uses: actions/setup-node@v2
54+
- run: sh -c "npx --quiet --yes @defi-wonderland/natspec-smells@1.1.3 --enforceInheritdoc=false --include='src/!(*Interfaces).sol'" 2>&1 | grep --quiet "No issues found"

.gitignore

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Compiler files
2+
cache/
3+
out/
4+
5+
# Ignores development broadcast logs
6+
!/broadcast
7+
/broadcast/*/31337/
8+
/broadcast/**/dry-run/
9+
10+
# Ignores coverage reports
11+
lcov.info
12+
13+
# Docs
14+
docs/
15+
16+
# Dotenv file
17+
.env
18+
19+
# Node modules
20+
node_modules/
21+
# Build output
22+
dist/

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "lib/dss-test"]
2+
path = lib/dss-test
3+
url = https://github.com/makerdao/dss-test

README.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,55 @@
1-
# sky-hyper-burn
1+
# Sky Hyper Burner
2+
3+
A short-term solution for implementing a buyback mechanism on Hyperliquid.
4+
5+
## Overview
6+
7+
**HyperBurner** pulls USDS/DAI from the Sky surplus, swaps it for USDC via LitePSM without a fee, and then sends the USDC to the configured EOA. The remaining actions, such as "swapping USDC into SKY" and "returning SKY to the Pause Proxy", are performed off-chain by the authorized EOA.
8+
9+
The contract has two main functions:
10+
11+
- **`grab`**
12+
- Only callable by the `bot` address
13+
- Only callable once every `hop` seconds
14+
- Only callable if the surplus buffer is higher than `hump` + `bump`
15+
- Withdraws `bump` amount of DAI from the surplus buffer
16+
- Swaps DAI for USDC with no fee via DssLitePsm
17+
- Sends the swapped USDC to the designated `bot` address
18+
- **`freeze`**
19+
- Only callable by the `freezer` address
20+
- Prevents further `grab`s by denying itself from vat
21+
22+
## Environment Variables
23+
24+
Copy [`env.example`](./env.example) and update the values as described below:
25+
26+
| Variable | Description |
27+
|-----------------------|--------------------------------------------------------------------|
28+
| `MAINNET_RPC_URL` | RPC URL for Ethereum mainnet |
29+
| `ETHERSCAN_API_KEY` | Etherscan API key to verify deployed contracts |
30+
31+
## Quick Start
32+
33+
### Testing
34+
35+
To run integration tests, set the `MAINNET_RPC_URL` environment variable to a valid Mainnet node or use a `.env` file:
36+
37+
```bash
38+
MAINNET_RPC_URL='...' forge test -vvv
39+
# or using `.env` file
40+
forge test -vvv
41+
```
42+
43+
### Deployment (Mainnet)
44+
45+
Before running the deployment script, ensure all environment variables are set.
46+
47+
- Dry-run the transaction to estimate the required gas:
48+
```sh
49+
forge script script/Deploy.s.sol:Deploy --fork-url mainnet
50+
```
51+
52+
- Deploy the contract and verify the deployed contract:
53+
```sh
54+
forge script script/Deploy.s.sol:Deploy --fork-url mainnet --broadcast --verify --account $KEYSTORE_NAME
55+
```

deploy/HyperBurnerDeploy.sol

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-FileCopyrightText: © 2025 Dai Foundation <www.daifoundation.org>
2+
// SPDX-License-Identifier: AGPL-3.0-or-later
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
pragma solidity ^0.8.21;
18+
19+
import {ScriptTools} from "dss-test/ScriptTools.sol";
20+
import {HyperBurner} from "../src/HyperBurner.sol";
21+
22+
struct HyperBurnerDeployParams {
23+
address deployer;
24+
address owner;
25+
address daiJoin;
26+
address vow;
27+
address psm;
28+
}
29+
30+
library HyperBurnerDeploy {
31+
function deploy(HyperBurnerDeployParams memory p) internal returns (address hyperBurner) {
32+
hyperBurner = address(new HyperBurner(
33+
p.daiJoin,
34+
p.vow,
35+
p.psm
36+
));
37+
38+
ScriptTools.switchOwner(hyperBurner, p.deployer, p.owner);
39+
}
40+
}

deploy/HyperBurnerInit.sol

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// SPDX-FileCopyrightText: © 2025 Dai Foundation <www.daifoundation.org>
2+
// SPDX-License-Identifier: AGPL-3.0-or-later
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
pragma solidity >=0.8.0;
18+
19+
import {DssInstance} from "dss-test/MCD.sol";
20+
21+
interface HyperBurnerLike {
22+
function psm() external view returns (address);
23+
function vat() external view returns (address);
24+
function daiJoin() external view returns (address);
25+
function dai() external view returns (address);
26+
function usdc() external view returns (address);
27+
function vow() external view returns (address);
28+
function gemTo18ConversionFactor() external view returns (uint256);
29+
function file(bytes32 what, address data) external;
30+
function file(bytes32 what, uint256 data) external;
31+
function wards(address usr) external view returns (uint256);
32+
}
33+
34+
interface PSMLike {
35+
function to18ConversionFactor() external view returns (uint256);
36+
function kiss(address usr) external;
37+
}
38+
39+
struct HyperBurnerInitConfig {
40+
address bot;
41+
address freezer;
42+
address psm;
43+
uint256 bump;
44+
uint256 hump;
45+
uint256 hop;
46+
bytes32 hyperBurnerKey;
47+
}
48+
49+
library HyperBurnerInit {
50+
uint256 constant internal RAD = 10**45;
51+
52+
/// @notice Initializes HyperBurner contract with the specified configuration.
53+
/// @dev Sets up permissions and configures parameters.
54+
/// @param dss The DSS (MakerDAO) instance containing core contract references.
55+
/// @param hyperBurner_ The HyperBurner contract address to initialize.
56+
/// @param cfg The configuration parameters for HyperBurner.
57+
function init(
58+
DssInstance memory dss,
59+
address hyperBurner_,
60+
HyperBurnerInitConfig memory cfg
61+
) internal {
62+
HyperBurnerLike hyperBurner = HyperBurnerLike(hyperBurner_);
63+
PSMLike psm = PSMLike(cfg.psm);
64+
65+
// Sanity checks
66+
require(cfg.bot != address(0), "HyperBurnerInit: zero bot");
67+
require(cfg.bump >= RAD, "HyperBurnerInit: bump too low");
68+
require(cfg.hump >= RAD, "HyperBurnerInit: hump too low");
69+
require(cfg.hop > 0, "HyperBurnerInit: zero hop");
70+
require(cfg.psm == hyperBurner.psm(), "HyperBurnerInit: PSM mismatch");
71+
require(hyperBurner.wards(dss.chainlog.getAddress("MCD_PAUSE_PROXY")) == 1, "HyperBurnerInit: MCD_PAUSE_PROXY not relied");
72+
require(psm.to18ConversionFactor() == hyperBurner.gemTo18ConversionFactor(), "HyperBurnerInit: gemTo18ConversionFactor mismatch");
73+
require(hyperBurner.daiJoin() == dss.chainlog.getAddress("MCD_JOIN_DAI"), "HyperBurnerInit: DaiJoin mismatch");
74+
require(hyperBurner.dai() == address(dss.dai), "HyperBurnerInit: Dai mismatch");
75+
require(hyperBurner.vat() == address(dss.vat), "HyperBurnerInit: Vat mismatch");
76+
require(hyperBurner.vow() == address(dss.vow), "HyperBurnerInit: Vow mismatch");
77+
78+
// Approve the HyperBurner contract to swap without fee on PSM
79+
psm.kiss(address(hyperBurner));
80+
81+
// Authorize HyperBurner to vat
82+
dss.vat.rely(hyperBurner_);
83+
84+
// Set bot
85+
hyperBurner.file("bot", cfg.bot);
86+
// Set freezer
87+
hyperBurner.file("freezer", cfg.freezer);
88+
// Set bump
89+
hyperBurner.file("bump", cfg.bump);
90+
// Set hump
91+
hyperBurner.file("hump", cfg.hump);
92+
// Set hop
93+
hyperBurner.file("hop", cfg.hop);
94+
95+
// Add HyperBurner address to chainlog
96+
dss.chainlog.setAddress(cfg.hyperBurnerKey, hyperBurner_);
97+
}
98+
}

env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
MAINNET_RPC_URL = ADD_YOUR_MAINNET_RPC_URL_HERE
2+
ETHERSCAN_API_KEY = ADD_YOUR_ETHERSCAN_API_KEY_HERE

foundry.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[profile.default]
2+
src = "src"
3+
out = "out"
4+
libs = ["lib"]
5+
solc = "0.8.21"
6+
optimizer = true
7+
optimizer_runs = 200
8+
no_match_coverage = "script"
9+
10+
[rpc_endpoints]
11+
mainnet = "${MAINNET_RPC_URL}"
12+
13+
[etherscan]
14+
mainnet = {key = "${ETHERSCAN_API_KEY}", chain = 1 }
15+
16+
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options

lib/dss-test

Submodule dss-test added at 61cf29f

0 commit comments

Comments
 (0)