471 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			471 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // @flow
 | |
| 
 | |
| import {
 | |
|   Account,
 | |
|   Authorized,
 | |
|   Connection,
 | |
|   Lockup,
 | |
|   PublicKey,
 | |
|   sendAndConfirmTransaction,
 | |
|   LAMPORTS_PER_SOL,
 | |
|   StakeAuthorizationLayout,
 | |
|   StakeInstruction,
 | |
|   StakeProgram,
 | |
|   SystemInstruction,
 | |
|   Transaction,
 | |
| } from '../src';
 | |
| import {mockRpcEnabled} from './__mocks__/node-fetch';
 | |
| import {url} from './url';
 | |
| 
 | |
| if (!mockRpcEnabled) {
 | |
|   // Testing max commitment level takes around 20s to complete
 | |
|   jest.setTimeout(30000);
 | |
| }
 | |
| 
 | |
| test('createAccountWithSeed', async () => {
 | |
|   const fromPubkey = new Account().publicKey;
 | |
|   const seed = 'test string';
 | |
|   const newAccountPubkey = await PublicKey.createWithSeed(
 | |
|     fromPubkey,
 | |
|     seed,
 | |
|     StakeProgram.programId,
 | |
|   );
 | |
|   const authorizedPubkey = new Account().publicKey;
 | |
|   const authorized = new Authorized(authorizedPubkey, authorizedPubkey);
 | |
|   const lockup = new Lockup(0, 0, fromPubkey);
 | |
|   const lamports = 123;
 | |
|   const transaction = StakeProgram.createAccountWithSeed({
 | |
|     fromPubkey,
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     basePubkey: fromPubkey,
 | |
|     seed,
 | |
|     authorized,
 | |
|     lockup,
 | |
|     lamports,
 | |
|   });
 | |
|   expect(transaction.instructions).toHaveLength(2);
 | |
|   const [systemInstruction, stakeInstruction] = transaction.instructions;
 | |
|   const systemParams = {
 | |
|     fromPubkey,
 | |
|     newAccountPubkey,
 | |
|     basePubkey: fromPubkey,
 | |
|     seed,
 | |
|     lamports,
 | |
|     space: StakeProgram.space,
 | |
|     programId: StakeProgram.programId,
 | |
|   };
 | |
|   expect(systemParams).toEqual(
 | |
|     SystemInstruction.decodeCreateWithSeed(systemInstruction),
 | |
|   );
 | |
|   const initParams = {stakePubkey: newAccountPubkey, authorized, lockup};
 | |
|   expect(initParams).toEqual(
 | |
|     StakeInstruction.decodeInitialize(stakeInstruction),
 | |
|   );
 | |
| });
 | |
| 
 | |
| test('createAccount', () => {
 | |
|   const fromPubkey = new Account().publicKey;
 | |
|   const newAccountPubkey = new Account().publicKey;
 | |
|   const authorizedPubkey = new Account().publicKey;
 | |
|   const authorized = new Authorized(authorizedPubkey, authorizedPubkey);
 | |
|   const lockup = new Lockup(0, 0, fromPubkey);
 | |
|   const lamports = 123;
 | |
|   const transaction = StakeProgram.createAccount({
 | |
|     fromPubkey,
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorized,
 | |
|     lockup,
 | |
|     lamports,
 | |
|   });
 | |
|   expect(transaction.instructions).toHaveLength(2);
 | |
|   const [systemInstruction, stakeInstruction] = transaction.instructions;
 | |
|   const systemParams = {
 | |
|     fromPubkey,
 | |
|     newAccountPubkey,
 | |
|     lamports,
 | |
|     space: StakeProgram.space,
 | |
|     programId: StakeProgram.programId,
 | |
|   };
 | |
|   expect(systemParams).toEqual(
 | |
|     SystemInstruction.decodeCreateAccount(systemInstruction),
 | |
|   );
 | |
| 
 | |
|   const initParams = {stakePubkey: newAccountPubkey, authorized, lockup};
 | |
|   expect(initParams).toEqual(
 | |
|     StakeInstruction.decodeInitialize(stakeInstruction),
 | |
|   );
 | |
| });
 | |
| 
 | |
| test('delegate', () => {
 | |
|   const stakePubkey = new Account().publicKey;
 | |
|   const authorizedPubkey = new Account().publicKey;
 | |
|   const votePubkey = new Account().publicKey;
 | |
|   const params = {
 | |
|     stakePubkey,
 | |
|     authorizedPubkey,
 | |
|     votePubkey,
 | |
|   };
 | |
|   const transaction = StakeProgram.delegate(params);
 | |
|   expect(transaction.instructions).toHaveLength(1);
 | |
|   const [stakeInstruction] = transaction.instructions;
 | |
|   expect(params).toEqual(StakeInstruction.decodeDelegate(stakeInstruction));
 | |
| });
 | |
| 
 | |
| test('authorize', () => {
 | |
|   const stakePubkey = new Account().publicKey;
 | |
|   const authorizedPubkey = new Account().publicKey;
 | |
|   const newAuthorizedPubkey = new Account().publicKey;
 | |
|   const stakeAuthorizationType = StakeAuthorizationLayout.Staker;
 | |
|   const params = {
 | |
|     stakePubkey,
 | |
|     authorizedPubkey,
 | |
|     newAuthorizedPubkey,
 | |
|     stakeAuthorizationType,
 | |
|   };
 | |
|   const transaction = StakeProgram.authorize(params);
 | |
|   expect(transaction.instructions).toHaveLength(1);
 | |
|   const [stakeInstruction] = transaction.instructions;
 | |
|   expect(params).toEqual(StakeInstruction.decodeAuthorize(stakeInstruction));
 | |
| });
 | |
| 
 | |
| test('authorizeWithSeed', () => {
 | |
|   const stakePubkey = new Account().publicKey;
 | |
|   const authorityBase = new Account().publicKey;
 | |
|   const authoritySeed = 'test string';
 | |
|   const authorityOwner = new Account().publicKey;
 | |
|   const newAuthorizedPubkey = new Account().publicKey;
 | |
|   const stakeAuthorizationType = StakeAuthorizationLayout.Staker;
 | |
|   const params = {
 | |
|     stakePubkey,
 | |
|     authorityBase,
 | |
|     authoritySeed,
 | |
|     authorityOwner,
 | |
|     newAuthorizedPubkey,
 | |
|     stakeAuthorizationType,
 | |
|   };
 | |
|   const transaction = StakeProgram.authorizeWithSeed(params);
 | |
|   expect(transaction.instructions).toHaveLength(1);
 | |
|   const [stakeInstruction] = transaction.instructions;
 | |
|   expect(params).toEqual(
 | |
|     StakeInstruction.decodeAuthorizeWithSeed(stakeInstruction),
 | |
|   );
 | |
| });
 | |
| 
 | |
| test('split', () => {
 | |
|   const stakePubkey = new Account().publicKey;
 | |
|   const authorizedPubkey = new Account().publicKey;
 | |
|   const splitStakePubkey = new Account().publicKey;
 | |
|   const params = {
 | |
|     stakePubkey,
 | |
|     authorizedPubkey,
 | |
|     splitStakePubkey,
 | |
|     lamports: 123,
 | |
|   };
 | |
|   const transaction = StakeProgram.split(params);
 | |
|   expect(transaction.instructions).toHaveLength(2);
 | |
|   const [systemInstruction, stakeInstruction] = transaction.instructions;
 | |
|   const systemParams = {
 | |
|     fromPubkey: authorizedPubkey,
 | |
|     newAccountPubkey: splitStakePubkey,
 | |
|     lamports: 0,
 | |
|     space: StakeProgram.space,
 | |
|     programId: StakeProgram.programId,
 | |
|   };
 | |
|   expect(systemParams).toEqual(
 | |
|     SystemInstruction.decodeCreateAccount(systemInstruction),
 | |
|   );
 | |
|   expect(params).toEqual(StakeInstruction.decodeSplit(stakeInstruction));
 | |
| });
 | |
| 
 | |
| test('withdraw', () => {
 | |
|   const stakePubkey = new Account().publicKey;
 | |
|   const authorizedPubkey = new Account().publicKey;
 | |
|   const toPubkey = new Account().publicKey;
 | |
|   const params = {
 | |
|     stakePubkey,
 | |
|     authorizedPubkey,
 | |
|     toPubkey,
 | |
|     lamports: 123,
 | |
|   };
 | |
|   const transaction = StakeProgram.withdraw(params);
 | |
|   expect(transaction.instructions).toHaveLength(1);
 | |
|   const [stakeInstruction] = transaction.instructions;
 | |
|   expect(params).toEqual(StakeInstruction.decodeWithdraw(stakeInstruction));
 | |
| });
 | |
| 
 | |
| test('deactivate', () => {
 | |
|   const stakePubkey = new Account().publicKey;
 | |
|   const authorizedPubkey = new Account().publicKey;
 | |
|   const params = {stakePubkey, authorizedPubkey};
 | |
|   const transaction = StakeProgram.deactivate(params);
 | |
|   expect(transaction.instructions).toHaveLength(1);
 | |
|   const [stakeInstruction] = transaction.instructions;
 | |
|   expect(params).toEqual(StakeInstruction.decodeDeactivate(stakeInstruction));
 | |
| });
 | |
| 
 | |
| test('StakeInstructions', async () => {
 | |
|   const from = new Account();
 | |
|   const seed = 'test string';
 | |
|   const newAccountPubkey = await PublicKey.createWithSeed(
 | |
|     from.publicKey,
 | |
|     seed,
 | |
|     StakeProgram.programId,
 | |
|   );
 | |
|   const authorized = new Account();
 | |
|   const amount = 123;
 | |
|   const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash
 | |
|   const createWithSeed = StakeProgram.createAccountWithSeed({
 | |
|     fromPubkey: from.publicKey,
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     basePubkey: from.publicKey,
 | |
|     seed,
 | |
|     authorized: new Authorized(authorized.publicKey, authorized.publicKey),
 | |
|     lockup: new Lockup(0, 0, from.publicKey),
 | |
|     lamports: amount,
 | |
|   });
 | |
|   const createWithSeedTransaction = new Transaction({recentBlockhash}).add(
 | |
|     createWithSeed,
 | |
|   );
 | |
| 
 | |
|   expect(createWithSeedTransaction.instructions).toHaveLength(2);
 | |
|   const systemInstructionType = SystemInstruction.decodeInstructionType(
 | |
|     createWithSeedTransaction.instructions[0],
 | |
|   );
 | |
|   expect(systemInstructionType).toEqual('CreateWithSeed');
 | |
| 
 | |
|   const stakeInstructionType = StakeInstruction.decodeInstructionType(
 | |
|     createWithSeedTransaction.instructions[1],
 | |
|   );
 | |
|   expect(stakeInstructionType).toEqual('Initialize');
 | |
| 
 | |
|   expect(() => {
 | |
|     StakeInstruction.decodeInstructionType(
 | |
|       createWithSeedTransaction.instructions[0],
 | |
|     );
 | |
|   }).toThrow();
 | |
| 
 | |
|   const stake = new Account();
 | |
|   const vote = new Account();
 | |
|   const delegate = StakeProgram.delegate({
 | |
|     stakePubkey: stake.publicKey,
 | |
|     authorizedPubkey: authorized.publicKey,
 | |
|     votePubkey: vote.publicKey,
 | |
|   });
 | |
| 
 | |
|   const delegateTransaction = new Transaction({recentBlockhash}).add(delegate);
 | |
|   const anotherStakeInstructionType = StakeInstruction.decodeInstructionType(
 | |
|     delegateTransaction.instructions[0],
 | |
|   );
 | |
|   expect(anotherStakeInstructionType).toEqual('Delegate');
 | |
| });
 | |
| 
 | |
| test('live staking actions', async () => {
 | |
|   if (mockRpcEnabled) {
 | |
|     console.log('non-live test skipped');
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   const connection = new Connection(url, 'recent');
 | |
|   const voteAccounts = await connection.getVoteAccounts();
 | |
|   const voteAccount = voteAccounts.current.concat(voteAccounts.delinquent)[0];
 | |
|   const votePubkey = new PublicKey(voteAccount.votePubkey);
 | |
| 
 | |
|   const from = new Account();
 | |
|   const authorized = new Account();
 | |
|   await connection.requestAirdrop(from.publicKey, 2 * LAMPORTS_PER_SOL);
 | |
|   await connection.requestAirdrop(authorized.publicKey, 2 * LAMPORTS_PER_SOL);
 | |
| 
 | |
|   const minimumAmount = await connection.getMinimumBalanceForRentExemption(
 | |
|     StakeProgram.space,
 | |
|     'recent',
 | |
|   );
 | |
| 
 | |
|   {
 | |
|     // Create Stake account without seed
 | |
|     const newStakeAccount = new Account();
 | |
|     let createAndInitialize = StakeProgram.createAccount({
 | |
|       fromPubkey: from.publicKey,
 | |
|       stakePubkey: newStakeAccount.publicKey,
 | |
|       authorized: new Authorized(authorized.publicKey, authorized.publicKey),
 | |
|       lockup: new Lockup(0, 0, new PublicKey(0)),
 | |
|       lamports: minimumAmount + 42,
 | |
|     });
 | |
| 
 | |
|     await sendAndConfirmTransaction(
 | |
|       connection,
 | |
|       createAndInitialize,
 | |
|       [from, newStakeAccount],
 | |
|       {commitment: 'single', skipPreflight: true},
 | |
|     );
 | |
|     expect(await connection.getBalance(newStakeAccount.publicKey)).toEqual(
 | |
|       minimumAmount + 42,
 | |
|     );
 | |
| 
 | |
|     let delegation = StakeProgram.delegate({
 | |
|       stakePubkey: newStakeAccount.publicKey,
 | |
|       authorizedPubkey: authorized.publicKey,
 | |
|       votePubkey,
 | |
|     });
 | |
|     await sendAndConfirmTransaction(connection, delegation, [authorized], {
 | |
|       commitment: 'single',
 | |
|       skipPreflight: true,
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   // Create Stake account with seed
 | |
|   const seed = 'test string';
 | |
|   const newAccountPubkey = await PublicKey.createWithSeed(
 | |
|     from.publicKey,
 | |
|     seed,
 | |
|     StakeProgram.programId,
 | |
|   );
 | |
| 
 | |
|   let createAndInitializeWithSeed = StakeProgram.createAccountWithSeed({
 | |
|     fromPubkey: from.publicKey,
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     basePubkey: from.publicKey,
 | |
|     seed,
 | |
|     authorized: new Authorized(authorized.publicKey, authorized.publicKey),
 | |
|     lockup: new Lockup(0, 0, new PublicKey(0)),
 | |
|     lamports: 3 * minimumAmount + 42,
 | |
|   });
 | |
| 
 | |
|   await sendAndConfirmTransaction(
 | |
|     connection,
 | |
|     createAndInitializeWithSeed,
 | |
|     [from],
 | |
|     {commitment: 'single', skipPreflight: true},
 | |
|   );
 | |
|   let originalStakeBalance = await connection.getBalance(newAccountPubkey);
 | |
|   expect(originalStakeBalance).toEqual(3 * minimumAmount + 42);
 | |
| 
 | |
|   let delegation = StakeProgram.delegate({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorizedPubkey: authorized.publicKey,
 | |
|     votePubkey,
 | |
|   });
 | |
|   await sendAndConfirmTransaction(connection, delegation, [authorized], {
 | |
|     commitment: 'single',
 | |
|     skipPreflight: true,
 | |
|   });
 | |
| 
 | |
|   // Test that withdraw fails before deactivation
 | |
|   const recipient = new Account();
 | |
|   let withdraw = StakeProgram.withdraw({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorizedPubkey: authorized.publicKey,
 | |
|     toPubkey: recipient.publicKey,
 | |
|     lamports: 1000,
 | |
|   });
 | |
|   await expect(
 | |
|     sendAndConfirmTransaction(connection, withdraw, [authorized], {
 | |
|       commitment: 'single',
 | |
|       skipPreflight: true,
 | |
|     }),
 | |
|   ).rejects.toThrow();
 | |
| 
 | |
|   // Split stake
 | |
|   const newStake = new Account();
 | |
|   let split = StakeProgram.split({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorizedPubkey: authorized.publicKey,
 | |
|     splitStakePubkey: newStake.publicKey,
 | |
|     lamports: minimumAmount + 20,
 | |
|   });
 | |
|   await sendAndConfirmTransaction(connection, split, [authorized, newStake], {
 | |
|     commitment: 'single',
 | |
|     skipPreflight: true,
 | |
|   });
 | |
| 
 | |
|   // Authorize to new account
 | |
|   const newAuthorized = new Account();
 | |
|   await connection.requestAirdrop(newAuthorized.publicKey, LAMPORTS_PER_SOL);
 | |
| 
 | |
|   let authorize = StakeProgram.authorize({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorizedPubkey: authorized.publicKey,
 | |
|     newAuthorizedPubkey: newAuthorized.publicKey,
 | |
|     stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
 | |
|   });
 | |
|   await sendAndConfirmTransaction(connection, authorize, [authorized], {
 | |
|     commitment: 'single',
 | |
|     skipPreflight: true,
 | |
|   });
 | |
|   authorize = StakeProgram.authorize({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorizedPubkey: authorized.publicKey,
 | |
|     newAuthorizedPubkey: newAuthorized.publicKey,
 | |
|     stakeAuthorizationType: StakeAuthorizationLayout.Staker,
 | |
|   });
 | |
|   await sendAndConfirmTransaction(connection, authorize, [authorized], {
 | |
|     commitment: 'single',
 | |
|     skipPreflight: true,
 | |
|   });
 | |
| 
 | |
|   // Test old authorized can't deactivate
 | |
|   let deactivateNotAuthorized = StakeProgram.deactivate({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorizedPubkey: authorized.publicKey,
 | |
|   });
 | |
|   await expect(
 | |
|     sendAndConfirmTransaction(
 | |
|       connection,
 | |
|       deactivateNotAuthorized,
 | |
|       [authorized],
 | |
|       {commitment: 'single', skipPreflight: true},
 | |
|     ),
 | |
|   ).rejects.toThrow();
 | |
| 
 | |
|   // Deactivate stake
 | |
|   let deactivate = StakeProgram.deactivate({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorizedPubkey: newAuthorized.publicKey,
 | |
|   });
 | |
|   await sendAndConfirmTransaction(connection, deactivate, [newAuthorized], {
 | |
|     commitment: 'single',
 | |
|     skipPreflight: true,
 | |
|   });
 | |
| 
 | |
|   // Test that withdraw succeeds after deactivation
 | |
|   withdraw = StakeProgram.withdraw({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorizedPubkey: newAuthorized.publicKey,
 | |
|     toPubkey: recipient.publicKey,
 | |
|     lamports: minimumAmount + 20,
 | |
|   });
 | |
|   await sendAndConfirmTransaction(connection, withdraw, [newAuthorized], {
 | |
|     commitment: 'single',
 | |
|     skipPreflight: true,
 | |
|   });
 | |
|   const balance = await connection.getBalance(newAccountPubkey);
 | |
|   expect(balance).toEqual(minimumAmount + 2);
 | |
|   const recipientBalance = await connection.getBalance(recipient.publicKey);
 | |
|   expect(recipientBalance).toEqual(minimumAmount + 20);
 | |
| 
 | |
|   // Authorize a derived address
 | |
|   authorize = StakeProgram.authorize({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorizedPubkey: newAuthorized.publicKey,
 | |
|     newAuthorizedPubkey: newAccountPubkey,
 | |
|     stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
 | |
|   });
 | |
|   await sendAndConfirmTransaction(connection, authorize, [newAuthorized], {
 | |
|     commitment: 'single',
 | |
|     skipPreflight: true,
 | |
|   });
 | |
| 
 | |
|   // Restore the previous authority using a dervied address
 | |
|   authorize = StakeProgram.authorizeWithSeed({
 | |
|     stakePubkey: newAccountPubkey,
 | |
|     authorityBase: from.publicKey,
 | |
|     authoritySeed: seed,
 | |
|     authorityOwner: StakeProgram.programId,
 | |
|     newAuthorizedPubkey: newAuthorized.publicKey,
 | |
|     stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
 | |
|   });
 | |
|   await sendAndConfirmTransaction(connection, authorize, [from], {
 | |
|     commitment: 'single',
 | |
|     skipPreflight: true,
 | |
|   });
 | |
| });
 |