Skip to content

Commit 49448ff

Browse files
authored
fix(svm): root bundles over cctp (#687)
* fix: relay root bundle over cctp Signed-off-by: Reinis Martinsons <reinis@umaproject.org> * fix: support emit_cpi in remote relay root bundle Signed-off-by: Reinis Martinsons <reinis@umaproject.org> * fix: update relay root bundle in tests and scripts Signed-off-by: Reinis Martinsons <reinis@umaproject.org> * fix: remote emergency delete bundle Signed-off-by: Reinis Martinsons <reinis@umaproject.org> * fix: remove redundant mut Signed-off-by: Reinis Martinsons <reinis@umaproject.org> * fix: no mut state Signed-off-by: Reinis Martinsons <reinis@umaproject.org> * fix: mut state in test Signed-off-by: Reinis Martinsons <reinis@umaproject.org> --------- Signed-off-by: Reinis Martinsons <reinis@umaproject.org>
1 parent fb49aaf commit 49448ff

File tree

10 files changed

+246
-23
lines changed

10 files changed

+246
-23
lines changed

programs/svm-spoke/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub enum CalldataError {
1010
InvalidBool,
1111
#[msg("Invalid solidity address argument")]
1212
InvalidAddress,
13+
#[msg("Invalid solidity uint32 argument")]
14+
InvalidUint32,
1315
#[msg("Invalid solidity uint64 argument")]
1416
InvalidUint64,
1517
#[msg("Invalid solidity uint128 argument")]

programs/svm-spoke/src/instructions/admin.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,18 +210,20 @@ pub fn set_enable_route(
210210
#[derive(Accounts)]
211211
pub struct RelayRootBundle<'info> {
212212
#[account(
213-
mut, // TODO: remove this mut and have separate payer when adding support to invoke this via CCTP.
214213
constraint = is_local_or_remote_owner(&signer, &state) @ CustomError::NotOwner
215214
)]
216215
pub signer: Signer<'info>,
217216

217+
#[account(mut)]
218+
pub payer: Signer<'info>,
219+
218220
// TODO: standardize usage of state.seed vs state.key()
219221
#[account(mut, seeds = [b"state", state.seed.to_le_bytes().as_ref()], bump)]
220222
pub state: Account<'info, State>,
221223

222224
// TODO: consider deriving seed from state.seed instead of state.key() as this could be cheaper (need to verify).
223225
#[account(init, // TODO: add comment explaining why init
224-
payer = signer,
226+
payer = payer,
225227
space = DISCRIMINATOR_SIZE + RootBundle::INIT_SPACE,
226228
seeds =[b"root_bundle", state.key().as_ref(), state.root_bundle_id.to_le_bytes().as_ref()],
227229
bump)]
@@ -257,17 +259,21 @@ pub fn relay_root_bundle(
257259
#[instruction(root_bundle_id: u32)]
258260
pub struct EmergencyDeleteRootBundle<'info> {
259261
#[account(
260-
mut,
261262
constraint = is_local_or_remote_owner(&signer, &state) @ CustomError::NotOwner
262263
)]
263264
pub signer: Signer<'info>,
264265

266+
#[account(mut)]
267+
// We do not restrict who can receive lamports from closing root_bundle account as that would require storing the
268+
// original payer when root bundle was relayed and unnecessarily make it more expensive to relay in the happy path.
269+
pub closer: SystemAccount<'info>,
270+
265271
#[account(seeds = [b"state", state.seed.to_le_bytes().as_ref()], bump)]
266272
pub state: Account<'info, State>,
267273

268274
#[account(mut,
269275
seeds =[b"root_bundle", state.key().as_ref(), root_bundle_id.to_le_bytes().as_ref()],
270-
close = signer,
276+
close = closer,
271277
bump)]
272278
pub root_bundle: Account<'info, RootBundle>,
273279
}

programs/svm-spoke/src/instructions/handle_receive_message.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ fn translate_message(data: &Vec<u8>) -> Result<Vec<u8>> {
8282
(origin_token, destination_chain_id, enabled)
8383
.encode_instruction_data("global:set_enable_route")
8484
}
85+
s if s == utils::encode_solidity_selector("relayRootBundle(bytes32,bytes32)") => {
86+
let relayer_refund_root = utils::get_solidity_arg(data, 0)?;
87+
let slow_relay_root = utils::get_solidity_arg(data, 1)?;
88+
89+
(relayer_refund_root, slow_relay_root)
90+
.encode_instruction_data("global:relay_root_bundle")
91+
}
92+
s if s == utils::encode_solidity_selector("emergencyDeleteRootBundle(uint256)") => {
93+
let root_id = utils::decode_solidity_uint32(&utils::get_solidity_arg(data, 0)?)?;
94+
95+
root_id.encode_instruction_data("global:emergency_delete_root_bundle")
96+
}
8597
_ => Err(CalldataError::UnsupportedSelector.into()),
8698
}
8799
}

programs/svm-spoke/src/utils/cctp_utils.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ pub fn get_self_authority_pda() -> Pubkey {
6161
pda_address
6262
}
6363

64+
pub fn decode_solidity_uint32(data: &[u8; 32]) -> Result<u32> {
65+
let h_value = u128::from_be_bytes(data[..16].try_into().unwrap());
66+
let l_value = u128::from_be_bytes(data[16..].try_into().unwrap());
67+
if h_value > 0 || l_value > u32::MAX as u128 {
68+
return err!(CalldataError::InvalidUint32);
69+
}
70+
Ok(l_value as u32)
71+
}
72+
6473
pub fn decode_solidity_uint64(data: &[u8; 32]) -> Result<u64> {
6574
let h_value = u128::from_be_bytes(data[..16].try_into().unwrap());
6675
let l_value = u128::from_be_bytes(data[16..].try_into().unwrap());

scripts/svm/simpleFakeRelayerRepayment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ async function testBundleLogic(): Promise<void> {
167167
state: statePda,
168168
rootBundle: rootBundle,
169169
signer: signer,
170+
payer: signer,
170171
systemProgram: SystemProgram.programId,
171172
})
172173
.rpc();

test/svm/SvmSpoke.Bundle.ts

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,13 @@ describe("svm_spoke.bundle", () => {
100100
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
101101

102102
// Try to relay root bundle as non-owner
103-
let relayRootBundleAccounts = { state: state, rootBundle, signer: nonOwner.publicKey, program: program.programId };
103+
let relayRootBundleAccounts = {
104+
state: state,
105+
rootBundle,
106+
signer: nonOwner.publicKey,
107+
payer: nonOwner.publicKey,
108+
program: program.programId,
109+
};
104110
try {
105111
await program.methods
106112
.relayRootBundle(relayerRefundRootArray, slowRelayRootArray)
@@ -113,7 +119,7 @@ describe("svm_spoke.bundle", () => {
113119
}
114120

115121
// Relay root bundle as owner
116-
relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
122+
relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
117123
await program.methods
118124
.relayRootBundle(relayerRefundRootArray, slowRelayRootArray)
119125
.accounts(relayRootBundleAccounts)
@@ -145,7 +151,13 @@ describe("svm_spoke.bundle", () => {
145151
const seeds2 = [Buffer.from("root_bundle"), state.toBuffer(), rootBundleIdBuffer2];
146152
const [rootBundle2] = PublicKey.findProgramAddressSync(seeds2, program.programId);
147153

148-
relayRootBundleAccounts = { state, rootBundle: rootBundle2, signer: owner, program: program.programId };
154+
relayRootBundleAccounts = {
155+
state,
156+
rootBundle: rootBundle2,
157+
signer: owner,
158+
payer: owner,
159+
program: program.programId,
160+
};
149161
await program.methods
150162
.relayRootBundle(relayerRefundRootArray2, slowRelayRootArray2)
151163
.accounts(relayRootBundleAccounts)
@@ -169,7 +181,7 @@ describe("svm_spoke.bundle", () => {
169181
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
170182

171183
// Relay root bundle as owner
172-
const relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
184+
const relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
173185
const tx = await program.methods
174186
.relayRootBundle(relayerRefundRootArray, slowRelayRootArray)
175187
.accounts(relayRootBundleAccounts)
@@ -219,7 +231,7 @@ describe("svm_spoke.bundle", () => {
219231
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
220232

221233
// Relay root bundle
222-
let relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
234+
let relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
223235
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
224236

225237
const remainingAccounts = [
@@ -341,7 +353,7 @@ describe("svm_spoke.bundle", () => {
341353
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
342354

343355
// Relay root bundle
344-
let relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
356+
let relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
345357
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
346358

347359
const remainingAccounts = [
@@ -672,7 +684,7 @@ describe("svm_spoke.bundle", () => {
672684
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
673685

674686
// Relay root bundle
675-
let relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
687+
let relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
676688
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
677689

678690
const remainingAccounts = [
@@ -734,7 +746,7 @@ describe("svm_spoke.bundle", () => {
734746
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
735747

736748
// Relay root bundle
737-
let relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
749+
let relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
738750
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
739751

740752
const remainingAccounts = [
@@ -796,7 +808,7 @@ describe("svm_spoke.bundle", () => {
796808
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
797809

798810
// Relay root bundle
799-
let relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
811+
let relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
800812
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
801813

802814
const remainingAccounts = [{ pubkey: relayerTA, isWritable: true, isSigner: false }];
@@ -886,7 +898,7 @@ describe("svm_spoke.bundle", () => {
886898
const seeds = [Buffer.from("root_bundle"), state.toBuffer(), rootBundleIdBuffer];
887899
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
888900

889-
const relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
901+
const relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
890902
await program.methods
891903
.relayRootBundle(relayerRefundRootArray, slowRelayRootArray)
892904
.accounts(relayRootBundleAccounts)
@@ -902,6 +914,7 @@ describe("svm_spoke.bundle", () => {
902914
state,
903915
rootBundle,
904916
signer: nonOwner.publicKey,
917+
closer: nonOwner.publicKey,
905918
program: program.programId,
906919
};
907920
await program.methods
@@ -915,7 +928,13 @@ describe("svm_spoke.bundle", () => {
915928
}
916929

917930
// Execute the emergency delete
918-
const emergencyDeleteRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
931+
const emergencyDeleteRootBundleAccounts = {
932+
state,
933+
rootBundle,
934+
signer: owner,
935+
closer: owner,
936+
program: program.programId,
937+
};
919938
await program.methods.emergencyDeleteRootBundle(rootBundleId).accounts(emergencyDeleteRootBundleAccounts).rpc();
920939

921940
// Verify that the root bundle has been deleted
@@ -943,7 +962,13 @@ describe("svm_spoke.bundle", () => {
943962
`Root bundle index should be ${initialRootBundleId + 1}`
944963
);
945964

946-
const newRelayRootBundleAccounts = { state, rootBundle: newRootBundle, signer: owner, program: program.programId };
965+
const newRelayRootBundleAccounts = {
966+
state,
967+
rootBundle: newRootBundle,
968+
signer: owner,
969+
payer: owner,
970+
program: program.programId,
971+
};
947972
await program.methods
948973
.relayRootBundle(newRelayerRefundRootArray, newSlowRelayRootArray)
949974
.accounts(newRelayRootBundleAccounts)
@@ -1034,7 +1059,7 @@ describe("svm_spoke.bundle", () => {
10341059
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
10351060

10361061
// Relay root bundle
1037-
const relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
1062+
const relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
10381063
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
10391064

10401065
// Verify valid leaf
@@ -1186,7 +1211,7 @@ describe("svm_spoke.bundle", () => {
11861211
rootBundleIdBuffer.writeUInt32LE(rootBundleId);
11871212
const seeds = [Buffer.from("root_bundle"), state.toBuffer(), rootBundleIdBuffer];
11881213
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
1189-
let relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
1214+
let relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
11901215
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
11911216
const proofAsNumbers = proof.map((p) => Array.from(p));
11921217
const executeRelayerRefundLeafAccounts = {
@@ -1262,7 +1287,7 @@ describe("svm_spoke.bundle", () => {
12621287
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
12631288

12641289
// Relay root bundle
1265-
const relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
1290+
const relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
12661291
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
12671292

12681293
const remainingAccounts = [{ pubkey: relayerTA, isWritable: true, isSigner: false }];
@@ -1336,7 +1361,7 @@ describe("svm_spoke.bundle", () => {
13361361
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
13371362

13381363
// Relay root bundle
1339-
const relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
1364+
const relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
13401365
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
13411366

13421367
const remainingAccounts = [
@@ -1439,7 +1464,7 @@ describe("svm_spoke.bundle", () => {
14391464
const [rootBundle] = PublicKey.findProgramAddressSync(seeds, program.programId);
14401465

14411466
// Relay root bundle
1442-
const relayRootBundleAccounts = { state, rootBundle, signer: owner, program: program.programId };
1467+
const relayRootBundleAccounts = { state, rootBundle, signer: owner, payer: owner, program: program.programId };
14431468
await program.methods.relayRootBundle(Array.from(root), Array.from(root)).accounts(relayRootBundleAccounts).rpc();
14441469

14451470
// Pass refund addresses in remaining accounts.

0 commit comments

Comments
 (0)