use ed25519_dalek::SigningKey;
use sha2::{Digest, Sha256};
use soroban_env_host::xdr::{
AccountEntry, AccountEntryExt, AccountId, AlphaNum4, AssetCode4, LedgerEntry, LedgerEntryData,
LedgerEntryExt, LedgerKey, LedgerKeyAccount, LedgerKeyTrustLine, PublicKey, SequenceNumber,
Thresholds, TrustLineAsset, TrustLineEntry, TrustLineEntryExt, Uint256,
};
pub const USDC_MAINNET_ISSUER: &str = "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN";
#[derive(Clone, Debug)]
pub struct TestAccount {
pub secret_seed: [u8; 32],
pub public_key: [u8; 32],
pub balance_stroops: i64,
}
impl TestAccount {
pub fn account_strkey(&self) -> String {
stellar_strkey::ed25519::PublicKey(self.public_key)
.to_string()
.as_str()
.to_string()
}
pub fn secret_key_strkey(&self) -> String {
stellar_strkey::ed25519::PrivateKey(self.secret_seed)
.to_string()
.as_str()
.to_string()
}
}
impl TestAccount {
pub(crate) fn ledger_entry(&self, fork_ledger_seq: u32) -> (LedgerKey, LedgerEntry) {
let account_id = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(self.public_key)));
let initial_seq = (fork_ledger_seq as i64) << 32;
let entry = AccountEntry {
account_id: account_id.clone(),
balance: self.balance_stroops,
seq_num: SequenceNumber(initial_seq),
num_sub_entries: 0,
inflation_dest: None,
flags: 0,
home_domain: Default::default(),
thresholds: Thresholds([1, 0, 0, 0]),
signers: Default::default(),
ext: AccountEntryExt::V0,
};
let ledger_entry = LedgerEntry {
last_modified_ledger_seq: fork_ledger_seq,
data: LedgerEntryData::Account(entry),
ext: LedgerEntryExt::V0,
};
let key = LedgerKey::Account(LedgerKeyAccount { account_id });
(key, ledger_entry)
}
pub(crate) fn trustline_entry(
&self,
asset: TrustLineAsset,
fork_ledger_seq: u32,
) -> (LedgerKey, LedgerEntry) {
let account_id = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(self.public_key)));
let entry = TrustLineEntry {
account_id: account_id.clone(),
asset: asset.clone(),
balance: 0,
limit: i64::MAX,
flags: 1,
ext: TrustLineEntryExt::V0,
};
let ledger_entry = LedgerEntry {
last_modified_ledger_seq: fork_ledger_seq,
data: LedgerEntryData::Trustline(entry),
ext: LedgerEntryExt::V0,
};
let key = LedgerKey::Trustline(LedgerKeyTrustLine { account_id, asset });
(key, ledger_entry)
}
}
pub(crate) fn usdc_mainnet_trustline_asset() -> TrustLineAsset {
let issuer_strkey: stellar_strkey::ed25519::PublicKey = USDC_MAINNET_ISSUER
.parse()
.expect("USDC_MAINNET_ISSUER is a valid strkey");
let issuer = AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(issuer_strkey.0)));
let mut code = [0u8; 4];
code.copy_from_slice(b"USDC");
TrustLineAsset::CreditAlphanum4(AlphaNum4 {
asset_code: AssetCode4(code),
issuer,
})
}
pub fn generate(n: usize) -> Vec<TestAccount> {
const DEFAULT_BALANCE_STROOPS: i64 = 100_000 * 10_000_000;
(0..n)
.map(|i| {
let seed_input = format!("soroban-fork test account {i}");
let seed: [u8; 32] = Sha256::digest(seed_input.as_bytes()).into();
let signing = SigningKey::from_bytes(&seed);
let public_key = signing.verifying_key().to_bytes();
TestAccount {
secret_seed: seed,
public_key,
balance_stroops: DEFAULT_BALANCE_STROOPS,
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generated_accounts_are_deterministic() {
let a = generate(3);
let b = generate(3);
assert_eq!(a.len(), 3);
for (x, y) in a.iter().zip(b.iter()) {
assert_eq!(x.secret_seed, y.secret_seed);
assert_eq!(x.public_key, y.public_key);
}
}
#[test]
fn strkeys_have_expected_prefixes() {
let a = &generate(1)[0];
assert!(a.account_strkey().starts_with('G'));
assert!(a.secret_key_strkey().starts_with('S'));
assert_eq!(a.account_strkey().len(), 56);
assert_eq!(a.secret_key_strkey().len(), 56);
}
#[test]
fn account_zero_is_stable() {
let a = &generate(1)[0];
let strkey = a.account_strkey();
assert_eq!(strkey.len(), 56);
assert!(strkey.starts_with('G'));
}
}