Skip to content

Commit fe7bad9

Browse files
committed
update testnet scripts
update README
1 parent 99f9849 commit fe7bad9

File tree

3 files changed

+89
-33
lines changed

3 files changed

+89
-33
lines changed

README.md

+30-10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# HoneyPause
22

3-
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!
3+
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!
44

55
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.
6+
67
## Flow
78

89
### Protocol Registration
@@ -19,33 +20,52 @@ Note that the HoneyPause contract never custodies bounties. It is up to the prot
1920
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:
2021

2122
1. Call into itself to enter a new call frame.
22-
1. Call into the **Exploiter**, applying the exploit.
23-
2. Run the protocol's **Verifier** to assert that the protocol has reached an exploited state.
24-
3. Revert the call frame, undoing the exploit and bubbling up the result of 2.
23+
1. Run the protocol's **Verifier** to assert that the protocol has not been exploited yet and to track any necessary state.
24+
2. Call into the **Exploiter**, applying the exploit.
25+
3. Run the protocol's **Verifier** again to assert that the protocol has reached an exploited state (success means exploited).
26+
4. Revert the call frame, undoing the exploit and bubbling up the result of 3.
2527
2. If the exploit was successful, we will then:
26-
1. Call into the protocol's **Pauser** to freeze the protocol!
28+
1. Call into the protocol's **Pauser** to freeze the protocol.
2729
2. Call into the protocol's **Payer** to pay the bounty to the whitehat.
28-
3. Ensure the whitehat received the stipulated bounty amount.
30+
3. Ensure the whitehat received the agreed bounty amount.
2931

3032
> ⚠️ 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!
3133
3234
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.
3335

3436
## Writing Verifiers
3537

36-
TODO
38+
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.
39+
40+
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.
41+
42+
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.
43+
44+
### Risks
45+
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.
46+
47+
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.
48+
49+
## Writing Pausers
50+
51+
Pausers should generally be designed to pause the *entire* protocol. Only the HoneyPause contract should be allowed to call `pause()` on the Pauser contract.
52+
53+
## Writing Payers
54+
55+
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.
56+
57+
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.
3758

3859
## Deployed Addresses
3960

4061
| Chain | Address |
4162
|-------|---------|
42-
| Ethereum Mainnet | `TBD` |
63+
| Ethereum Mainnet | `TBD (will deploy if we get on stage)` |
4364
| Ethereum Sepolia | `0x00a4748f0D0072f65aFe9bb52A723733c5878821` |
4465

45-
4666
## Credits
4767

4868
HoneyPause is an EthDenver hackathon project by the following sleep deprived folks:
49-
* [@JordanCason](https://github.com/JordanCason)
69+
* [@CryptRillionair](https://twitter.com/CryptRillionair)
5070
* [@justinschuldt](https://github.com/justinschuldt)
5171
* [@merklejerk](https://github.com/merklejerk)

script/demo/SecretProtocol.sol

+44-3
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,31 @@ import { ERC20 } from 'solmate/tokens/ERC20.sol';
55
import { HoneyPause, IVerifier, IPauser, IPayer, IExploiter, ETH_TOKEN } from '../../src/HoneyPause.sol';
66

77
contract SecretProtocol {
8+
address public immutable pauser;
89
bytes3 public hash;
10+
bool public paused;
911

1012
event PreimageFound(bytes32 preimage);
13+
event Paused();
1114

12-
constructor(bytes3 hash_) {
15+
constructor(bytes3 hash_, address pauser_) {
1316
hash = hash_;
17+
pauser = pauser_;
1418
}
1519

1620
function solve(bytes32 preimage) external {
21+
require(!paused, 'paused');
1722
if (hash != 0 && bytes3(keccak256(abi.encode(preimage))) == hash) {
1823
emit PreimageFound(preimage);
1924
hash = 0;
2025
}
2126
}
27+
28+
function pause() external {
29+
require(msg.sender == pauser, 'not pauser');
30+
paused = true;
31+
emit Paused();
32+
}
2233
}
2334

2435
contract SecretProtocolVerifier is IVerifier {
@@ -28,10 +39,12 @@ contract SecretProtocolVerifier is IVerifier {
2839

2940
function beforeExploit(bytes memory)
3041
external returns (bytes memory stateData)
31-
{}
42+
{
43+
require(proto.hash() != 0, 'already exploited');
44+
}
3245

3346
function assertExploit(bytes memory, bytes memory) external view {
34-
require(proto.hash() != 0, 'not exploited');
47+
require(proto.hash() == 0, 'not exploited');
3548
}
3649
}
3750

@@ -41,4 +54,32 @@ contract SecretExploiter is IExploiter {
4154
abi.decode(exploiterData, (SecretProtocol, bytes32));
4255
proto.solve(preimage);
4356
}
57+
}
58+
59+
contract SecretProtocolPauser is IPauser {
60+
HoneyPause immutable honey;
61+
SecretProtocol public immutable proto;
62+
63+
constructor(HoneyPause honey_, SecretProtocol proto_) {
64+
honey = honey_;
65+
proto = proto_;
66+
}
67+
68+
function pause() external {
69+
require(msg.sender == address(honey), 'not honey');
70+
proto.pause();
71+
}
72+
}
73+
74+
contract SecretProtocolPayer is IPayer {
75+
HoneyPause immutable honey;
76+
77+
constructor(HoneyPause honey_) {
78+
honey = honey_;
79+
}
80+
81+
function payExploiter(ERC20 token, address payable to, uint256 amount) external {
82+
require(msg.sender == address(honey), 'not honey');
83+
token.transfer(to, amount);
84+
}
4485
}

script/demo/Testnet.s.sol

+15-20
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,25 @@ pragma solidity ^0.8.23;
44
import { Script, console2 } from 'forge-std/Script.sol';
55
import { ERC20 } from 'solmate/tokens/ERC20.sol';
66
import { HoneyPause, IVerifier, IPauser, IPayer, ETH_TOKEN } from '../../src/HoneyPause.sol';
7-
import { SecretProtocol, SecretProtocolVerifier, SecretExploiter } from './SecretProtocol.sol';
7+
import {
8+
SecretProtocol,
9+
SecretProtocolVerifier,
10+
SecretExploiter,
11+
SecretProtocolPauser,
12+
SecretProtocolPayer
13+
} from './SecretProtocol.sol';
814
import { TestPayer, TestToken, SucceedingContract, FailingContract } from './Dummies.sol';
915

1016
contract Testnet is Script {
1117
uint256 deployerKey;
18+
address deployer;
1219
HoneyPause honey;
1320
TestToken usdc;
1421
address operator;
1522

1623
function setUp() external {
1724
deployerKey = uint256(vm.envBytes32('DEPLOYER_KEY'));
25+
deployer = vm.addr(deployerKey);
1826
honey = HoneyPause(vm.envAddress('HONEY'));
1927
usdc = TestToken(vm.envAddress('USDC'));
2028
operator = vm.envAddress('OPERATOR');
@@ -53,31 +61,18 @@ contract Testnet is Script {
5361
}
5462

5563
function registerUsdcProtocol(string memory name, uint256 amount, bytes3 hash) external {
64+
address pauserAddress = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 2);
5665
_broadcast();
57-
SecretProtocol proto = new SecretProtocol(hash);
66+
SecretProtocol proto = new SecretProtocol(hash, pauserAddress);
5867
_broadcast();
5968
IVerifier verifier = new SecretProtocolVerifier(proto);
60-
IPauser pauser = IPauser(_deploySucceedingContract()) ;
61-
IPayer payer = _deployPayerContract(usdc, amount);
6269
_broadcast();
63-
honey.add({
64-
name: name,
65-
payoutToken: usdc,
66-
payoutAmount: amount,
67-
verifier: verifier,
68-
pauser: pauser,
69-
payer: payer,
70-
operator: operator
71-
});
72-
}
73-
74-
function registerEthProtocol(string memory name, uint256 amount, bytes3 hash) external {
70+
IPauser pauser = new SecretProtocolPauser(honey, proto);
71+
assert(address(pauser) == pauserAddress);
7572
_broadcast();
76-
SecretProtocol proto = new SecretProtocol(hash);
73+
IPayer payer = new SecretProtocolPayer(honey);
7774
_broadcast();
78-
IVerifier verifier = new SecretProtocolVerifier(proto);
79-
IPauser pauser = IPauser(_deploySucceedingContract()) ;
80-
IPayer payer = _deployPayerContract(ETH_TOKEN, amount);
75+
usdc.mint(address(payer), amount);
8176
_broadcast();
8277
honey.add({
8378
name: name,

0 commit comments

Comments
 (0)