|
@@ -0,0 +1,146 @@
|
|
|
|
+import { start } from 'solana-bankrun';
|
|
|
|
+import { expect } from 'chai';
|
|
|
|
+import { PublicKey, SystemProgram, Keypair, Connection } from '@solana/web3.js';
|
|
|
|
+import { TOKEN_PROGRAM_ID, createMint, getOrCreateAssociatedTokenAccount, mintTo } from '@solana/spl-token';
|
|
|
|
+
|
|
|
|
+jest.setTimeout(30000); // Set timeout to 30 seconds
|
|
|
|
+
|
|
|
|
+const ACCOUNT_SIZE = 8 + 32 + 20; // Define your account size here
|
|
|
|
+
|
|
|
|
+async function retryWithBackoff(fn: () => Promise<any>, retries = 5, delay = 500): Promise<any> {
|
|
|
|
+ try {
|
|
|
|
+ return await fn();
|
|
|
|
+ } catch (err) {
|
|
|
|
+ if (retries === 0) throw err;
|
|
|
|
+ await new Promise(resolve => setTimeout(resolve, delay));
|
|
|
|
+ return retryWithBackoff(fn, retries - 1, delay * 2);
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+describe('External Delegate Token Master Tests', () => {
|
|
|
|
+ let context: any;
|
|
|
|
+ let program: any;
|
|
|
|
+ let authority: Keypair;
|
|
|
|
+ let userAccount: Keypair;
|
|
|
|
+ let mint: PublicKey;
|
|
|
|
+ let userTokenAccount: PublicKey;
|
|
|
|
+ let recipientTokenAccount: PublicKey;
|
|
|
|
+ let userPda: PublicKey;
|
|
|
|
+ let bumpSeed: number;
|
|
|
|
+
|
|
|
|
+ beforeEach(async () => {
|
|
|
|
+ authority = Keypair.generate();
|
|
|
|
+ userAccount = Keypair.generate();
|
|
|
|
+
|
|
|
|
+ const programs = [
|
|
|
|
+ {
|
|
|
|
+ name: "external_delegate_token_master",
|
|
|
|
+ programId: new PublicKey("FYPkt5VWMvtyWZDMGCwoKFkE3wXTzphicTpnNGuHWVbD"),
|
|
|
|
+ program: "target/deploy/external_delegate_token_master.so",
|
|
|
|
+ },
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ context = await retryWithBackoff(async () => await start(programs, []));
|
|
|
|
+
|
|
|
|
+ const connection = new Connection("https://api.devnet.solana.com", "confirmed");
|
|
|
|
+ context.connection = connection;
|
|
|
|
+
|
|
|
|
+ // Airdrop SOL to authority with retry logic
|
|
|
|
+ await retryWithBackoff(async () => {
|
|
|
|
+ await connection.requestAirdrop(authority.publicKey, 1000000000);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // Create mint with retry logic
|
|
|
|
+ mint = await retryWithBackoff(async () =>
|
|
|
|
+ await createMint(connection, authority, authority.publicKey, null, 6)
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ const userTokenAccountInfo = await retryWithBackoff(async () =>
|
|
|
|
+ await getOrCreateAssociatedTokenAccount(connection, authority, mint, authority.publicKey)
|
|
|
|
+ );
|
|
|
|
+ userTokenAccount = userTokenAccountInfo.address;
|
|
|
|
+
|
|
|
|
+ const recipientTokenAccountInfo = await retryWithBackoff(async () =>
|
|
|
|
+ await getOrCreateAssociatedTokenAccount(connection, authority, mint, Keypair.generate().publicKey)
|
|
|
|
+ );
|
|
|
|
+ recipientTokenAccount = recipientTokenAccountInfo.address;
|
|
|
|
+
|
|
|
|
+ // Mint tokens to the user's account
|
|
|
|
+ await retryWithBackoff(async () =>
|
|
|
|
+ await mintTo(connection, authority, mint, userTokenAccount, authority, 1000000000)
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // Find program-derived address (PDA)
|
|
|
|
+ [userPda, bumpSeed] = await retryWithBackoff(async () =>
|
|
|
|
+ await PublicKey.findProgramAddress([userAccount.publicKey.toBuffer()], context.program.programId)
|
|
|
|
+ );
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ it('should initialize user account', async () => {
|
|
|
|
+ const space = ACCOUNT_SIZE;
|
|
|
|
+ const rentExempt = await retryWithBackoff(async () => {
|
|
|
|
+ return await context.connection.getMinimumBalanceForRentExemption(space);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ await context.program.methods
|
|
|
|
+ .initialize()
|
|
|
|
+ .accounts({
|
|
|
|
+ userAccount: userAccount.publicKey,
|
|
|
|
+ authority: authority.publicKey,
|
|
|
|
+ systemProgram: SystemProgram.programId,
|
|
|
|
+ })
|
|
|
|
+ .preInstructions([
|
|
|
|
+ SystemProgram.createAccount({
|
|
|
|
+ fromPubkey: authority.publicKey,
|
|
|
|
+ newAccountPubkey: userAccount.publicKey,
|
|
|
|
+ lamports: rentExempt,
|
|
|
|
+ space: space,
|
|
|
|
+ programId: context.program.programId,
|
|
|
|
+ }),
|
|
|
|
+ ])
|
|
|
|
+ .signers([authority, userAccount])
|
|
|
|
+ .rpc();
|
|
|
|
+
|
|
|
|
+ const account = await context.program.account.userAccount.fetch(userAccount.publicKey);
|
|
|
|
+ expect(account.authority.toString()).to.equal(authority.publicKey.toString());
|
|
|
|
+ expect(account.ethereumAddress).to.deep.equal(new Array(20).fill(0));
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ it('should set ethereum address', async () => {
|
|
|
|
+ const ethereumAddress = Buffer.from('1C8cd0c38F8DE35d6056c7C7aBFa7e65D260E816', 'hex');
|
|
|
|
+
|
|
|
|
+ await context.program.methods
|
|
|
|
+ .setEthereumAddress(ethereumAddress)
|
|
|
|
+ .accounts({
|
|
|
|
+ userAccount: userAccount.publicKey,
|
|
|
|
+ authority: authority.publicKey,
|
|
|
|
+ })
|
|
|
|
+ .signers([authority])
|
|
|
|
+ .rpc();
|
|
|
|
+
|
|
|
|
+ const account = await context.program.account.userAccount.fetch(userAccount.publicKey);
|
|
|
|
+ expect(account.ethereumAddress).to.deep.equal(Array.from(ethereumAddress));
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ it('should perform authority transfer', async () => {
|
|
|
|
+ const newAuthority = Keypair.generate();
|
|
|
|
+
|
|
|
|
+ await context.program.methods
|
|
|
|
+ .transferAuthority(newAuthority.publicKey)
|
|
|
|
+ .accounts({
|
|
|
|
+ userAccount: userAccount.publicKey,
|
|
|
|
+ authority: authority.publicKey,
|
|
|
|
+ })
|
|
|
|
+ .signers([authority])
|
|
|
|
+ .rpc();
|
|
|
|
+
|
|
|
|
+ const account = await context.program.account.userAccount.fetch(userAccount.publicKey);
|
|
|
|
+ expect(account.authority.toString()).to.equal(newAuthority.publicKey.toString());
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ afterEach(async () => {
|
|
|
|
+ if (context && typeof context.terminate === 'function') {
|
|
|
|
+ await context.terminate();
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+});
|