Skip to content

Commit 235ec4e

Browse files
committed
MevCommitBapp implementation
1 parent 60bf6ee commit 235ec4e

3 files changed

Lines changed: 189 additions & 0 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// SPDX-License-Identifier: BSL 1.1
2+
pragma solidity 0.8.28;
3+
4+
import {MevCommitBappStorage} from "./MevCommitBappStorage.sol";
5+
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
6+
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
7+
import {OwnableUpgradeableBasedApp} from "ssv/src/middleware/modules/roles/OwnableUpgradeableBasedApp.sol";
8+
import {TimestampOccurrence} from "../../utils/Occurrence.sol";
9+
10+
contract MevCommitBapp is MevCommitBappStorage, UUPSUpgradeable, PausableUpgradeable, OwnableUpgradeableBasedApp {
11+
12+
event ValidatorRegistered(bytes indexed pubkey, address indexed registrar);
13+
event ValidatorDeregistrationRequested(bytes indexed pubkey, address indexed deregistrar);
14+
event ValidatorDeregistered(bytes indexed pubkey, address indexed deregistrar);
15+
event ValidatorFrozen(bytes indexed pubkey, address indexed freezer);
16+
event ValidatorUnfrozen(bytes indexed pubkey, address indexed unfreezer);
17+
18+
error AlreadyWhitelisted();
19+
error ZeroAddress();
20+
error NotWhitelisted();
21+
error NonWhitelistedCaller();
22+
error NotOptedInCaller();
23+
error ValidatorAlreadyRegistered(bytes pubkey);
24+
error ValidatorNotRegistered(bytes pubkey);
25+
error ValidatorAlreadyRequestedDeregistration(bytes pubkey);
26+
error ValidatorNotFrozen(bytes pubkey);
27+
error UnfreezeTooSoon();
28+
error UnfreezeTransferFailed();
29+
error RefundFailed();
30+
error UnfreezeFeeRequired(uint256 requiredFee);
31+
32+
// TODO: Need to address constructor pattern mismatch
33+
constructor(address _basedAppManager, address owner) OwnableUpgradeableBasedApp(_basedAppManager, owner) {
34+
_disableInitializers();
35+
}
36+
function initialize(uint256 _unfreezePeriod, uint256 _unfreezeFee, address _unfreezeReceiver) public initializer {
37+
unfreezePeriod = _unfreezePeriod;
38+
unfreezeFee = _unfreezeFee;
39+
unfreezeReceiver = _unfreezeReceiver;
40+
__UUPSUpgradeable_init();
41+
__Pausable_init();
42+
}
43+
44+
function addWhitelisted(address account) external onlyOwner {
45+
if (isWhitelisted[account]) revert AlreadyWhitelisted();
46+
if (account == address(0)) revert ZeroAddress();
47+
isWhitelisted[account] = true;
48+
}
49+
50+
function removeWhitelisted(address account) external onlyOwner {
51+
if (!isWhitelisted[account]) revert NotWhitelisted();
52+
delete isWhitelisted[account];
53+
}
54+
55+
function pause() external onlyOwner {
56+
_pause();
57+
}
58+
function unpause() external onlyOwner {
59+
_unpause();
60+
}
61+
62+
function optInToBApp(
63+
uint32, /*strategyId*/
64+
address[] calldata, /*tokens*/
65+
uint32[] calldata, /*obligationPercentages*/
66+
bytes calldata /*data*/
67+
) external override onlySSVBasedAppManager whenNotPaused returns (bool success) {
68+
if (!isWhitelisted[msg.sender]) revert NonWhitelistedCaller();
69+
isOptedIn[msg.sender] = true;
70+
// Before slashing is enabled, strategies are irrelevant to mev-commit.
71+
return true;
72+
}
73+
74+
/// After opting-in, EOAs must register L1 validator pubkeys that attest to the rules of mev-commit.
75+
function registerValidatorPubkeys(bytes[] calldata pubkeys) external whenNotPaused {
76+
if (!isWhitelisted[msg.sender]) revert NonWhitelistedCaller();
77+
if (!isOptedIn[msg.sender]) revert NotOptedInCaller();
78+
for (uint256 i = 0; i < pubkeys.length; i++) {
79+
bytes calldata pubkey = pubkeys[i];
80+
if (validatorRecords[pubkey].exists) revert ValidatorAlreadyRegistered(pubkey);
81+
validatorRecords[pubkey] = ValidatorRecord({
82+
exists: true,
83+
registrar: msg.sender,
84+
freezeOccurrence: TimestampOccurrence.Occurrence({
85+
exists: false,
86+
timestamp: 0
87+
}),
88+
deregRequestOccurrence: TimestampOccurrence.Occurrence({
89+
exists: false,
90+
timestamp: 0
91+
})
92+
});
93+
emit ValidatorRegistered(pubkey, msg.sender);
94+
}
95+
}
96+
97+
function requestDeregistrations(bytes[] calldata pubkeys) external whenNotPaused {
98+
if (!isWhitelisted[msg.sender]) revert NonWhitelistedCaller();
99+
if (!isOptedIn[msg.sender]) revert NotOptedInCaller();
100+
for (uint256 i = 0; i < pubkeys.length; i++) {
101+
bytes calldata pubkey = pubkeys[i];
102+
if (!validatorRecords[pubkey].exists) revert ValidatorNotRegistered(pubkey);
103+
if (validatorRecords[pubkey].deregRequestOccurrence.exists) revert ValidatorAlreadyRequestedDeregistration(pubkey);
104+
TimestampOccurrence.captureOccurrence(validatorRecords[pubkey].deregRequestOccurrence);
105+
emit ValidatorDeregistrationRequested(pubkey, msg.sender);
106+
}
107+
}
108+
109+
function deregisterValidators(bytes[] calldata pubkeys) external whenNotPaused {
110+
if (!isWhitelisted[msg.sender]) revert NonWhitelistedCaller();
111+
if (!isOptedIn[msg.sender]) revert NotOptedInCaller();
112+
for (uint256 i = 0; i < pubkeys.length; i++) {
113+
bytes calldata pubkey = pubkeys[i];
114+
delete validatorRecords[pubkey];
115+
emit ValidatorDeregistered(pubkey, msg.sender);
116+
}
117+
}
118+
119+
function freezeValidators(bytes[] calldata pubkeys) external onlyOwner {
120+
for (uint256 i = 0; i < pubkeys.length; i++) {
121+
bytes calldata pubkey = pubkeys[i];
122+
if (!validatorRecords[pubkey].exists) revert ValidatorNotRegistered(pubkey);
123+
TimestampOccurrence.captureOccurrence(validatorRecords[pubkey].freezeOccurrence);
124+
emit ValidatorFrozen(pubkey, msg.sender);
125+
}
126+
}
127+
128+
function unfreeze(bytes[] calldata valPubKeys) external payable whenNotPaused() {
129+
uint256 requiredFee = unfreezeFee * valPubKeys.length;
130+
require(msg.value >= requiredFee, UnfreezeFeeRequired(requiredFee));
131+
uint256 len = valPubKeys.length;
132+
for (uint256 i = 0; i < len; ++i) {
133+
bytes calldata pubkey = valPubKeys[i];
134+
ValidatorRecord storage record = validatorRecords[pubkey];
135+
require(record.exists, ValidatorNotRegistered(pubkey));
136+
require(record.freezeOccurrence.exists, ValidatorNotFrozen(pubkey));
137+
require(block.timestamp > record.freezeOccurrence.timestamp + unfreezePeriod, UnfreezeTooSoon());
138+
TimestampOccurrence.del(record.freezeOccurrence);
139+
emit ValidatorUnfrozen(pubkey, record.registrar);
140+
}
141+
(bool success, ) = unfreezeReceiver.call{value: requiredFee}("");
142+
require(success, UnfreezeTransferFailed());
143+
uint256 excessFee = msg.value - requiredFee;
144+
if (excessFee != 0) {
145+
(bool successRefund, ) = msg.sender.call{value: excessFee}("");
146+
require(successRefund, RefundFailed());
147+
}
148+
}
149+
150+
function isValidatorOptedIn(bytes calldata pubkey) public view returns (bool) {
151+
ValidatorRecord storage record = validatorRecords[pubkey];
152+
if (!record.exists) return false;
153+
if (!isWhitelisted[record.registrar]) return false;
154+
if (!isOptedIn[record.registrar]) return false;
155+
if (record.freezeOccurrence.exists) return false;
156+
if (record.deregRequestOccurrence.exists) return false;
157+
return true;
158+
}
159+
160+
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
161+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-License-Identifier: BSL 1.1
2+
pragma solidity 0.8.28;
3+
4+
import {TimestampOccurrence} from "../../utils/Occurrence.sol";
5+
6+
contract MevCommitBappStorage {
7+
8+
struct ValidatorRecord {
9+
bool exists;
10+
address registrar;
11+
TimestampOccurrence.Occurrence freezeOccurrence;
12+
TimestampOccurrence.Occurrence deregRequestOccurrence;
13+
}
14+
15+
uint256 public unfreezePeriod;
16+
uint256 public unfreezeFee;
17+
address public unfreezeReceiver;
18+
19+
mapping(address => bool) public isWhitelisted;
20+
21+
mapping(address => bool) public isOptedIn;
22+
23+
mapping(bytes blsPubkey => ValidatorRecord) public validatorRecords;
24+
25+
/// @dev See https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps
26+
uint256[48] private __gap;
27+
}

contracts/remappings.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ forge-std/=lib/forge-std/src/
77
eigenlayer-contracts/=lib/eigenlayer-contracts/
88
symbiotic-core/=lib/core/src/
99
symbiotic-burners/=lib/burners/src/
10+
ssv/src/=lib/based-applications/src/

0 commit comments

Comments
 (0)