1- /* eslint-disable */
2-
31import test from 'ava' ;
4- import { start , BanksClient , ProgramTestContext } from 'solana-bankrun' ;
2+ import path from 'node:path' ;
3+ import { LiteSVM , StakeHistoryEntry , FailedTransactionMetadata } from 'litesvm' ;
54import {
65 Keypair ,
76 PublicKey ,
7+ SystemProgram ,
88 Transaction ,
99 Authorized ,
1010 TransactionInstruction ,
@@ -38,17 +38,46 @@ const voteAccount = {
3838 } ,
3939} ;
4040
41- const SLOTS_PER_EPOCH : bigint = 432000n ;
4241const LAMPORTS_PER_SOL : number = 1_000_000_000 ;
42+ const STAKE_HISTORY_ENTRY = new StakeHistoryEntry (
43+ BigInt ( LAMPORTS_PER_SOL ) * 1000n ,
44+ BigInt ( LAMPORTS_PER_SOL ) * 10n ,
45+ BigInt ( LAMPORTS_PER_SOL ) * 10n ,
46+ ) ;
47+
48+ type LiteContext = {
49+ svm : LiteSVM ;
50+ payer : Keypair ;
51+ banksClient : LiteBanksClient ;
52+ advanceEpoch ( ) : void ;
53+ } ;
54+
55+ class LiteBanksClient {
56+ constructor ( readonly svm : LiteSVM ) { }
57+
58+ async getAccount ( address : PublicKey ) {
59+ const account = this . svm . getAccount ( address ) ;
60+ if ( ! account ) return null ;
61+ return { ...account , data : Buffer . from ( account . data ) } ;
62+ }
63+
64+ async processTransaction ( tx : Transaction ) {
65+ const res = this . svm . sendTransaction ( tx ) ;
66+ if ( res instanceof FailedTransactionMetadata ) {
67+ throw new Error ( `${ res . err ( ) . toString ( ) } \n${ res . meta ( ) . prettyLogs ( ) } ` ) ;
68+ }
69+ return res ;
70+ }
71+ }
4372
4473class BanksConnection {
45- constructor ( client : BanksClient , payer : Keypair ) {
74+ constructor ( client : LiteBanksClient , payer : Keypair ) {
4675 this . client = client ;
4776 this . payer = payer ;
4877 }
4978
5079 async getMinimumBalanceForRentExemption ( dataLen : number ) : Promise < number > {
51- const rent = await this . client . getRent ( ) ;
80+ const rent = await this . client . svm . getRent ( ) ;
5281 return Number ( rent . minimumBalance ( BigInt ( dataLen ) ) ) ;
5382 }
5483
@@ -61,27 +90,27 @@ class BanksConnection {
6190 data : Buffer . from ( [ 13 , 0 , 0 , 0 ] ) ,
6291 } ) ,
6392 ) ;
64- transaction . recentBlockhash = ( await this . client . getLatestBlockhash ( ) ) [ 0 ] ;
93+ transaction . recentBlockhash = this . client . svm . latestBlockhash ( ) ;
6594 transaction . feePayer = this . payer . publicKey ;
6695 transaction . sign ( this . payer ) ;
6796
68- const res = await this . client . simulateTransaction ( transaction ) ;
69- const data = Array . from ( res . inner . meta . returnData . data ) ;
97+ const res = await this . client . svm . simulateTransaction ( transaction ) ;
98+ const data = Array . from ( res . meta ( ) . returnData ( ) . data ( ) ) ;
7099 const minimumDelegation = data [ 0 ] + ( data [ 1 ] << 8 ) + ( data [ 2 ] << 16 ) + ( data [ 3 ] << 24 ) ;
71100
72101 return { value : minimumDelegation } ;
73102 }
74103
75- async getAccountInfo ( address : PublicKey , commitment ?: string ) : Promise < AccountInfo < Buffer > > {
76- const account = await this . client . getAccount ( address , commitment ) ;
104+ async getAccountInfo ( address : PublicKey ) : Promise < AccountInfo < Buffer > > {
105+ const account = await this . client . getAccount ( address ) ;
77106 if ( account ) {
78107 account . data = Buffer . from ( account . data ) ;
79108 }
80109 return account ;
81110 }
82111}
83112
84- async function startWithContext ( authorizedWithdrawer ?: PublicKey ) {
113+ async function startWithContext ( authorizedWithdrawer ?: PublicKey ) : Promise < LiteContext > {
85114 const voteAccountData = Uint8Array . from ( atob ( voteAccount . account . data [ 0 ] ) , ( c ) =>
86115 c . charCodeAt ( 0 ) ,
87116 ) ;
@@ -90,48 +119,74 @@ async function startWithContext(authorizedWithdrawer?: PublicKey) {
90119 voteAccountData . set ( authorizedWithdrawer . toBytes ( ) , 36 ) ;
91120 }
92121
93- return await start (
94- [
95- {
96- name : Buffer . from ( 'spl_single_pool' ) . toString ( 'utf-8' ) ,
97- programId : SinglePoolProgram . programId ,
98- } ,
99- {
100- name : Buffer . from ( 'mpl_token_metadata' ) . toString ( 'utf-8' ) ,
101- programId : MPL_METADATA_PROGRAM_ID ,
102- } ,
103- ] ,
104- [
105- {
106- address : new PublicKey ( voteAccount . pubkey ) ,
107- info : {
108- lamports : voteAccount . account . lamports ,
109- data : voteAccountData ,
110- owner : VoteProgram . programId ,
111- executable : false ,
112- } ,
113- } ,
114- ] ,
115- undefined ,
116- undefined ,
117- // stake_raise_minimum_delegation_to_1_sol::id()
118- [ new PublicKey ( '9onWzzvCzNC2jfhxxeqRgs5q7nFAAKpCUvkj6T6GJK9i' ) ] ,
122+ const svm = new LiteSVM ( ) ;
123+ const payer = Keypair . generate ( ) ;
124+
125+ svm . airdrop ( payer . publicKey , 10_000n * BigInt ( LAMPORTS_PER_SOL ) ) ;
126+
127+ svm . addProgramFromFile (
128+ SinglePoolProgram . programId ,
129+ path . resolve ( process . cwd ( ) , 'tests' , 'fixtures' , 'spl_single_pool.so' ) ,
119130 ) ;
131+ svm . addProgramFromFile (
132+ MPL_METADATA_PROGRAM_ID ,
133+ path . resolve ( process . cwd ( ) , 'tests' , 'fixtures' , 'mpl_token_metadata.so' ) ,
134+ ) ;
135+
136+ svm . setAccount ( new PublicKey ( voteAccount . pubkey ) , {
137+ lamports : voteAccount . account . lamports ,
138+ data : voteAccountData ,
139+ owner : VoteProgram . programId ,
140+ executable : false ,
141+ rentEpoch : 0 ,
142+ } ) ;
143+
144+ const schedule = svm . getEpochSchedule ( ) ;
145+ const clock = svm . getClock ( ) ;
146+ const history = svm . getStakeHistory ( ) ;
147+
148+ clock . slot = schedule . firstNormalSlot + 1n ;
149+ clock . epoch = schedule . firstNormalEpoch ;
150+ clock . leaderScheduleEpoch = schedule . firstNormalEpoch ;
151+
152+ for ( let epoch = 0n ; epoch < schedule . firstNormalEpoch ; epoch += 1n ) {
153+ history . add ( epoch , STAKE_HISTORY_ENTRY ) ;
154+ }
155+
156+ svm . setClock ( clock ) ;
157+ svm . setStakeHistory ( history ) ;
158+
159+ const banksClient = new LiteBanksClient ( svm ) ;
160+
161+ return {
162+ svm,
163+ payer,
164+ banksClient,
165+ advanceEpoch ( ) {
166+ const schedule = svm . getEpochSchedule ( ) ;
167+ const clock = svm . getClock ( ) ;
168+ const history = svm . getStakeHistory ( ) ;
169+
170+ history . add ( clock . epoch , STAKE_HISTORY_ENTRY ) ;
171+ clock . slot += schedule . slotsPerEpoch ;
172+ clock . epoch += 1n ;
173+ clock . leaderScheduleEpoch = clock . epoch ;
174+
175+ svm . setClock ( clock ) ;
176+ svm . setStakeHistory ( history ) ;
177+ } ,
178+ } ;
120179}
121180
122- async function processTransaction (
123- context : ProgramTestContext ,
124- transaction : Transaction ,
125- signers = [ ] ,
126- ) {
127- transaction . recentBlockhash = context . lastBlockhash ;
181+ async function processTransaction ( context : LiteContext , transaction : Transaction , signers = [ ] ) {
182+ transaction . recentBlockhash = context . svm . latestBlockhash ( ) ;
128183 transaction . feePayer = context . payer . publicKey ;
129184 transaction . sign ( ...[ context . payer ] . concat ( signers ) ) ;
130185 return context . banksClient . processTransaction ( transaction ) ;
131186}
132187
133188async function createAndDelegateStakeAccount (
134- context : ProgramTestContext ,
189+ context : LiteContext ,
135190 voteAccountAddress : PublicKey ,
136191) : Promise < PublicKey > {
137192 const connection = new BanksConnection ( context . banksClient , context . payer ) ;
@@ -193,6 +248,9 @@ test('replenish pool', async (t) => {
193248 const connection = new BanksConnection ( client , payer ) ;
194249
195250 const voteAccountAddress = new PublicKey ( voteAccount . pubkey ) ;
251+ const poolAddress = await findPoolAddress ( SinglePoolProgram . programId , voteAccountAddress ) ;
252+ const poolStakeAddress = await findPoolStakeAddress ( SinglePoolProgram . programId , poolAddress ) ;
253+ const poolOnrampAddress = await findPoolOnRampAddress ( SinglePoolProgram . programId , poolAddress ) ;
196254
197255 // initialize pool
198256 let transaction = await SinglePoolProgram . initialize (
@@ -201,17 +259,24 @@ test('replenish pool', async (t) => {
201259 payer . publicKey ,
202260 ) ;
203261 await processTransaction ( context , transaction ) ;
204-
205- const slot = await client . getSlot ( ) ;
206- context . warpToSlot ( slot + SLOTS_PER_EPOCH ) ;
262+ context . advanceEpoch ( ) ;
263+
264+ transaction = new Transaction ( ) . add (
265+ SystemProgram . transfer ( {
266+ fromPubkey : payer . publicKey ,
267+ toPubkey : poolStakeAddress ,
268+ lamports : LAMPORTS_PER_SOL ,
269+ } ) ,
270+ ) ;
271+ await processTransaction ( context , transaction ) ;
207272
208273 // replenish pool
209274 transaction = await SinglePoolProgram . replenishPool ( voteAccountAddress ) ;
275+ await processTransaction ( context , transaction ) ;
210276
211- // NOTE we cannot test executing this because bankrun latest is on 1.18
212- // maybe someday
213- //await processTransaction(context, transaction);
214- t . true ( true ) ;
277+ const stakeRent = await connection . getMinimumBalanceForRentExemption ( StakeProgram . space ) ;
278+ const poolOnrampAccount = await client . getAccount ( poolOnrampAddress ) ;
279+ t . is ( poolOnrampAccount . lamports , LAMPORTS_PER_SOL + stakeRent , 'lamports have been replenished' ) ;
215280} ) ;
216281
217282test ( 'deposit' , async ( t ) => {
@@ -232,12 +297,9 @@ test('deposit', async (t) => {
232297 payer . publicKey ,
233298 ) ;
234299 await processTransaction ( context , transaction ) ;
235-
236- const slot = await client . getSlot ( ) ;
237- context . warpToSlot ( slot + SLOTS_PER_EPOCH ) ;
300+ context . advanceEpoch ( ) ;
238301
239302 // deposit
240- /* bankrun is still on 1.18 so this fails. update later
241303 transaction = await SinglePoolProgram . deposit ( {
242304 connection,
243305 pool : poolAddress ,
@@ -254,9 +316,6 @@ test('deposit', async (t) => {
254316 LAMPORTS_PER_SOL + minimumDelegation + stakeRent ,
255317 'stake has been deposited' ,
256318 ) ;
257- */
258-
259- t . true ( true ) ;
260319} ) ;
261320
262321test ( 'withdraw' , async ( t ) => {
@@ -277,9 +336,7 @@ test('withdraw', async (t) => {
277336 payer . publicKey ,
278337 ) ;
279338 await processTransaction ( context , transaction ) ;
280-
281- const slot = await client . getSlot ( ) ;
282- context . warpToSlot ( slot + SLOTS_PER_EPOCH ) ;
339+ context . advanceEpoch ( ) ;
283340
284341 // deposit
285342 transaction = await SinglePoolProgram . deposit ( {
@@ -288,7 +345,6 @@ test('withdraw', async (t) => {
288345 userWallet : payer . publicKey ,
289346 userStakeAccount : depositAccount ,
290347 } ) ;
291- /* bankrun is still on 1.18 so this fails. update later
292348 await processTransaction ( context , transaction ) ;
293349
294350 const minimumDelegation = ( await connection . getStakeMinimumDelegation ( ) ) . value ;
@@ -310,9 +366,6 @@ test('withdraw', async (t) => {
310366 const stakeRent = await connection . getMinimumBalanceForRentExemption ( StakeProgram . space ) ;
311367 const userStakeAccount = await client . getAccount ( withdrawAccount . publicKey ) ;
312368 t . is ( userStakeAccount . lamports , minimumDelegation + stakeRent , 'stake has been withdrawn' ) ;
313- */
314-
315- t . true ( true ) ;
316369} ) ;
317370
318371test ( 'create metadata' , async ( t ) => {
0 commit comments