Skip to content

Commit 4af2f26

Browse files
authored
feat: updatable validator commissions (#3591)
* feat: updatable validator commissions - stake table contract: allow updates with rate limits - migrate commissions from V1 stake table into storage for V2 - handle commission changes in the GCL - test: stake table state gets invalid commission - break up some lines to fix cargo fmt - handle patch upgrades for stake table - stake table upgrade: dedupe upgrade preparation - test: unittest commission updates * fix: mock stake table, initialization for tests * update Cargo.lock * fix: solhint * fix: staking-cli command * fix: solhint * fix: clippy, unnnecessary into_iter I don't understand why this shows up in this branch, but not on main. * fix: check revert selector * test: assert assert to check effect * test: fork test for sepolia stake table upgrade * fix: commit missing file * test: commission updates integration test * cleanup: use new fetch_commission function * remove half implemented commission tracking * remove half implemented commission tracking (v2) * remove half implemented commission tracking (v3) * address review comments - More tests for initialization from existing contract. - Emit previous validator commissions. - Update staking-cli README. - Rename Update -> Increase. * fix compilation: add missing fields * fix: serde * test: UpdateCommissions serde rountrip test * address review comments - Add no-self-delegation delegator config for easier testing of commission change. - Add CLI test for too early commission updates. - Add some clarifying comments. * test: assert other validators receiving rewards * fixup: remove duplicate
1 parent 7ec1f4b commit 4af2f26

File tree

29 files changed

+6995
-3152
lines changed

29 files changed

+6995
-3152
lines changed

Cargo.lock

Lines changed: 454 additions & 430 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ alloy = { version = "0.13", default-features = false, features = [
103103
"rpc",
104104
"rpc-client",
105105
"rpc-types",
106+
"serde",
106107
"signer-local",
107108
"signer-mnemonic",
108109
"signer-ledger",

contracts/rust/adapter/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ark-ff = { workspace = true }
2323
ark-poly = { workspace = true }
2424
ark-serialize = { workspace = true }
2525
ark-std = { workspace = true }
26+
derive_more = { workspace = true }
2627
hotshot-types = { workspace = true }
2728
jf-pcs = { workspace = true }
2829
jf-plonk = { workspace = true }
@@ -35,6 +36,7 @@ serde = { workspace = true }
3536
thiserror = { workspace = true }
3637

3738
[dev-dependencies]
39+
bincode = { workspace = true }
3840
tokio = { workspace = true }
3941

4042
[lints]

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

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

contracts/rust/adapter/src/sol_types.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
269269
use self::{
270270
staketablev2::{EdOnBN254::EdOnBN254Point, BN254::G2Point},
271271
StakeTableV2::{
272-
ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated, Undelegated, ValidatorExit,
273-
ValidatorRegistered, ValidatorRegisteredV2,
272+
CommissionUpdated, ConsensusKeysUpdated, ConsensusKeysUpdatedV2, Delegated, Undelegated,
273+
ValidatorExit, ValidatorRegistered, ValidatorRegisteredV2,
274274
},
275275
};
276276

@@ -373,6 +373,37 @@ impl<'de> Deserialize<'de> for ValidatorRegisteredV2 {
373373
}
374374
}
375375

376+
impl Serialize for CommissionUpdated {
377+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
378+
where
379+
S: Serializer,
380+
{
381+
(
382+
&self.validator,
383+
self.timestamp,
384+
self.oldCommission,
385+
self.newCommission,
386+
)
387+
.serialize(serializer)
388+
}
389+
}
390+
391+
#[allow(non_snake_case)]
392+
impl<'de> Deserialize<'de> for CommissionUpdated {
393+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
394+
where
395+
D: Deserializer<'de>,
396+
{
397+
let (validator, timestamp, oldCommission, newCommission) = <_>::deserialize(deserializer)?;
398+
Ok(Self {
399+
validator,
400+
timestamp,
401+
newCommission,
402+
oldCommission,
403+
})
404+
}
405+
}
406+
376407
impl Serialize for EdOnBN254Point {
377408
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
378409
where
@@ -558,3 +589,29 @@ impl<'de> Deserialize<'de> for ConsensusKeysUpdatedV2 {
558589
})
559590
}
560591
}
592+
593+
#[cfg(test)]
594+
mod tests {
595+
use alloy::{primitives::U256, sol_types::private::Address};
596+
597+
use super::*;
598+
599+
#[test]
600+
fn test_commission_updated_serde_roundtrip() {
601+
let original = CommissionUpdated {
602+
validator: Address::random(),
603+
timestamp: U256::from(999),
604+
oldCommission: 123,
605+
newCommission: 456,
606+
};
607+
608+
let serialized = bincode::serialize(&original).expect("Failed to serialize");
609+
let deserialized: CommissionUpdated =
610+
bincode::deserialize(&serialized).expect("Failed to deserialize");
611+
612+
assert_eq!(original.validator, deserialized.validator);
613+
assert_eq!(original.timestamp, deserialized.timestamp);
614+
assert_eq!(original.oldCommission, deserialized.oldCommission);
615+
assert_eq!(original.newCommission, deserialized.newCommission);
616+
}
617+
}

contracts/rust/adapter/src/stake_table.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,15 @@ impl ConsensusKeysUpdatedV2 {
200200
}
201201
}
202202

203+
impl From<StakeTable::ValidatorRegistered> for StakeTableV2::InitialCommission {
204+
fn from(value: StakeTable::ValidatorRegistered) -> Self {
205+
Self {
206+
validator: value.account,
207+
commission: value.commission,
208+
}
209+
}
210+
}
211+
203212
#[cfg(test)]
204213
mod test {
205214
use alloy::primitives::Address;

contracts/rust/deployer/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ tracing = "0.1.37"
2323
vbs = { workspace = true }
2424

2525
[dev-dependencies]
26+
rand = { workspace = true }
2627
sequencer-utils = { version = "0.1.0", path = "../../../utils" }
2728
test-log = { workspace = true }
2829

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use alloy::{
2+
network::{Network, TransactionBuilder},
3+
primitives::Address,
4+
providers::{
5+
fillers::{FillerControlFlow, TxFiller},
6+
Provider, SendableTx,
7+
},
8+
transports::TransportResult,
9+
};
10+
11+
/// A filler that sets the `from` field on transactions to impersonate a specific address.
12+
/// This is useful when using Anvil's impersonation features to send transactions from
13+
/// accounts that we don't have the private key for.
14+
///
15+
/// Avoids having to manually set the `from` field on transactions.
16+
#[derive(Clone, Debug, Default)]
17+
pub struct ImpersonateFiller {
18+
from: Address,
19+
}
20+
21+
impl ImpersonateFiller {
22+
pub fn new(from: Address) -> Self {
23+
Self { from }
24+
}
25+
}
26+
27+
#[derive(Clone, Debug)]
28+
pub struct ImpersonateFillable {
29+
pub from: Address,
30+
}
31+
32+
impl<N: Network> TxFiller<N> for ImpersonateFiller {
33+
type Fillable = ImpersonateFillable;
34+
35+
fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow {
36+
if tx.from().is_none() {
37+
FillerControlFlow::Ready
38+
} else {
39+
FillerControlFlow::Finished
40+
}
41+
}
42+
43+
async fn prepare<P: Provider<N>>(
44+
&self,
45+
_provider: &P,
46+
_tx: &N::TransactionRequest,
47+
) -> TransportResult<Self::Fillable> {
48+
Ok(ImpersonateFillable { from: self.from })
49+
}
50+
51+
async fn fill(
52+
&self,
53+
fillable: Self::Fillable,
54+
mut tx: SendableTx<N>,
55+
) -> TransportResult<SendableTx<N>> {
56+
if let Some(builder) = tx.as_mut_builder() {
57+
builder.set_from(fillable.from);
58+
}
59+
Ok(tx)
60+
}
61+
62+
fn fill_sync(&self, tx: &mut SendableTx<N>) {
63+
if let Some(builder) = tx.as_mut_builder() {
64+
builder.set_from(self.from);
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)