Skip to content

Commit 150ff79

Browse files
authored
Merge branch 'main' into bf/proposal-epoch-refactor
2 parents 1708c5e + 8b9fc47 commit 150ff79

File tree

19 files changed

+799
-112
lines changed

19 files changed

+799
-112
lines changed

.env

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ ESPRESSO_DEMO_SEQUENCER_LIBP2P_PORT_4=7004
9999
ESPRESSO_SEQUENCER_STAKE_TABLE_CAPACITY=10
100100

101101
# The demo can use a short exit escrow period. It should be the length of at least 3 epochs plus a
102-
# reasonably timeframe to submit slashing evidence. This assumes the demo is using 20 blocks epoch.
103-
ESPRESSO_SEQUENCER_STAKE_TABLE_EXIT_ESCROW_PERIOD=3m
102+
# reasonably timeframe to submit slashing evidence. The stake table contract requires a minimum of 2 days.
103+
ESPRESSO_SEQUENCER_STAKE_TABLE_EXIT_ESCROW_PERIOD=2d
104104

105105
# Foundry
106106
# The mnemonic used by foundry to deploy contracts.
@@ -178,4 +178,4 @@ ESPRESSO_OPS_TIMELOCK_EXECUTORS=${ESPRESSO_SEQUENCER_ETH_MULTISIG_ADDRESS}
178178
ESPRESSO_SAFE_EXIT_TIMELOCK_DELAY=1209600
179179
ESPRESSO_SAFE_EXIT_TIMELOCK_ADMIN=8626f6940e2eb28930efb4cef49b2d1f2c9c1199
180180
ESPRESSO_SAFE_EXIT_TIMELOCK_PROPOSERS=${ESPRESSO_SEQUENCER_ETH_MULTISIG_ADDRESS}
181-
ESPRESSO_SAFE_EXIT_TIMELOCK_EXECUTORS=${ESPRESSO_SEQUENCER_ETH_MULTISIG_ADDRESS}
181+
ESPRESSO_SAFE_EXIT_TIMELOCK_EXECUTORS=${ESPRESSO_SEQUENCER_ETH_MULTISIG_ADDRESS}

contracts/artifacts/abi/StakeTableV2.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,19 @@
3030
],
3131
"stateMutability": "view"
3232
},
33+
{
34+
"type": "function",
35+
"name": "MAX_EXIT_ESCROW_PERIOD",
36+
"inputs": [],
37+
"outputs": [
38+
{
39+
"name": "",
40+
"type": "uint64",
41+
"internalType": "uint64"
42+
}
43+
],
44+
"stateMutability": "view"
45+
},
3346
{
3447
"type": "function",
3548
"name": "MAX_METADATA_URI_LENGTH",
@@ -43,6 +56,19 @@
4356
],
4457
"stateMutability": "view"
4558
},
59+
{
60+
"type": "function",
61+
"name": "MIN_EXIT_ESCROW_PERIOD",
62+
"inputs": [],
63+
"outputs": [
64+
{
65+
"name": "",
66+
"type": "uint64",
67+
"internalType": "uint64"
68+
}
69+
],
70+
"stateMutability": "view"
71+
},
4672
{
4773
"type": "function",
4874
"name": "PAUSER_ROLE",

contracts/rust/adapter/src/bindings/stake_table.rs

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.

contracts/rust/adapter/src/bindings/stake_table_v2.rs

Lines changed: 417 additions & 5 deletions
Large diffs are not rendered by default.

contracts/rust/deployer/src/builder.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,9 @@ impl<P: Provider + WalletProvider> DeployerArgs<P> {
367367
let lc_addr = contracts
368368
.address(Contract::LightClientProxy)
369369
.context("no LightClient proxy address")?;
370-
let escrow_period = self.exit_escrow_period.unwrap_or(U256::from(250));
370+
let escrow_period = self
371+
.exit_escrow_period
372+
.unwrap_or(U256::from(crate::DEFAULT_EXIT_ESCROW_PERIOD_SECONDS));
371373
crate::deploy_stake_table_proxy(
372374
provider,
373375
contracts,

contracts/rust/deployer/src/lib.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ pub fn build_random_provider(url: Url) -> HttpProviderWithWallet {
110110
const LIBRARY_PLACEHOLDER_ADDRESS: &str = "ffffffffffffffffffffffffffffffffffffffff";
111111
/// `stateHistoryRetentionPeriod` in LightClient.sol as the maximum retention period in seconds
112112
pub const MAX_HISTORY_RETENTION_SECONDS: u32 = 864000;
113+
/// Default exit escrow period for stake table (2 days in seconds)
114+
pub const DEFAULT_EXIT_ESCROW_PERIOD_SECONDS: u64 = 172800;
113115

114116
/// Set of predeployed contracts.
115117
#[derive(Clone, Debug, Parser)]
@@ -1826,7 +1828,7 @@ mod tests {
18261828
)
18271829
.await?;
18281830
let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
1829-
let exit_escrow_period = U256::from(1000);
1831+
let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
18301832

18311833
let stake_table_proxy_addr = deploy_stake_table_proxy(
18321834
&provider,
@@ -2430,7 +2432,7 @@ mod tests {
24302432
.await?;
24312433

24322434
// deploy stake table
2433-
let exit_escrow_period = U256::from(250);
2435+
let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
24342436
let owner = init_recipient;
24352437
let stake_table_addr = deploy_stake_table_proxy(
24362438
&provider,
@@ -2489,7 +2491,7 @@ mod tests {
24892491
.await?;
24902492

24912493
// deploy stake table
2492-
let exit_escrow_period = U256::from(250);
2494+
let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
24932495
let owner = init_recipient;
24942496
let stake_table_addr = deploy_stake_table_proxy(
24952497
&provider,
@@ -2600,7 +2602,7 @@ mod tests {
26002602
)
26012603
.await?;
26022604

2603-
let exit_escrow_period = U256::from(250);
2605+
let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
26042606
let owner = init_recipient;
26052607
let stake_table_proxy_addr = deploy_stake_table_proxy(
26062608
&provider,
@@ -3179,7 +3181,7 @@ mod tests {
31793181
&mut contracts,
31803182
token_addr,
31813183
lc_proxy_addr,
3182-
U256::from(1000u64),
3184+
U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS),
31833185
admin,
31843186
)
31853187
.await?
@@ -3421,7 +3423,7 @@ mod tests {
34213423
.await?;
34223424

34233425
let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
3424-
let exit_escrow_period = U256::from(1000);
3426+
let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
34253427

34263428
let stake_table_proxy_addr = deploy_stake_table_proxy(
34273429
&provider,
@@ -3458,7 +3460,7 @@ mod tests {
34583460
.await?;
34593461

34603462
let lc_addr = deploy_light_client_contract(&provider, &mut contracts, false).await?;
3461-
let exit_escrow_period = U256::from(1000);
3463+
let exit_escrow_period = U256::from(DEFAULT_EXIT_ESCROW_PERIOD_SECONDS);
34623464

34633465
let stake_table_proxy_addr = deploy_stake_table_proxy(
34643466
&provider,

contracts/src/StakeTable.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,7 @@ contract StakeTable is Initializable, InitializedAt, OwnableUpgradeable, UUPSUpg
247247
token = ERC20(_tokenAddress);
248248
lightClient = ILightClient(_lightClientAddress);
249249

250-
uint256 minExitEscrowPeriod = 90 seconds; // assuming 15s per block and min blocks per epoch
251-
// is 6 in the light client
250+
uint256 minExitEscrowPeriod = 2 days;
252251
if (_exitEscrowPeriod < minExitEscrowPeriod) {
253252
revert ExitEscrowPeriodInvalid();
254253
}

contracts/src/StakeTableV2.sol

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,20 @@ contract StakeTableV2 is StakeTable, PausableUpgradeable, AccessControlUpgradeab
111111
/// @notice Maximum commission in basis points (100% = 10000 bps)
112112
uint16 public constant MAX_COMMISSION_BPS = 10000;
113113

114+
/// @notice Minimum exit escrow period (2 days)
115+
/// @dev This is a technical minimum bound enforced by the contract. Setting the exit escrow
116+
/// period
117+
/// to this minimum does not guarantee safety. The actual exit escrow period must be set such
118+
/// that the contract holds funds long enough until they are no longer staked in Espresso,
119+
/// allowing sufficient time for validators to exit the active validator set and for slashing
120+
/// evidence to be submitted. Governance should set a value appropriate for Espresso network
121+
/// parameters (e.g., blocksPerEpoch, blockTime, and epoch duration) to ensure security.
122+
uint64 public constant MIN_EXIT_ESCROW_PERIOD = 2 days;
123+
124+
/// @notice Maximum exit escrow period (14 days)
125+
/// @dev Reasonable upper bound to prevent excessive lockup periods
126+
uint64 public constant MAX_EXIT_ESCROW_PERIOD = 14 days;
127+
114128
/// @notice Minimum time interval between commission increases (in seconds)
115129
uint256 public minCommissionIncreaseInterval;
116130

@@ -787,21 +801,25 @@ contract StakeTableV2 is StakeTable, PausableUpgradeable, AccessControlUpgradeab
787801
/// @notice Update the exit escrow period
788802
/// @param newExitEscrowPeriod The new exit escrow period
789803
/// @dev This function ensures that the exit escrow period is within the valid range
790-
/// @dev This function is not pausable so that governance can perform emergency updates in the
791-
/// presence of system
804+
/// (MIN_EXIT_ESCROW_PERIOD
805+
/// to MAX_EXIT_ESCROW_PERIOD). However, governance MUST set a value that ensures funds are held
806+
/// until they are no longer staked in Espresso, accounting for validator exit time and slashing
807+
/// evidence submission windows. This function is not pausable so that governance can perform
808+
/// emergency updates in the
809+
/// presence of system upgrades.
792810
function updateExitEscrowPeriod(uint64 newExitEscrowPeriod)
793811
external
794812
virtual
795813
onlyRole(DEFAULT_ADMIN_ROLE)
796814
{
797-
uint64 minExitEscrowPeriod = lightClient.blocksPerEpoch() * 15; // assuming 15 seconds per
798-
// block
799-
uint64 maxExitEscrowPeriod = 86400 * 14; // 14 days
800-
801-
if (newExitEscrowPeriod < minExitEscrowPeriod || newExitEscrowPeriod > maxExitEscrowPeriod)
802-
{
815+
// check if the new exit escrow period is within the valid range
816+
if (
817+
newExitEscrowPeriod < MIN_EXIT_ESCROW_PERIOD
818+
|| newExitEscrowPeriod > MAX_EXIT_ESCROW_PERIOD
819+
) {
803820
revert ExitEscrowPeriodInvalid();
804821
}
822+
805823
exitEscrowPeriod = newExitEscrowPeriod;
806824
emit ExitEscrowPeriodUpdated(newExitEscrowPeriod);
807825
}

contracts/test/StakeTable.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,8 +1338,8 @@ contract StakeTableUpgradeV2Test is Test {
13381338
address defaultAdmin = proxy.owner();
13391339
vm.startPrank(defaultAdmin);
13401340
vm.expectEmit(false, false, false, true, address(proxy));
1341-
emit StakeTableV2.ExitEscrowPeriodUpdated(200 seconds);
1342-
proxy.updateExitEscrowPeriod(200 seconds);
1341+
emit StakeTableV2.ExitEscrowPeriodUpdated(2 days);
1342+
proxy.updateExitEscrowPeriod(2 days);
13431343
vm.stopPrank();
13441344
}
13451345

@@ -1356,7 +1356,7 @@ contract StakeTableUpgradeV2Test is Test {
13561356
IAccessControl.AccessControlUnauthorizedAccount.selector, notAdmin, adminRole
13571357
)
13581358
);
1359-
StakeTableV2(proxy).updateExitEscrowPeriod(200 seconds);
1359+
StakeTableV2(proxy).updateExitEscrowPeriod(2 days);
13601360
vm.stopPrank();
13611361
}
13621362

contracts/test/StakeTableV2Governance.t.sol

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { OwnableUpgradeable } from
1212
"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
1313
import { StakeTableUpgradeV2Test } from "./StakeTable.t.sol";
1414
import { StakeTable_register_Test } from "./StakeTable.t.sol";
15+
import { ILightClient } from "../src/interfaces/ILightClient.sol";
1516

1617
/// @title StakeTableV2 Governance Tests
1718
/// @notice Comprehensive tests for governance functions: transferOwnership, grantRole, revokeRole
@@ -91,12 +92,12 @@ contract StakeTableV2GovernanceTest is Test {
9192
)
9293
);
9394
vm.expectEmit(true, true, true, true, address(proxy));
94-
emit StakeTableV2.ExitEscrowPeriodUpdated(200 seconds);
95-
proxy.updateExitEscrowPeriod(200 seconds);
95+
emit StakeTableV2.ExitEscrowPeriodUpdated(2 days);
96+
proxy.updateExitEscrowPeriod(2 days);
9697
vm.stopPrank();
9798

9899
vm.prank(initialOwner);
99-
proxy.updateExitEscrowPeriod(200 seconds);
100+
proxy.updateExitEscrowPeriod(2 days);
100101
}
101102

102103
function test_TransferOwnership_Success() public {
@@ -170,7 +171,7 @@ contract StakeTableV2GovernanceTest is Test {
170171
vm.stopPrank();
171172

172173
vm.prank(initialOwner);
173-
proxy.updateExitEscrowPeriod(200 seconds);
174+
proxy.updateExitEscrowPeriod(2 days);
174175
}
175176

176177
function test_TransferOwnership_RevertsWhenNotAdmin() public {
@@ -196,7 +197,7 @@ contract StakeTableV2GovernanceTest is Test {
196197

197198
vm.startPrank(newOwner);
198199

199-
uint64 newPeriod = 200 seconds;
200+
uint64 newPeriod = 2 days;
200201
proxy.updateExitEscrowPeriod(newPeriod);
201202

202203
proxy.grantRole(proxy.PAUSER_ROLE(), newOwner);
@@ -221,7 +222,7 @@ contract StakeTableV2GovernanceTest is Test {
221222
IAccessControl.AccessControlUnauthorizedAccount.selector, initialOwner, adminRole
222223
)
223224
);
224-
proxy.updateExitEscrowPeriod(200 seconds);
225+
proxy.updateExitEscrowPeriod(2 days);
225226
vm.stopPrank();
226227
}
227228

0 commit comments

Comments
 (0)