Skip to content

Commit ee1525f

Browse files
committed
feat: Implement RegisterOperator
1 parent 32db27f commit ee1525f

15 files changed

Lines changed: 482 additions & 51 deletions

File tree

‎dlp-api/src/v2/args/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
mod init_protocol_config;
2+
mod register_operator;
23

34
pub use init_protocol_config::*;
5+
pub use register_operator::*;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use wheels::{layout::Decodable, variable_offset_layout};
2+
3+
use crate::solana_program::program_error::ProgramError;
4+
5+
#[derive(Clone, Debug, PartialEq, Eq)]
6+
#[variable_offset_layout(buffer_offset = unaligned)]
7+
pub struct RegisterOperatorArgs {
8+
pub amount_lamports: u64,
9+
}
10+
11+
impl RegisterOperatorArgs {
12+
pub fn try_from_bytes(data: &[u8]) -> Result<Self, ProgramError> {
13+
let view = <Self as Decodable>::decode(data)
14+
.map_err(super::super::state::layout_error_to_program_error)?;
15+
16+
Ok(Self {
17+
amount_lamports: view.amount_lamports(),
18+
})
19+
}
20+
}

‎dlp-api/src/v2/instruction.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use strum::IntoStaticStr;
77
pub enum DlpV2Instruction {
88
/// Creates the global v2 protocol config and verifier registry accounts.
99
InitProtocolConfig = 100,
10+
/// Registers one operator and deposits its initial stake.
11+
RegisterOperator = 101,
1012
}
1113

1214
impl DlpV2Instruction {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
mod init_protocol_config;
2+
mod register_operator;
23

34
pub use init_protocol_config::*;
5+
pub use register_operator::*;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use solana_program::{
2+
instruction::{AccountMeta, Instruction},
3+
pubkey::Pubkey,
4+
};
5+
use solana_sdk_ids::system_program;
6+
use wheels::layout::Encodable;
7+
8+
use crate::{
9+
compat::{Compatize, Modernize},
10+
v2::{
11+
pda::{operator_bond_pda, protocol_config_pda},
12+
DlpV2Instruction, RegisterOperatorArgs,
13+
},
14+
};
15+
16+
/// Builds the instruction that registers one operator for v2 commitments.
17+
pub fn register_operator(
18+
operator: Pubkey,
19+
authority: Pubkey,
20+
args: RegisterOperatorArgs,
21+
) -> Instruction {
22+
Instruction {
23+
program_id: crate::id().modernize(),
24+
accounts: vec![
25+
AccountMeta::new(operator, true),
26+
AccountMeta::new_readonly(authority, true),
27+
AccountMeta::new(
28+
operator_bond_pda(&operator.compatize()).modernize(),
29+
false,
30+
),
31+
AccountMeta::new_readonly(protocol_config_pda().modernize(), false),
32+
AccountMeta::new_readonly(system_program::id(), false),
33+
],
34+
data: [
35+
DlpV2Instruction::RegisterOperator.to_vec(),
36+
args.encode().unwrap(),
37+
]
38+
.concat(),
39+
}
40+
}

‎dlp-api/src/v2/pda.rs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::compat::Pubkey;
22

33
pub const PROTOCOL_CONFIG_SEED: &[u8] = b"protocol-config";
4+
pub const OPERATOR_BOND_SEED: &[u8] = b"operator-bond";
45
pub const VERIFIER_REGISTRY_SEED: &[u8] = b"verifier-registry";
56

67
pub fn protocol_config_pda() -> Pubkey {
@@ -10,3 +11,11 @@ pub fn protocol_config_pda() -> Pubkey {
1011
pub fn verifier_registry_pda() -> Pubkey {
1112
Pubkey::find_program_address(&[VERIFIER_REGISTRY_SEED], &crate::id()).0
1213
}
14+
15+
pub fn operator_bond_pda(operator: &Pubkey) -> Pubkey {
16+
Pubkey::find_program_address(
17+
&[OPERATOR_BOND_SEED, operator.as_ref()],
18+
&crate::id(),
19+
)
20+
.0
21+
}

‎dlp-api/src/v2/state/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
mod operator_bond;
12
mod protocol_config;
23
mod utils;
34
mod verifier_registry;
45

6+
pub use operator_bond::*;
57
pub use protocol_config::*;
68
pub use utils::layout_error_to_program_error;
79
pub use verifier_registry::*;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use wheels::{
2+
fixed_offset_layout,
3+
layout::{Decodable, Encodable},
4+
};
5+
6+
use crate::{compat::Pubkey, solana_program::program_error::ProgramError};
7+
8+
pub const OPERATOR_STATUS_ACTIVE: u8 = 1;
9+
pub const OPERATOR_STATUS_EXITING: u8 = 2;
10+
pub const OPERATOR_STATUS_SLASHED: u8 = 3;
11+
pub const OPERATOR_STATUS_JAILED: u8 = 4;
12+
13+
#[derive(Clone, Debug, PartialEq, Eq)]
14+
#[fixed_offset_layout(buffer_offset = 0)]
15+
pub struct OperatorBond {
16+
pub operator_identity: Pubkey,
17+
pub stake_lamports: u64,
18+
pub locked_lamports: u64,
19+
pub status: u8,
20+
pub withdraw_requested_slot: Option<u64>,
21+
}
22+
23+
impl OperatorBond {
24+
pub const DISCRIMINATOR: [u8; 8] = *b"v2opbond";
25+
pub const SPACE: usize = 8 + Self::DATA_LEN;
26+
27+
pub fn to_bytes_with_discriminator(
28+
&self,
29+
data: &mut [u8],
30+
) -> Result<(), ProgramError> {
31+
let payload = super::utils::payload_with_discriminator_mut(
32+
&Self::DISCRIMINATOR,
33+
data,
34+
)?;
35+
self.encode_to(payload)
36+
.map_err(super::utils::layout_error_to_program_error)?;
37+
Ok(())
38+
}
39+
40+
pub fn try_from_bytes_with_discriminator(
41+
data: &[u8],
42+
) -> Result<Self, ProgramError> {
43+
let payload = super::utils::payload_with_discriminator(
44+
&Self::DISCRIMINATOR,
45+
data,
46+
)?;
47+
let view = <Self as Decodable>::decode(payload)
48+
.map_err(super::utils::layout_error_to_program_error)?;
49+
50+
Ok(Self {
51+
operator_identity: *view.operator_identity(),
52+
stake_lamports: view.stake_lamports(),
53+
locked_lamports: view.locked_lamports(),
54+
status: view.status(),
55+
withdraw_requested_slot: view.withdraw_requested_slot(),
56+
})
57+
}
58+
}

‎src/v2/processor/bootstrap/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
mod init_protocol_config;
2+
mod register_operator;
23

34
pub use init_protocol_config::*;
5+
pub use register_operator::*;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
use dlp_api::{
2+
error::DlpError,
3+
v2::{
4+
pda::{OPERATOR_BOND_SEED, PROTOCOL_CONFIG_SEED},
5+
OperatorBond, ProtocolConfig, RegisterOperatorArgs,
6+
OPERATOR_STATUS_ACTIVE,
7+
},
8+
};
9+
use solana_sdk_ids::system_program;
10+
use solana_system_interface::instruction as system_instruction;
11+
12+
use crate::{
13+
processor::utils::{
14+
loaders::{
15+
load_initialized_pda, load_program, load_signer,
16+
load_uninitialized_pda,
17+
},
18+
pda::create_pda,
19+
},
20+
solana_program::{
21+
account_info::AccountInfo, entrypoint::ProgramResult, program::invoke,
22+
program_error::ProgramError, pubkey::Pubkey,
23+
},
24+
};
25+
26+
/// Register one operator for v2 commitments.
27+
///
28+
/// Accounts:
29+
/// 0: `[signer, writable]` operator identity and stake payer
30+
/// 1: `[signer]` protocol authority that admits the operator
31+
/// 2: `[writable]` OperatorBond PDA
32+
/// 3: `[]` ProtocolConfig PDA
33+
/// 4: `[]` system program
34+
pub fn process_register_operator(
35+
_program_id: &Pubkey,
36+
accounts: &[AccountInfo],
37+
data: &[u8],
38+
) -> ProgramResult {
39+
let args = RegisterOperatorArgs::try_from_bytes(data)?;
40+
41+
let [operator, authority, operator_bond, protocol_config, system_program] =
42+
accounts
43+
else {
44+
return Err(ProgramError::NotEnoughAccountKeys);
45+
};
46+
47+
load_signer(operator, "operator")?;
48+
load_signer(authority, "authority")?;
49+
load_program(system_program, system_program::id(), "system program")?;
50+
51+
load_initialized_pda(
52+
protocol_config,
53+
&[PROTOCOL_CONFIG_SEED],
54+
&crate::id(),
55+
false,
56+
"protocol config",
57+
)?;
58+
59+
let protocol_config_data = protocol_config.try_borrow_data()?;
60+
let protocol_config_state =
61+
ProtocolConfig::try_from_bytes_with_discriminator(
62+
protocol_config_data.as_ref(),
63+
)?;
64+
65+
if protocol_config_state.authority != *authority.key {
66+
return Err(DlpError::InvalidAuthority.into());
67+
}
68+
69+
if *operator.key == Pubkey::default()
70+
|| args.amount_lamports < protocol_config_state.min_operator_bond
71+
{
72+
return Err(ProgramError::InvalidInstructionData);
73+
}
74+
75+
let operator_bond_bump = load_uninitialized_pda(
76+
operator_bond,
77+
&[OPERATOR_BOND_SEED, operator.key.as_ref()],
78+
&crate::id(),
79+
true,
80+
"operator bond",
81+
)?;
82+
83+
create_pda(
84+
operator_bond,
85+
&crate::id(),
86+
OperatorBond::SPACE,
87+
&[OPERATOR_BOND_SEED, operator.key.as_ref()],
88+
operator_bond_bump,
89+
system_program,
90+
operator,
91+
)?;
92+
93+
invoke(
94+
&system_instruction::transfer(
95+
operator.key,
96+
operator_bond.key,
97+
args.amount_lamports,
98+
),
99+
&[
100+
operator.clone(),
101+
operator_bond.clone(),
102+
system_program.clone(),
103+
],
104+
)?;
105+
106+
let operator_bond_state = OperatorBond {
107+
operator_identity: *operator.key,
108+
stake_lamports: args.amount_lamports,
109+
locked_lamports: 0,
110+
status: OPERATOR_STATUS_ACTIVE,
111+
withdraw_requested_slot: None,
112+
};
113+
114+
let mut operator_bond_data = operator_bond.try_borrow_mut_data()?;
115+
operator_bond_state
116+
.to_bytes_with_discriminator(operator_bond_data.as_mut())?;
117+
118+
Ok(())
119+
}

0 commit comments

Comments
 (0)