Skip to content

Commit 4302ecd

Browse files
MicaiahReidAlgorhythmic1
andauthored
feat(core): implement surfnet_setProgramAuthority cheatcode RPC endpoint (#197)
Co-authored-by: Algorhythmic1 <[email protected]>
1 parent fdc8977 commit 4302ecd

File tree

5 files changed

+259
-17
lines changed

5 files changed

+259
-17
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
target
22
.DS_Store
3+
4+
.cache
5+
test-ledger

crates/core/src/rpc/surfnet_cheatcodes.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,47 @@ pub trait SvmTricksRpc {
345345
meta: Self::Metadata,
346346
update: SupplyUpdate,
347347
) -> BoxFuture<Result<RpcResponse<()>>>;
348+
349+
/// A cheat code to set the upgrade authority of a program's ProgramData account.
350+
///
351+
/// This method allows developers to directly patch the upgrade authority of a program's ProgramData account.
352+
///
353+
/// ## Parameters
354+
/// - `meta`: Metadata passed with the request, such as the client's request context.
355+
/// - `program_id`: The base-58 encoded public key of the program.
356+
/// - `new_authority`: The base-58 encoded public key of the new authority. If omitted, the program will have no upgrade authority.
357+
///
358+
/// ## Returns
359+
/// A `RpcResponse<()>` indicating whether the authority update was successful.
360+
///
361+
/// ## Example Request
362+
/// ```json
363+
/// {
364+
/// "jsonrpc": "2.0",
365+
/// "id": 1,
366+
/// "method": "surfnet_setProgramAuthority",
367+
/// "params": [
368+
/// "PROGRAM_ID_BASE58",
369+
/// "NEW_AUTHORITY_BASE58"
370+
/// ]
371+
/// }
372+
/// ```
373+
///
374+
/// ## Example Response
375+
/// ```json
376+
/// {
377+
/// "jsonrpc": "2.0",
378+
/// "result": {},
379+
/// "id": 1
380+
/// }
381+
/// ```
382+
#[rpc(meta, name = "surfnet_setProgramAuthority")]
383+
fn set_program_authority(
384+
&self,
385+
meta: Self::Metadata,
386+
program_id_str: String,
387+
new_authority_str: Option<String>,
388+
) -> BoxFuture<Result<RpcResponse<()>>>;
348389
}
349390

350391
#[derive(Clone)]
@@ -675,4 +716,42 @@ impl SvmTricksRpc for SurfnetCheatcodesRpc {
675716
})
676717
})
677718
}
719+
720+
fn set_program_authority(
721+
&self,
722+
meta: Self::Metadata,
723+
program_id_str: String,
724+
new_authority_str: Option<String>,
725+
) -> BoxFuture<Result<RpcResponse<()>>> {
726+
let program_id = match verify_pubkey(&program_id_str) {
727+
Ok(res) => res,
728+
Err(e) => return e.into(),
729+
};
730+
let new_authority = if let Some(ref new_authority_str) = new_authority_str {
731+
match verify_pubkey(new_authority_str) {
732+
Ok(res) => Some(res),
733+
Err(e) => return e.into(),
734+
}
735+
} else {
736+
None
737+
};
738+
739+
let SurfnetRpcContext {
740+
svm_locker,
741+
remote_ctx,
742+
} = match meta.get_rpc_context(CommitmentConfig::confirmed()) {
743+
Ok(res) => res,
744+
Err(e) => return e.into(),
745+
};
746+
Box::pin(async move {
747+
let SvmAccessContext { slot, .. } = svm_locker
748+
.set_program_authority(&remote_ctx, program_id, new_authority)
749+
.await?;
750+
751+
Ok(RpcResponse {
752+
context: RpcResponseContext::new(slot),
753+
value: (),
754+
})
755+
})
756+
}
678757
}

crates/core/src/surfnet/locker.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{collections::BTreeMap, sync::Arc};
22

3+
use bincode::serialized_size;
34
use crossbeam_channel::{Receiver, Sender};
45
use itertools::Itertools;
56
use jsonrpc_core::futures::future::join_all;
@@ -1512,6 +1513,125 @@ impl SurfnetSvmLocker {
15121513
Ok(result.with_new_value(()))
15131514
}
15141515

1516+
pub async fn set_program_authority(
1517+
&self,
1518+
remote_ctx: &Option<(SurfnetRemoteClient, CommitmentConfig)>,
1519+
program_id: Pubkey,
1520+
new_authority: Option<Pubkey>,
1521+
) -> SurfpoolContextualizedResult<()> {
1522+
let SvmAccessContext {
1523+
slot,
1524+
latest_epoch_info,
1525+
latest_blockhash,
1526+
inner: mut get_account_result,
1527+
} = self.get_account(remote_ctx, &program_id, None).await?;
1528+
1529+
let original_authority = match &mut get_account_result {
1530+
GetAccountResult::None(pubkey) => {
1531+
return Err(SurfpoolError::invalid_program_account(
1532+
pubkey,
1533+
"Account not found",
1534+
))
1535+
}
1536+
GetAccountResult::FoundAccount(pubkey, program_account, _) => {
1537+
let programdata_address = get_program_data_address(pubkey);
1538+
let mut programdata_account_result = self
1539+
.get_account(remote_ctx, &programdata_address, None)
1540+
.await?
1541+
.inner;
1542+
match &mut programdata_account_result {
1543+
GetAccountResult::None(pubkey) => {
1544+
return Err(SurfpoolError::invalid_program_account(
1545+
pubkey,
1546+
"Program data account does not exist",
1547+
));
1548+
}
1549+
GetAccountResult::FoundAccount(_, programdata_account, _) => {
1550+
let original_authority = update_programdata_account(
1551+
&program_id,
1552+
programdata_account,
1553+
new_authority,
1554+
)?;
1555+
1556+
get_account_result = GetAccountResult::FoundProgramAccount(
1557+
(pubkey.clone(), program_account.clone()),
1558+
(programdata_address, Some(programdata_account.clone())),
1559+
);
1560+
1561+
original_authority
1562+
}
1563+
GetAccountResult::FoundProgramAccount(_, _) => {
1564+
return Err(SurfpoolError::invalid_program_account(
1565+
pubkey,
1566+
"Not a program account",
1567+
));
1568+
}
1569+
}
1570+
}
1571+
GetAccountResult::FoundProgramAccount(_, (_, None)) => {
1572+
return Err(SurfpoolError::invalid_program_account(
1573+
&program_id,
1574+
"Program data account does not exist",
1575+
))
1576+
}
1577+
GetAccountResult::FoundProgramAccount(_, (_, Some(programdata_account))) => {
1578+
update_programdata_account(&program_id, programdata_account, new_authority)?
1579+
}
1580+
};
1581+
1582+
let simnet_events_tx = self.simnet_events_tx();
1583+
match (original_authority, new_authority) {
1584+
(Some(original), Some(new)) => {
1585+
if original != new {
1586+
let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1587+
"Setting new authority for program {}",
1588+
program_id
1589+
)));
1590+
let _ = simnet_events_tx
1591+
.send(SimnetEvent::info(format!("Old Authority: {}", original)));
1592+
let _ =
1593+
simnet_events_tx.send(SimnetEvent::info(format!("New Authority: {}", new)));
1594+
} else {
1595+
let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1596+
"No authority change for program {}",
1597+
program_id
1598+
)));
1599+
}
1600+
}
1601+
(Some(original), None) => {
1602+
let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1603+
"Removing authority for program {}",
1604+
program_id
1605+
)));
1606+
let _ = simnet_events_tx
1607+
.send(SimnetEvent::info(format!("Old Authority: {}", original)));
1608+
}
1609+
(None, Some(new)) => {
1610+
let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1611+
"Setting new authority for program {}",
1612+
program_id
1613+
)));
1614+
let _ = simnet_events_tx.send(SimnetEvent::info(format!("Old Authority: None")));
1615+
let _ = simnet_events_tx.send(SimnetEvent::info(format!("New Authority: {}", new)));
1616+
}
1617+
(None, None) => {
1618+
let _ = simnet_events_tx.send(SimnetEvent::info(format!(
1619+
"No authority change for program {}",
1620+
program_id
1621+
)));
1622+
}
1623+
};
1624+
1625+
self.write_account_update(get_account_result);
1626+
1627+
Ok(SvmAccessContext::new(
1628+
slot,
1629+
latest_epoch_info,
1630+
latest_blockhash,
1631+
(),
1632+
))
1633+
}
1634+
15151635
pub async fn get_program_accounts(
15161636
&self,
15171637
remote_ctx: &Option<SurfnetRemoteClient>,
@@ -1767,3 +1887,51 @@ fn apply_rpc_filters(account_data: &[u8], filters: &[RpcFilterType]) -> Surfpool
17671887
pub fn is_supported_token_program(program_id: &Pubkey) -> bool {
17681888
*program_id == spl_token::ID || *program_id == spl_token_2022::ID
17691889
}
1890+
1891+
fn update_programdata_account(
1892+
program_id: &Pubkey,
1893+
programdata_account: &mut Account,
1894+
new_authority: Option<Pubkey>,
1895+
) -> SurfpoolResult<Option<Pubkey>> {
1896+
let upgradeable_loader_state =
1897+
bincode::deserialize::<UpgradeableLoaderState>(&programdata_account.data).map_err(|e| {
1898+
SurfpoolError::invalid_program_account(
1899+
&program_id,
1900+
format!("Failed to serialize program data: {}", e),
1901+
)
1902+
})?;
1903+
if let UpgradeableLoaderState::ProgramData {
1904+
upgrade_authority_address,
1905+
slot,
1906+
} = upgradeable_loader_state
1907+
{
1908+
let offset = if upgrade_authority_address.is_some() {
1909+
UpgradeableLoaderState::size_of_programdata_metadata()
1910+
} else {
1911+
UpgradeableLoaderState::size_of_programdata_metadata()
1912+
- serialized_size(&Pubkey::default()).unwrap() as usize
1913+
};
1914+
1915+
let mut data = bincode::serialize(&UpgradeableLoaderState::ProgramData {
1916+
upgrade_authority_address: new_authority,
1917+
slot,
1918+
})
1919+
.map_err(|e| {
1920+
SurfpoolError::invalid_program_account(
1921+
&program_id,
1922+
format!("Failed to serialize program data: {}", e),
1923+
)
1924+
})?;
1925+
1926+
data.append(&mut programdata_account.data[offset..].to_vec());
1927+
1928+
programdata_account.data = data;
1929+
1930+
Ok(upgrade_authority_address)
1931+
} else {
1932+
return Err(SurfpoolError::invalid_program_account(
1933+
&program_id,
1934+
"Invalid program data account",
1935+
));
1936+
}
1937+
}

crates/core/src/surfnet/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ pub enum SignatureSubscriptionType {
9393

9494
type DoUpdateSvm = bool;
9595

96-
#[derive(Clone)]
96+
#[derive(Clone, Debug)]
9797
/// Represents the result of a get_account operation.
9898
pub enum GetAccountResult {
9999
/// Represents that the account was not found.

crates/core/src/surfnet/svm.rs

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -737,17 +737,13 @@ impl SurfnetSvm {
737737
GetAccountResult::FoundAccount(pubkey, account, do_update_account) => {
738738
if do_update_account {
739739
if let Err(e) = self.set_account(&pubkey, account.clone()) {
740-
let _ = self
741-
.simnet_events_tx
742-
.send(SimnetEvent::error(e.to_string()));
740+
let _ = self.simnet_events_tx.send(SimnetEvent::warn(e.to_string()));
743741
}
744742
}
745743
}
746744
GetAccountResult::FoundProgramAccount((pubkey, account), (_, None)) => {
747745
if let Err(e) = self.set_account(&pubkey, account.clone()) {
748-
let _ = self
749-
.simnet_events_tx
750-
.send(SimnetEvent::error(e.to_string()));
746+
let _ = self.simnet_events_tx.send(SimnetEvent::warn(e.to_string()));
751747
}
752748
}
753749
GetAccountResult::FoundProgramAccount(
@@ -756,14 +752,10 @@ impl SurfnetSvm {
756752
) => {
757753
// The data account _must_ be set first, as the program account depends on it.
758754
if let Err(e) = self.set_account(&data_pubkey, data_account.clone()) {
759-
let _ = self
760-
.simnet_events_tx
761-
.send(SimnetEvent::error(e.to_string()));
755+
let _ = self.simnet_events_tx.send(SimnetEvent::warn(e.to_string()));
762756
};
763757
if let Err(e) = self.set_account(&pubkey, account.clone()) {
764-
let _ = self
765-
.simnet_events_tx
766-
.send(SimnetEvent::error(e.to_string()));
758+
let _ = self.simnet_events_tx.send(SimnetEvent::warn(e.to_string()));
767759
};
768760
}
769761
GetAccountResult::None(_) => {}
@@ -1317,11 +1309,11 @@ mod tests {
13171309
}
13181310
}
13191311

1320-
fn expect_error_event(events_rx: &Receiver<SimnetEvent>, expected_error: &str) -> bool {
1312+
fn expect_warn_event(events_rx: &Receiver<SimnetEvent>, expected_warning: &str) -> bool {
13211313
match events_rx.recv() {
13221314
Ok(event) => match event {
1323-
SimnetEvent::ErrorLog(_, err) => {
1324-
assert_eq!(err, expected_error);
1315+
SimnetEvent::WarnLog(_, warn) => {
1316+
assert_eq!(warn, expected_warning);
13251317
true
13261318
}
13271319
event => {
@@ -1422,7 +1414,7 @@ mod tests {
14221414
(program_data_address, None),
14231415
);
14241416
svm.write_account_update(found_program_account_update);
1425-
if !expect_error_event(&events_rx, &format!("Internal error: \"Failed to set account {}: An account required by the instruction is missing\"", program_address)) {
1417+
if !expect_warn_event(&events_rx, &format!("Internal error: \"Failed to set account {}: An account required by the instruction is missing\"", program_address)) {
14261418
panic!("Expected error event not received after inserting program account with no program data account");
14271419
}
14281420
assert_eq!(svm.accounts_registry, index_before);

0 commit comments

Comments
 (0)