Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bolt-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ Commands:
deposit Deposit into a strategy
register Register an operator into the bolt AVS
deregister Deregister an EigenLayer operator from the bolt AVS
update-rpc Update the operator RPC
status Check the status of an operator in the bolt AVS
help Print this message or the help of the given subcommand(s)

Expand All @@ -354,6 +355,7 @@ Usage: bolt operators symbiotic <COMMAND>
Commands:
register Register into the bolt manager contract as a Symbiotic operator
deregister Deregister a Symbiotic operator from bolt
update-rpc Update the operator RPC
status Check the status of a Symbiotic operator
help Print this message or the help of the given subcommand(s)

Expand Down
26 changes: 26 additions & 0 deletions bolt-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,19 @@ pub enum EigenLayerSubcommand {
operator_private_key: B256,
},

/// Update the operator RPC.
UpdateRpc {
/// The URL of the RPC to broadcast the transaction.
#[clap(long, env = "RPC_URL")]
rpc_url: Url,
/// The private key of the operator.
#[clap(long, env = "OPERATOR_PRIVATE_KEY")]
operator_private_key: B256,
/// The URL of the operator RPC.
#[clap(long, env = "OPERATOR_RPC")]
operator_rpc: Url,
},

/// Check the status of an operator in the bolt AVS.
Status {
/// The URL of the RPC to broadcast the transaction.
Expand Down Expand Up @@ -305,6 +318,19 @@ pub enum SymbioticSubcommand {
operator_private_key: B256,
},

/// Update the operator RPC.
UpdateRpc {
/// The URL of the RPC to broadcast the transaction.
#[clap(long, env = "RPC_URL")]
rpc_url: Url,
/// The private key of the operator.
#[clap(long, env = "OPERATOR_PRIVATE_KEY")]
operator_private_key: B256,
/// The URL of the operator RPC.
#[clap(long, env = "OPERATOR_RPC")]
operator_rpc: Url,
},

/// Check the status of a Symbiotic operator.
Status {
/// The URL of the RPC to broadcast the transaction.
Expand Down
105 changes: 101 additions & 4 deletions bolt-cli/src/commands/operators/eigenlayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ use tracing::{info, warn};

use crate::{
cli::{Chain, EigenLayerSubcommand},
common::{bolt_manager::BoltManagerContract, request_confirmation, try_parse_contract_error},
common::{
bolt_manager::BoltManagerContract::{self, BoltManagerContractErrors},
request_confirmation, try_parse_contract_error,
},
contracts::{
bolt::{
BoltEigenLayerMiddleware::{self, BoltEigenLayerMiddlewareErrors},
Expand Down Expand Up @@ -161,6 +164,9 @@ impl EigenLayerSubcommand {
BoltEigenLayerMiddlewareErrors::NotOperator(_) => {
eyre::bail!("Operator not registered in EigenLayer")
}
BoltEigenLayerMiddlewareErrors::SaltSpent(_) => {
eyre::bail!("Salt already spent")
}
other => unreachable!(
"Unexpected error with selector {:?}",
other.selector()
Expand Down Expand Up @@ -224,6 +230,59 @@ impl EigenLayerSubcommand {
Ok(())
}

Self::UpdateRpc { rpc_url, operator_private_key, operator_rpc } => {
let signer = PrivateKeySigner::from_bytes(&operator_private_key)
.wrap_err("valid private key")?;
let address = signer.address();

let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(EthereumWallet::from(signer))
.on_http(rpc_url);

let chain = Chain::try_from_provider(&provider).await?;

info!(operator = %address, rpc = %operator_rpc, ?chain, "Updating EigenLayer operator RPC");

request_confirmation();

let deployments = deployments_for_chain(chain);

let bolt_manager =
BoltManagerContract::new(deployments.bolt.manager, provider.clone());
if bolt_manager.isOperator(address).call().await?._0 {
info!(?address, "EigenLayer operator is registered");
} else {
warn!(?address, "Operator not registered");
return Ok(());
}

match bolt_manager.updateOperatorRPC(operator_rpc.to_string()).send().await {
Ok(pending) => {
info!(
hash = ?pending.tx_hash(),
"updateOperatorRPC transaction sent, awaiting receipt..."
);

let receipt = pending.get_receipt().await?;
if !receipt.status() {
eyre::bail!("Transaction failed: {:?}", receipt)
}

info!("Succesfully updated EigenLayer operator RPC");
}
Err(e) => match try_parse_contract_error::<BoltManagerContractErrors>(e)? {
BoltManagerContractErrors::OperatorNotRegistered(_) => {
eyre::bail!("Operator not registered in bolt")
}
other => {
unreachable!("Unexpected error with selector {:?}", other.selector())
}
},
}
Ok(())
}

Self::Status { rpc_url: rpc, address } => {
let provider = ProviderBuilder::new().on_http(rpc.clone());

Expand All @@ -236,6 +295,21 @@ impl EigenLayerSubcommand {
info!(?address, "EigenLayer operator is registered");
} else {
warn!(?address, "Operator not registered");
return Ok(())
}

match bolt_manager.getOperatorData(address).call().await {
Ok(operator_data) => {
info!(?address, operator_data = ?operator_data._0, "Operator data");
}
Err(e) => match try_parse_contract_error::<BoltManagerContractErrors>(e)? {
BoltManagerContractErrors::KeyNotFound(_) => {
warn!(?address, "Operator data not found");
}
other => {
unreachable!("Unexpected error with selector {:?}", other.selector())
}
},
}

// Check if operator has collateral
Expand Down Expand Up @@ -293,18 +367,18 @@ mod tests {
sol_types::SolValue,
};
use alloy_node_bindings::WEI_IN_ETHER;
use reqwest::Url;

#[tokio::test]
async fn test_eigenlayer_flow() {
let _ = tracing_subscriber::fmt::try_init();
let s1 = PrivateKeySigner::random();
let secret_key = s1.to_bytes();

let wallet = EthereumWallet::new(s1);

let rpc_url = "https://holesky.drpc.org";
let anvil = Anvil::default().fork(rpc_url).spawn();
let anvil_url = Url::parse(&anvil.endpoint()).expect("valid URL");
let anvil_url = anvil.endpoint_url();
let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(wallet)
Expand Down Expand Up @@ -351,7 +425,7 @@ mod tests {
.expect("to get receipt for register as operator");

assert!(receipt.status(), "operator should be registered");
println!("Registered operator with address {}", account);
// println!("Registered operator with address {}", account);

let is_operator = delegation_manager
.isOperator(account)
Expand Down Expand Up @@ -403,6 +477,29 @@ mod tests {

check_operator_registration.run().await.expect("to check operator registration");

let update_rpc = OperatorsCommand {
subcommand: OperatorsSubcommand::EigenLayer {
subcommand: EigenLayerSubcommand::UpdateRpc {
rpc_url: anvil_url.clone(),
operator_private_key: secret_key,
operator_rpc: "https://boooooolt.chainbound.io/rpc".parse().expect("valid url"),
},
},
};

update_rpc.run().await.expect("to update operator rpc");

let check_operator_registration = OperatorsCommand {
subcommand: OperatorsSubcommand::EigenLayer {
subcommand: EigenLayerSubcommand::Status {
rpc_url: anvil_url.clone(),
address: account,
},
},
};

check_operator_registration.run().await.expect("to check operator registration");

let deregister_operator = OperatorsCommand {
subcommand: OperatorsSubcommand::EigenLayer {
subcommand: EigenLayerSubcommand::Deregister {
Expand Down
98 changes: 97 additions & 1 deletion bolt-cli/src/commands/operators/symbiotic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ use tracing::{info, warn};

use crate::{
cli::{Chain, SymbioticSubcommand},
common::{bolt_manager::BoltManagerContract, request_confirmation, try_parse_contract_error},
common::{
bolt_manager::BoltManagerContract::{self, BoltManagerContractErrors},
request_confirmation, try_parse_contract_error,
},
contracts::{
bolt::BoltSymbioticMiddleware::{self, BoltSymbioticMiddlewareErrors},
deployments_for_chain,
Expand Down Expand Up @@ -140,6 +143,59 @@ impl SymbioticSubcommand {
Ok(())
}

Self::UpdateRpc { rpc_url, operator_private_key, operator_rpc } => {
let signer = PrivateKeySigner::from_bytes(&operator_private_key)
.wrap_err("valid private key")?;
let address = signer.address();

let provider = ProviderBuilder::new()
.with_recommended_fillers()
.wallet(EthereumWallet::from(signer))
.on_http(rpc_url);

let chain = Chain::try_from_provider(&provider).await?;

info!(operator = %address, rpc = %operator_rpc, ?chain, "Updating Symbiotic operator RPC");

request_confirmation();

let deployments = deployments_for_chain(chain);

let bolt_manager =
BoltManagerContract::new(deployments.bolt.manager, provider.clone());
if bolt_manager.isOperator(address).call().await?._0 {
info!(?address, "Symbiotic operator is registered");
} else {
warn!(?address, "Operator not registered");
return Ok(())
}

match bolt_manager.updateOperatorRPC(operator_rpc.to_string()).send().await {
Ok(pending) => {
info!(
hash = ?pending.tx_hash(),
"updateOperatorRPC transaction sent, awaiting receipt..."
);

let receipt = pending.get_receipt().await?;
if !receipt.status() {
eyre::bail!("Transaction failed: {:?}", receipt)
}

info!("Succesfully updated Symbiotic operator RPC");
}
Err(e) => match try_parse_contract_error::<BoltManagerContractErrors>(e)? {
BoltManagerContractErrors::OperatorNotRegistered(_) => {
eyre::bail!("Operator not registered in bolt")
}
other => {
unreachable!("Unexpected error with selector {:?}", other.selector())
}
},
}
Ok(())
}

Self::Status { rpc_url, address } => {
let provider = ProviderBuilder::new().on_http(rpc_url.clone());

Expand All @@ -152,6 +208,21 @@ impl SymbioticSubcommand {
info!(?address, "Symbiotic operator is registered");
} else {
warn!(?address, "Operator not registered");
return Ok(())
}

match bolt_manager.getOperatorData(address).call().await {
Ok(operator_data) => {
info!(?address, operator_data = ?operator_data._0, "Operator data");
}
Err(e) => match try_parse_contract_error::<BoltManagerContractErrors>(e)? {
BoltManagerContractErrors::KeyNotFound(_) => {
warn!(?address, "Operator data not found");
}
other => {
unreachable!("Unexpected error with selector {:?}", other.selector())
}
},
}

// Check if operator has collateral
Expand Down Expand Up @@ -333,6 +404,31 @@ mod tests {

check_status.run().await.expect("to check operator status");

let update_rpc = OperatorsCommand {
subcommand: OperatorsSubcommand::Symbiotic {
subcommand: SymbioticSubcommand::UpdateRpc {
rpc_url: anvil_url.clone(),
operator_private_key: secret_key,
operator_rpc: "https://boooooooooooooooolt.chainbound.io"
.parse()
.expect("valid url"),
},
},
};

update_rpc.run().await.expect("to update operator rpc");

let check_status = OperatorsCommand {
subcommand: OperatorsSubcommand::Symbiotic {
subcommand: SymbioticSubcommand::Status {
rpc_url: anvil_url.clone(),
address: account,
},
},
};

check_status.run().await.expect("to check operator status");

let deregister_command = OperatorsCommand {
subcommand: OperatorsSubcommand::Symbiotic {
subcommand: SymbioticSubcommand::Deregister {
Expand Down
18 changes: 18 additions & 0 deletions bolt-cli/src/common/bolt_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,30 @@ sol! {
uint256[] amounts;
}

#[derive(Debug, Default, Serialize)]
struct Operator {
// RPC endpoint
string rpc;
// Middleware contract address
address middleware;
// Timestamp of registration
uint256 timestamp;
}

function getProposerStatus(bytes32 pubkeyHash) external view returns (ProposerStatus memory);

function isOperator(address operator) public view returns (bool);

function getOperatorStake(address operator, address collateral) public view returns (uint256);

/// @notice Update the RPC associated to msg.sender.
function updateOperatorRPC(string calldata rpc) external;

function getOperatorData(address operator) public view returns (Operator memory);

error InvalidQuery();
error ValidatorDoesNotExist();
error OperatorNotRegistered();
error KeyNotFound(address key);
}
}
9 changes: 8 additions & 1 deletion bolt-cli/src/contracts/bolt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,14 @@ sol! {
error AlreadyRegistered();
error NotOperator();
error NotRegistered();
error KeyNotFound();
error KeyNotFound(address key);
error SaltSpent();

// From IDelegationManager
/// @dev Thrown when an account is not actively delegated.
error NotActivelyDelegated();
/// @dev Thrown when `operator` is not a registered operator.
error OperatorNotRegistered();
}

#[allow(missing_docs)]
Expand Down
Loading