Skip to content

Commit 9ca6ad6

Browse files
fix: improve token2022 support (#188)
Co-authored-by: BretasArthur1 <[email protected]>
1 parent 9ceecfb commit 9ca6ad6

File tree

10 files changed

+351
-53
lines changed

10 files changed

+351
-53
lines changed

.cursor/mcp.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace.package]
2-
version = "0.8.0"
2+
version = "0.8.1"
33
edition = "2021"
44
description = "Surfpool is the best place to train before surfing Solana."
55
license = "Apache-2.0"

crates/core/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ solana-version = { workspace = true }
7070
spl-associated-token-account = { workspace = true }
7171
surfpool-subgraph = { workspace = true }
7272
surfpool-types = { workspace = true }
73-
# surfpool-types = { version = "0.2.0", default-features = false }
74-
# surfpool-subgraph = { version = "0.2.0", default-features = false }
73+
# surfpool-types = { version = "0.8.1", default-features = false }
74+
# surfpool-subgraph = { version = "0.8.1", default-features = false }
7575
symlink = "0.1.0"
7676
tokio = { version = "1.45.0", features = ["full"] }
7777
tokio-util = "0.7.13"

crates/core/src/rpc/accounts_data.rs

Lines changed: 241 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use super::{not_implemented_err, RunloopContext, SurfnetRpcContext};
2020
use crate::{
2121
error::{SurfpoolError, SurfpoolResult},
2222
rpc::{utils::verify_pubkey, State},
23-
surfnet::locker::SvmAccessContext,
23+
surfnet::{locker::SvmAccessContext, GetAccountResult},
2424
};
2525

2626
#[rpc]
@@ -377,12 +377,20 @@ impl AccountsData for SurfpoolAccountsDataRpc {
377377
inner: account_update,
378378
..
379379
} = svm_locker.get_account(&remote_ctx, &pubkey, None).await?;
380-
381380
svm_locker.write_account_update(account_update.clone());
382381

382+
let SvmAccessContext {
383+
inner: associated_data,
384+
..
385+
} = svm_locker.get_local_account_associated_data(&account_update);
386+
383387
Ok(RpcResponse {
384388
context: RpcResponseContext::new(slot),
385-
value: account_update.try_into_ui_account(config.encoding, config.data_slice),
389+
value: account_update.try_into_ui_account(
390+
config.encoding,
391+
config.data_slice,
392+
associated_data,
393+
),
386394
})
387395
})
388396
}
@@ -425,9 +433,11 @@ impl AccountsData for SurfpoolAccountsDataRpc {
425433
let mut ui_accounts = vec![];
426434
{
427435
for account_update in account_updates.into_iter() {
428-
ui_accounts.push(
429-
account_update.try_into_ui_account(config.encoding, config.data_slice),
430-
);
436+
ui_accounts.push(account_update.try_into_ui_account(
437+
config.encoding,
438+
config.data_slice,
439+
None,
440+
));
431441
}
432442
}
433443

@@ -648,13 +658,30 @@ impl AccountsData for SurfpoolAccountsDataRpc {
648658
#[cfg(test)]
649659
mod tests {
650660
use solana_account::Account;
661+
use solana_client::rpc_client::RpcClient;
662+
use solana_keypair::Keypair;
651663
use solana_pubkey::Pubkey;
652-
use solana_sdk::{program_option::COption, program_pack::Pack};
664+
use solana_sdk::{
665+
program_option::COption, program_pack::Pack, system_instruction::create_account,
666+
};
667+
use solana_signer::Signer;
668+
use solana_transaction::Transaction;
669+
use spl_associated_token_account::{
670+
get_associated_token_address_with_program_id, instruction::create_associated_token_account,
671+
};
653672
use spl_token::state::{Account as TokenAccount, AccountState, Mint};
673+
use spl_token_2022::{
674+
extension::StateWithExtensions,
675+
instruction::{initialize_mint2, mint_to, transfer_checked},
676+
state::Account as Token2022Account,
677+
};
654678

655679
use super::*;
656680
use crate::{
657-
surfnet::{remote::SurfnetRemoteClient, GetAccountResult},
681+
surfnet::{
682+
remote::{SomeRemoteCtx, SurfnetRemoteClient},
683+
GetAccountResult,
684+
},
658685
tests::helpers::TestSetup,
659686
};
660687

@@ -1019,4 +1046,210 @@ mod tests {
10191046
let error_msg = res.unwrap_err().to_string();
10201047
println!("✅ Remote RPC failure handled: {}", error_msg);
10211048
}
1049+
1050+
#[tokio::test(flavor = "multi_thread")]
1051+
async fn test_transfer_token() {
1052+
// Create connection to local validator
1053+
let client = TestSetup::new(SurfpoolAccountsDataRpc);
1054+
let recent_blockhash = client
1055+
.context
1056+
.svm_locker
1057+
.with_svm_reader(|svm_reader| svm_reader.latest_blockhash());
1058+
1059+
// Generate a new keypair for the fee payer
1060+
let fee_payer = Keypair::new();
1061+
1062+
// Generate a second keypair for the token recipient
1063+
let recipient = Keypair::new();
1064+
1065+
// Airdrop 1 SOL to fee payer
1066+
client
1067+
.context
1068+
.svm_locker
1069+
.airdrop(&fee_payer.pubkey(), 1_000_000_000)
1070+
.unwrap();
1071+
1072+
// Airdrop 1 SOL to recipient for rent exemption
1073+
client
1074+
.context
1075+
.svm_locker
1076+
.airdrop(&recipient.pubkey(), 1_000_000_000)
1077+
.unwrap();
1078+
1079+
// Generate keypair to use as address of mint
1080+
let mint = Keypair::new();
1081+
1082+
// Get default mint account size (in bytes), no extensions enabled
1083+
let mint_space = Mint::LEN;
1084+
let mint_rent = client.context.svm_locker.with_svm_reader(|svm_reader| {
1085+
svm_reader
1086+
.inner
1087+
.minimum_balance_for_rent_exemption(mint_space)
1088+
});
1089+
1090+
// Instruction to create new account for mint (token 2022 program)
1091+
let create_account_instruction = create_account(
1092+
&fee_payer.pubkey(), // payer
1093+
&mint.pubkey(), // new account (mint)
1094+
mint_rent, // lamports
1095+
mint_space as u64, // space
1096+
&spl_token_2022::id(), // program id
1097+
);
1098+
1099+
// Instruction to initialize mint account data
1100+
let initialize_mint_instruction = initialize_mint2(
1101+
&spl_token_2022::id(),
1102+
&mint.pubkey(), // mint
1103+
&fee_payer.pubkey(), // mint authority
1104+
Some(&fee_payer.pubkey()), // freeze authority
1105+
2, // decimals
1106+
)
1107+
.unwrap();
1108+
1109+
// Calculate the associated token account address for fee_payer
1110+
let source_token_address = get_associated_token_address_with_program_id(
1111+
&fee_payer.pubkey(), // owner
1112+
&mint.pubkey(), // mint
1113+
&spl_token_2022::id(), // program_id
1114+
);
1115+
1116+
// Instruction to create associated token account for fee_payer
1117+
let create_source_ata_instruction = create_associated_token_account(
1118+
&fee_payer.pubkey(), // funding address
1119+
&fee_payer.pubkey(), // wallet address
1120+
&mint.pubkey(), // mint address
1121+
&spl_token_2022::id(), // program id
1122+
);
1123+
1124+
// Calculate the associated token account address for recipient
1125+
let destination_token_address = get_associated_token_address_with_program_id(
1126+
&recipient.pubkey(), // owner
1127+
&mint.pubkey(), // mint
1128+
&spl_token_2022::id(), // program_id
1129+
);
1130+
1131+
// Instruction to create associated token account for recipient
1132+
let create_destination_ata_instruction = create_associated_token_account(
1133+
&fee_payer.pubkey(), // funding address
1134+
&recipient.pubkey(), // wallet address
1135+
&mint.pubkey(), // mint address
1136+
&spl_token_2022::id(), // program id
1137+
);
1138+
1139+
// Amount of tokens to mint (100 tokens with 2 decimal places)
1140+
let amount = 100_00;
1141+
1142+
// Create mint_to instruction to mint tokens to the source token account
1143+
let mint_to_instruction = mint_to(
1144+
&spl_token_2022::id(),
1145+
&mint.pubkey(), // mint
1146+
&source_token_address, // destination
1147+
&fee_payer.pubkey(), // authority
1148+
&[&fee_payer.pubkey()], // signer
1149+
amount, // amount
1150+
)
1151+
.unwrap();
1152+
1153+
// Create transaction and add instructions
1154+
let transaction = Transaction::new_signed_with_payer(
1155+
&[
1156+
create_account_instruction,
1157+
initialize_mint_instruction,
1158+
create_source_ata_instruction,
1159+
create_destination_ata_instruction,
1160+
mint_to_instruction,
1161+
],
1162+
Some(&fee_payer.pubkey()),
1163+
&[&fee_payer, &mint],
1164+
recent_blockhash,
1165+
);
1166+
1167+
// Send and confirm transaction
1168+
client.context.svm_locker.with_svm_writer(|svm_writer| {
1169+
svm_writer
1170+
.send_transaction(transaction.clone().into(), false)
1171+
.unwrap();
1172+
});
1173+
1174+
println!("Mint Address: {}", mint.pubkey());
1175+
println!("Recipient Address: {}", recipient.pubkey());
1176+
println!("Source Token Account Address: {}", source_token_address);
1177+
println!(
1178+
"Destination Token Account Address: {}",
1179+
destination_token_address
1180+
);
1181+
println!("Minted {} tokens to the source token account", amount);
1182+
1183+
// Get the latest blockhash for the transfer transaction
1184+
let recent_blockhash = client
1185+
.context
1186+
.svm_locker
1187+
.with_svm_reader(|svm_reader| svm_reader.latest_blockhash());
1188+
1189+
// Amount of tokens to transfer (0.50 tokens with 2 decimals)
1190+
let transfer_amount = 50;
1191+
1192+
// Create transfer_checked instruction to send tokens from source to destination
1193+
let transfer_instruction = transfer_checked(
1194+
&spl_token_2022::id(), // program id
1195+
&source_token_address, // source
1196+
&mint.pubkey(), // mint
1197+
&destination_token_address, // destination
1198+
&fee_payer.pubkey(), // owner of source
1199+
&[&fee_payer.pubkey()], // signers
1200+
transfer_amount, // amount
1201+
2, // decimals
1202+
)
1203+
.unwrap();
1204+
1205+
// Create transaction for transferring tokens
1206+
let transaction = Transaction::new_signed_with_payer(
1207+
&[transfer_instruction],
1208+
Some(&fee_payer.pubkey()),
1209+
&[&fee_payer],
1210+
recent_blockhash,
1211+
);
1212+
1213+
// Send and confirm transaction
1214+
client.context.svm_locker.with_svm_writer(|svm_writer| {
1215+
svm_writer
1216+
.send_transaction(transaction.clone().into(), false)
1217+
.unwrap();
1218+
});
1219+
1220+
println!("Successfully transferred 0.50 tokens from sender to recipient");
1221+
1222+
// Get token account balances to verify the transfer
1223+
let source_token_account = client
1224+
.context
1225+
.svm_locker
1226+
.get_account_local(&source_token_address)
1227+
.inner;
1228+
let destination_token_account = client
1229+
.context
1230+
.svm_locker
1231+
.get_account_local(&destination_token_address)
1232+
.inner;
1233+
1234+
if let GetAccountResult::FoundAccount(_, source_account, _) = source_token_account {
1235+
let unpacked =
1236+
StateWithExtensions::<Token2022Account>::unpack(&source_account.data).unwrap();
1237+
println!(
1238+
"Source Token Account Balance: {} tokens",
1239+
unpacked.base.amount
1240+
);
1241+
assert_eq!(unpacked.base.amount, 9950);
1242+
}
1243+
1244+
if let GetAccountResult::FoundAccount(_, destination_account, _) = destination_token_account
1245+
{
1246+
let unpacked =
1247+
StateWithExtensions::<Token2022Account>::unpack(&destination_account.data).unwrap();
1248+
println!(
1249+
"Destination Token Account Balance: {} tokens",
1250+
unpacked.base.amount
1251+
);
1252+
assert_eq!(unpacked.base.amount, 50);
1253+
}
1254+
}
10221255
}

0 commit comments

Comments
 (0)