-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Post-hackathon changes #1
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
9ac25a9
refactor, more tests, add verifyBountyCanPay(), update README
merklejerk e177bcf
update readme and GH actions workflow
merklejerk f1798bc
redeploy
merklejerk 7174cef
Update README.md
merklejerk 31d15e0
Update README.md
merklejerk 75f3321
Update src/HoneyPause.sol
merklejerk 9516fe7
Update src/HoneyPause.sol
merklejerk a9c5706
Update README.md
merklejerk 10f79fb
Update README.md
merklejerk 6ce38ac
Update README.md
merklejerk ceff588
Update README.md
merklejerk 6ac1800
add codeowners
merklejerk fb5891a
update README
merklejerk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
name: test | ||
|
||
on: workflow_dispatch | ||
on: push | ||
|
||
env: | ||
FOUNDRY_PROFILE: ci | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
* @merklejerk @justinschuldt @JordanCason | ||
src/ @merklejerk |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,71 +1,71 @@ | ||
# HoneyPause | ||
|
||
HoneyPause is a permissionless on-chain exploit bounty tied to a circuit breaker. HoneyPause lets whitehats safely and atomically prove a smart contract exploit <i>on-chain</i>, pause the affected protocol, then collect a bounty. Protocols can opt into the system by registering a bounty on the smart contract. The entire system is permissionless, non-custodial, and free! | ||
HoneyPause is an onchain exploit bounty linked to a circuit breaker. HoneyPause lets whitehats safely and atomically prove a smart contract exploit on chain, *pause* the affected protocol, then collect the bounty-- all in a single transaction. Projects opt into the system by registering a bounty on the smart contract. The entire system is permissionless, non-custodial, and free! | ||
|
||
For Ethereum applications that can be exploited in a single transaction, this adds another form of proactive defense that can supplement traditional (off-chain) bug bounties and monitoring. | ||
For Ethereum protocols that can be exploited in a single transaction (which are often the case), this offers a novel form of proactive defense that can complement traditional (off-chain) bug bounties, threat monitoring, and in-protocol circuit breakers. | ||
|
||
## Flow | ||
## How it Works | ||
|
||
### Protocol Registration | ||
First a protocol must register itself to the HoneyPause contract via the `add()` function, providing: | ||
Projects register bounties with the HoneyPause contract via the `add()` function, providing: | ||
|
||
1. A bounty token and amount. | ||
2. The address of a custom **Verifier** contract which asserts on-chain state invariants that would be violated in the case of an exploit. Examples could be an AMM's reserve violating the constant product formula or a lending protocol incurring bad debt. | ||
3. The address of a custom **Pauser** contract which is authorized to pause/freeze the protocol when called by the HoneyPause contract. | ||
4. The address of a custom **Payer** contract which must pay the bounty to the whitehat when called by the HoneyPause contract. | ||
1. A bounty token and amount, but no deposit*. | ||
2. The address of a custom [`Verifier`](ause/blob/main/src/HoneyPause.sol#L8) contract which asserts onchain state invariants that would be violated in the case of an exploit. Examples could be an AMM's reserve violating the constant product formula or a lending protocol incurring bad debt. | ||
3. The address of a custom [`Pauser`](./src/HoneyPause.sol#L28) contract which is authorized to pause/freeze the protocol when called by the HoneyPause contract. | ||
4. The address of a custom [`Payer`](./src/HoneyPause.sol#L42) contract which must pay the bounty to the whitehat when called by the HoneyPause contract. | ||
5. An operator account for the bounty, who will be able to modify the bounty. | ||
|
||
Note that the HoneyPause contract never custodies bounties. It is up to the protocol's **Payer** contract to surface funds to cover the bounty when called. This means a protocol may employ an indirect way of paying the bounty when it is demanded, such is liquidating assets, activating a safety module, etc. | ||
> \* Note that the HoneyPause contract never custodies bounties. It is up to the project's **Payer** contract to surface funds to cover the bounty when called. | ||
|
||
### Claiming a Bounty | ||
A whitehat that has discovered an exploit on a registered protocol will post a `claim()` transaction **TO A PRIVATE MEMPOOL**, providing an **Exploiter** contract that will perform the exploit when called by the HoneyPause contract. The HoneyPause contract will: | ||
A whitehat that has discovered an exploit on a registered project will submit a successful `claim()` transaction **TO A PRIVATE MEMPOOL**, providing an [`Exploiter`](./src/HoneyPause.sol#L35) contract that will perform the exploit when called by HoneyPause. In the same transaction, the HoneyPause contract will: | ||
|
||
1. Call into itself to enter a new call frame. | ||
1. Run the protocol's **Verifier** to assert that the protocol has not been exploited yet and to track any necessary state. | ||
1. Run the project's **Verifier** to assert that the protocol has not been exploited yet and to track any necessary state. | ||
2. Call into the **Exploiter**, applying the exploit. | ||
3. Run the protocol's **Verifier** again to assert that the protocol has reached an exploited state (success means exploited). | ||
3. Run the project's **Verifier** again to assert that the protocol has reached an exploited state (success means exploited). | ||
4. Revert the call frame, undoing the exploit and bubbling up the result of 3. | ||
2. If the exploit was successful, we will then: | ||
1. Call into the protocol's **Pauser** to freeze the protocol. | ||
2. Call into the protocol's **Payer** to pay the bounty to the whitehat. | ||
1. Call into the project's **Pauser** to freeze the protocol. | ||
2. Call into the project's **Payer** to pay the bounty to the whitehat. | ||
3. Ensure the whitehat received the agreed bounty amount. | ||
|
||
> ⚠️ It is critical that the whitehat uses a private mempool to submit the transaction to in order to prevent an MEV bot from extracting the exploit from the unmined transaction and frontrunning the claim! | ||
> ⚠️ On Ethereum mainnet, it is critical that the whitehat uses a private mempool mechanism (e.g., Flashbots Protect with max privacy) to submit the transaction in order to prevent discovery of the exploit mechanism before the transaction is mined and the protocol can be paused! On other chains where sequencing cannot be practically frontrun, it may be sufficient to submit directly to the tx sequencer. | ||
|
||
As a further safeguard against extra clever MEV bots, it is recommended that the deployment of the **Exploiter** contract be performed in the same transaction as (prior to) the `claim()` call. | ||
You can check out an example trace of a claim tx [here](https://phalcon.blocksec.com/explorer/tx/sepolia/0xd3ce2ef3a80a6461142020909acc8499e8b6e893073c77d534734d7d129abdc7). | ||
|
||
## Writing Verifiers | ||
**Verifier**s must confirm that some critical invariants or health checks have been violated in the post-exploit state. Projects need to do the legwork of identifying a robust set of checks that would be considered critical enough to warrant pausing the entire protocol. These would typically be invariants that do not get checked during normal user interactions due to gas constraints. | ||
|
||
Verifiers should essentially confirm that some critical invariants or health checks have been violated by the exploit. Protocols need to do the legwork of identifying a robust and comprehensive set of checks that would be considered critical enough to warrant pausing the entire protocol. These would typically be invariants that do not get checked during normal user interactions due to gas constraints. | ||
### Two-Step Verification | ||
The **Verifier** contract should expose two methods: `beforeExploit()` and `assertExploit()`. As the names imply, the former is called before the exploit is executed and the latter is called after. | ||
|
||
The verifier contract should expose two methods: `beforeExploit()` and `assertExploit()`. As the names imply, the former is called before the exploit is executed and the latter is called after. Both methods accept an arbitrary `verifierData` bytes array that is provided by the exploiter to help identify an exploit. This may be needed if, for example, the exploit occurs on a specific pool that is not easily discoverable on-chain. You should document the uses of this data in your verifier. | ||
**Verifier**s *must* implement both `beforeExploit()` and `assertExploit()`, and *both* should verify the protocol's invariants. This redundancy is to prevent an exploiter from actually exploiting the project and then claim the bounty on top of it! A notable quirk is that `beforeExploit()` is expected to revert if the protocol *is* currently exploited and `assertExploit()` is expected to revert if the protocol *is not* currently exploited. | ||
|
||
A verifier's `beforeExploit()` function may also return arbitrary, intermediate state data, which is another bytes array. This will ultimately be passed into `assertExploit()`. A verifier can use this data to remember things between calls without affecting state. | ||
### Verifier Data | ||
Both methods accept an arbitrary `verifierData` bytes array that is *provided by the exploiter* to help identify an exploit. This may be needed if, for example, the exploit occurs on a specific pool that is not easily discoverable onchain. You should document the uses of this data in your **Verifier** as reference to whitehats. | ||
|
||
### Risks | ||
Verifiers should try to ensure that the protocol is not in an exploited state when `beforeExploit()` is called. Otherwise an attacker can exploit a protocol beforehand but still collect the bounty, effectively double-dipping. | ||
### Verifier State Data | ||
A **Verifier**'s `beforeExploit()` function returns arbitrary data. This will later be passed into `assertExploit()`. If verification requires observing the state delta before and after an exploit, this data can be used to cache information about that state without writing to expensive contract storage. | ||
|
||
If the verifier performs state changes (even transient ones), they should restrict the caller to the HoneyPause contract. Otherwise the verifier may inherit invalid state from a prior call that could affect validation. | ||
### Stateful Verifiers | ||
If the **Verifier** applies any state changes (even transient ones), they should restrict the caller to the HoneyPause contract. Otherwise the **Verifier** may be maliciously invoked before the call to `claim()` to manipulate results. | ||
|
||
## Writing Pausers | ||
|
||
Pausers should generally be designed to pause the *entire* protocol. Only the HoneyPause contract should be allowed to call `pause()` on the Pauser contract. | ||
Because the exploit will be detailed onchain for all to see after the claim tx is made, **Pausers** should pause as much of the protocol to prevent replicating the exploit across related components (pools) of the system. Only the HoneyPause contract should be allowed to call `pause()` on the **Pauser** contract. The pause *must* occur when `Pauser.pause()` is called, and not in the payer, which is called immediately afterwards. | ||
|
||
## Writing Payers | ||
The **Payer** contract will be invoked by HoneyPause to transfer the bounty to the whitehat. Bounties can be in either ETH or ERC20. HoneyPause will surround the `payExploiter()` call with balance checks to ensure that payment has been delivered. The **Payer** contract should only allow the HoneyPause contract to call its `payExploiter()` function. | ||
|
||
Because the system is non-custodial, the Payer contract must be invoked by HoneyPause to transfer the bounty to the whitehat. Only the HoneyPause contract should be allowed to call the `payExploiter()` function. HoneyPause will track the balance of the whitehat to ensure funds have been delivered. Payments can be in ETH or an ERC20 token. | ||
|
||
Instead of reserving a pool of payment coins for the bounty, a protocol may choose to perform some kind of just-in-time conversion of its assets towards the bounty. But note that the call to `payExploiter()` actually occurs *after* the protocol has been paused. The delivery mechanism used needs to be distinct from usual operations affected by a pause. Also, be wary of complex conversions as that increases the chances of a secondary exploit occurring in the Payer. | ||
The simplest **Payer** implementation will transfer the bounty directly out of a dedicated fund. Alternatively, a project may choose to keep the bounty value in its protocol and perform the conversion on-the-fly. In that case, the payment mechanism should be distinct from normal user operations on the protocol because the **Payer** will be invoked *after* the **Pauser**. Keep in mind that more complex payment mechanisms can open projects up to a secondary exploit. | ||
|
||
## Deployed Addresses | ||
|
||
| Chain | Address | | ||
|-------|---------| | ||
| Ethereum Mainnet | `TBD (will deploy if we get on stage)` | | ||
| Ethereum Sepolia | `0x00a4748f0D0072f65aFe9bb52A723733c5878821` | | ||
| Ethereum Mainnet | `TBD` | | ||
| Ethereum Sepolia | [`0x5cd701310ae6e3185C29de433019C96efd298d60`](https://sepolia.etherscan.io/address/0x5cd701310ae6e3185c29de433019c96efd298d60) | | ||
|
||
## Credits | ||
|
||
HoneyPause is an EthDenver hackathon project by the following sleep deprived folks: | ||
* [@CryptRillionair](https://twitter.com/CryptRillionair) | ||
* [@justinschuldt](https://github.com/justinschuldt) | ||
* [@merklejerk](https://github.com/merklejerk) | ||
HoneyPause is originally an EthDenver 2024 hack by [@justinschuldt](https://github.com/justinschuldt), [@CryptRillionair](https://twitter.com/CryptRillionair), and [@merklejerk](https://twitter.com/merklejerk), but we ultimately want this project to be community owned, so feedback and contributions are welcome! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
y u hate proactive