Skip to content

Commit 24eb902

Browse files
authored
fix: add validation for connected program pda (#90)
1 parent f16171f commit 24eb902

File tree

2 files changed

+209
-4
lines changed

2 files changed

+209
-4
lines changed

programs/gateway/src/lib.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ pub mod gateway {
332332
let token = &ctx.accounts.token_program;
333333
let signer_seeds: &[&[&[u8]]] = &[&[b"meta", &[ctx.bumps.pda]]];
334334

335-
// make sure that ctx.accounts.recipient_ata is ATA (PDA account of token program)
335+
// make sure that ctx.accounts.destination_program_pda_ata is ATA of destination_program
336336
let recipient_ata = get_associated_token_address(
337337
&ctx.accounts.destination_program_pda.key(),
338338
&ctx.accounts.mint_account.key(),
@@ -346,9 +346,7 @@ pub mod gateway {
346346
.key(),
347347
Errors::SPLAtaAndMintAddressMismatch,
348348
);
349-
350-
// TODO: also create destination program pda ata? add later for simplicity
351-
349+
// withdraw to destination program pda
352350
let xfer_ctx = CpiContext::new_with_signer(
353351
token.to_account_info(),
354352
anchor_spl::token::TransferChecked {
@@ -986,9 +984,17 @@ pub struct Execute<'info> {
986984
pub pda: Account<'info, Pda>,
987985

988986
/// The destination program.
987+
/// CHECK: This is arbitrary program.
989988
pub destination_program: AccountInfo<'info>,
990989

991990
// Pda for destination program
991+
/// CHECK: Validation will occur during instruction processing.
992+
#[account(
993+
mut,
994+
seeds = [b"connected"],
995+
bump,
996+
seeds::program = destination_program.key()
997+
)]
992998
pub destination_program_pda: UncheckedAccount<'info>,
993999
}
9941000

@@ -1124,9 +1130,17 @@ pub struct ExecuteSPLToken<'info> {
11241130
pub mint_account: Account<'info, Mint>,
11251131

11261132
/// The destination program.
1133+
/// CHECK: This is arbitrary program.
11271134
pub destination_program: AccountInfo<'info>,
11281135

11291136
// Pda for destination program
1137+
/// CHECK: Validation will occur during instruction processing.
1138+
#[account(
1139+
mut,
1140+
seeds = [b"connected"],
1141+
bump,
1142+
seeds::program = destination_program.key()
1143+
)]
11301144
pub destination_program_pda: UncheckedAccount<'info>,
11311145

11321146
/// The destination program associated token account.

tests/gateway.ts

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,80 @@ describe("Gateway", () => {
10091009
}
10101010
});
10111011

1012+
it("Calls execute and onCall reverts if signer is passed in remaining accounts", async () => {
1013+
await gatewayProgram.methods
1014+
.deposit(new anchor.BN(1_000_000_000), Array.from(address))
1015+
.rpc();
1016+
1017+
const randomWallet = anchor.web3.Keypair.generate();
1018+
const lastMessageData = "revert";
1019+
const data = Buffer.from(lastMessageData, "utf-8");
1020+
let seeds = [Buffer.from("connected", "utf-8")];
1021+
const [connectedPdaAccount] = anchor.web3.PublicKey.findProgramAddressSync(
1022+
seeds,
1023+
connectedProgram.programId
1024+
);
1025+
const amount = new anchor.BN(500000000);
1026+
1027+
// signature
1028+
const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount);
1029+
const nonce = pdaAccountData.nonce;
1030+
const buffer = Buffer.concat([
1031+
Buffer.from("ZETACHAIN", "utf-8"),
1032+
Buffer.from([0x05]),
1033+
chain_id_bn.toArrayLike(Buffer, "be", 8),
1034+
nonce.toArrayLike(Buffer, "be", 8),
1035+
amount.toArrayLike(Buffer, "be", 8),
1036+
connectedProgram.programId.toBuffer(),
1037+
data,
1038+
]);
1039+
const message_hash = keccak256(buffer);
1040+
const signature = keyPair.sign(message_hash, "hex");
1041+
const { r, s, recoveryParam } = signature;
1042+
const signatureBuffer = Buffer.concat([
1043+
r.toArrayLike(Buffer, "be", 32),
1044+
s.toArrayLike(Buffer, "be", 32),
1045+
]);
1046+
1047+
try {
1048+
// call the `execute` function in the gateway program
1049+
await gatewayProgram.methods
1050+
.execute(
1051+
amount,
1052+
Array.from(address),
1053+
data,
1054+
Array.from(signatureBuffer),
1055+
Number(recoveryParam),
1056+
Array.from(message_hash),
1057+
nonce
1058+
)
1059+
.accountsPartial({
1060+
// mandatory predefined accounts
1061+
signer: wallet.publicKey,
1062+
pda: pdaAccount,
1063+
destinationProgram: connectedProgram.programId,
1064+
destinationProgramPda: connectedPdaAccount,
1065+
})
1066+
.remainingAccounts([
1067+
// accounts coming from withdraw and call msg
1068+
{ pubkey: wallet.publicKey, isSigner: true, isWritable: true },
1069+
{ pubkey: connectedPdaAccount, isSigner: false, isWritable: true },
1070+
{ pubkey: pdaAccount, isSigner: false, isWritable: false },
1071+
{ pubkey: randomWallet.publicKey, isSigner: false, isWritable: true },
1072+
{
1073+
pubkey: anchor.web3.SystemProgram.programId,
1074+
isSigner: false,
1075+
isWritable: false,
1076+
},
1077+
])
1078+
.rpc();
1079+
throw new Error("Expected error not thrown"); // This line will make the test fail if no error is thrown
1080+
} catch (err) {
1081+
expect(err).to.be.instanceof(anchor.AnchorError);
1082+
expect(err.message).to.include("InvalidInstructionData");
1083+
}
1084+
});
1085+
10121086
it("Calls execute spl token and onCall", async () => {
10131087
await connectedSPLProgram.methods.initialize().rpc();
10141088

@@ -1251,6 +1325,123 @@ describe("Gateway", () => {
12511325
}
12521326
});
12531327

1328+
it("Calls execute spl token and onCall reverts if signer is passed in remaining accounts", async () => {
1329+
const randomWallet = anchor.web3.Keypair.generate();
1330+
let randomWalletAta = await spl.getOrCreateAssociatedTokenAccount(
1331+
conn,
1332+
wallet,
1333+
mint.publicKey,
1334+
randomWallet.publicKey,
1335+
true
1336+
);
1337+
const lastMessageData = "revert";
1338+
const data = Buffer.from(lastMessageData, "utf-8");
1339+
let seeds = [Buffer.from("connected", "utf-8")];
1340+
const [connectedPdaAccount] = anchor.web3.PublicKey.findProgramAddressSync(
1341+
seeds,
1342+
connectedSPLProgram.programId
1343+
);
1344+
let pda_ata = await spl.getAssociatedTokenAddress(
1345+
mint.publicKey,
1346+
pdaAccount,
1347+
true
1348+
);
1349+
const pdaAccountData = await gatewayProgram.account.pda.fetch(pdaAccount);
1350+
const amount = new anchor.BN(500_000);
1351+
const nonce = pdaAccountData.nonce;
1352+
1353+
let destinationPdaAta = await spl.getOrCreateAssociatedTokenAccount(
1354+
conn,
1355+
wallet,
1356+
mint.publicKey,
1357+
connectedPdaAccount,
1358+
true
1359+
);
1360+
1361+
const buffer = Buffer.concat([
1362+
Buffer.from("ZETACHAIN", "utf-8"),
1363+
Buffer.from([0x06]),
1364+
chain_id_bn.toArrayLike(Buffer, "be", 8),
1365+
nonce.toArrayLike(Buffer, "be", 8),
1366+
amount.toArrayLike(Buffer, "be", 8),
1367+
mint.publicKey.toBuffer(),
1368+
destinationPdaAta.address.toBuffer(),
1369+
data,
1370+
]);
1371+
const message_hash = keccak256(buffer);
1372+
const signature = keyPair.sign(message_hash, "hex");
1373+
const { r, s, recoveryParam } = signature;
1374+
const signatureBuffer = Buffer.concat([
1375+
r.toArrayLike(Buffer, "be", 32),
1376+
s.toArrayLike(Buffer, "be", 32),
1377+
]);
1378+
1379+
try {
1380+
// call the `execute_spl_token` function in the gateway program
1381+
await gatewayProgram.methods
1382+
.executeSplToken(
1383+
usdcDecimals,
1384+
amount,
1385+
Array.from(address),
1386+
data,
1387+
Array.from(signatureBuffer),
1388+
Number(recoveryParam),
1389+
Array.from(message_hash),
1390+
nonce
1391+
)
1392+
.accountsPartial({
1393+
// mandatory predefined accounts
1394+
signer: wallet.publicKey,
1395+
pda: pdaAccount,
1396+
pdaAta: pda_ata,
1397+
mintAccount: mint.publicKey,
1398+
destinationProgram: connectedSPLProgram.programId,
1399+
destinationProgramPda: connectedPdaAccount,
1400+
destinationProgramPdaAta: destinationPdaAta.address,
1401+
tokenProgram: spl.TOKEN_PROGRAM_ID,
1402+
associatedTokenProgram: spl.ASSOCIATED_TOKEN_PROGRAM_ID,
1403+
systemProgram: SYSTEM_PROGRAM_ID,
1404+
})
1405+
.remainingAccounts([
1406+
// accounts coming from withdraw and call msg
1407+
{ pubkey: wallet.publicKey, isSigner: true, isWritable: true },
1408+
{ pubkey: connectedPdaAccount, isSigner: false, isWritable: true },
1409+
{
1410+
pubkey: destinationPdaAta.address,
1411+
isSigner: false,
1412+
isWritable: true,
1413+
},
1414+
{ pubkey: mint.publicKey, isSigner: false, isWritable: false },
1415+
{ pubkey: pdaAccount, isSigner: false, isWritable: false },
1416+
{
1417+
pubkey: randomWallet.publicKey,
1418+
isSigner: false,
1419+
isWritable: false,
1420+
},
1421+
{
1422+
pubkey: randomWalletAta.address,
1423+
isSigner: false,
1424+
isWritable: true,
1425+
},
1426+
{
1427+
pubkey: spl.TOKEN_PROGRAM_ID,
1428+
isSigner: false,
1429+
isWritable: false,
1430+
},
1431+
{
1432+
pubkey: SYSTEM_PROGRAM_ID,
1433+
isSigner: false,
1434+
isWritable: false,
1435+
},
1436+
])
1437+
.rpc();
1438+
throw new Error("Expected error not thrown"); // This line will make the test fail if no error is thrown
1439+
} catch (err) {
1440+
expect(err).to.be.instanceof(anchor.AnchorError);
1441+
expect(err.message).to.include("InvalidInstructionData");
1442+
}
1443+
});
1444+
12541445
it("Calls execute spl token and onCall reverts if wrong msg hash", async () => {
12551446
const randomWallet = anchor.web3.Keypair.generate();
12561447
let randomWalletAta = await spl.getOrCreateAssociatedTokenAccount(

0 commit comments

Comments
 (0)