@@ -9,18 +9,25 @@ import {
99 BPF_LOADER_PROGRAM_ID ,
1010} from '@solana/web3.js' ;
1111
12- import { Token , NATIVE_MINT } from '../client/token' ;
12+ import {
13+ Token ,
14+ TOKEN_PROGRAM_ID ,
15+ ASSOCIATED_TOKEN_PROGRAM_ID ,
16+ NATIVE_MINT ,
17+ } from '../client/token' ;
1318import { url } from '../url' ;
1419import { newAccountWithLamports } from '../client/util/new-account-with-lamports' ;
1520import { sleep } from '../client/util/sleep' ;
1621import { Store } from './store' ;
1722
1823// Loaded token program's program id
1924let programId : PublicKey ;
25+ let associatedProgramId : PublicKey ;
2026
2127// Accounts setup in createMint and used by all subsequent tests
2228let testMintAuthority : Account ;
2329let testToken : Token ;
30+ let testTokenDecimals : number = 2 ;
2431
2532// Accounts setup in createAccount and used by all subsequent tests
2633let testAccountOwner : Account ;
@@ -78,43 +85,60 @@ async function loadProgram(
7885 return program_account . publicKey ;
7986}
8087
81- async function GetPrograms ( connection : Connection ) : Promise < PublicKey > {
88+ async function GetPrograms ( connection : Connection ) : Promise < void > {
8289 const programVersion = process . env . PROGRAM_VERSION ;
8390 if ( programVersion ) {
8491 switch ( programVersion ) {
8592 case '2.0.4' :
86- return new PublicKey ( 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' ) ;
93+ programId = TOKEN_PROGRAM_ID ;
94+ associatedProgramId = ASSOCIATED_TOKEN_PROGRAM_ID ;
95+ return ;
8796 default :
8897 throw new Error ( 'Unknown program version' ) ;
8998 }
9099 }
91100
92101 const store = new Store ( ) ;
93- let tokenProgramId = null ;
94102 try {
95103 const config = await store . load ( 'config.json' ) ;
96- console . log ( 'Using pre-loaded Token program ' ) ;
104+ console . log ( 'Using pre-loaded Token programs ' ) ;
97105 console . log (
98- ' Note: To reload program remove client/util/store/ config.json',
106+ ` Note: To reload program remove ${ Store . getFilename ( ' config.json') } ` ,
99107 ) ;
100- tokenProgramId = new PublicKey ( config . tokenProgramId ) ;
108+ programId = new PublicKey ( config . tokenProgramId ) ;
109+ associatedProgramId = new PublicKey ( config . associatedTokenProgramId ) ;
110+ let info ;
111+ info = await connection . getAccountInfo ( programId ) ;
112+ assert ( info != null ) ;
113+ info = await connection . getAccountInfo ( associatedProgramId ) ;
114+ assert ( info != null ) ;
101115 } catch ( err ) {
102- tokenProgramId = await loadProgram (
116+ console . log (
117+ 'Checking pre-loaded Token programs failed, will load new programs:' ,
118+ ) ;
119+ console . log ( { err} ) ;
120+
121+ programId = await loadProgram (
103122 connection ,
104123 '../../target/bpfel-unknown-unknown/release/spl_token.so' ,
105124 ) ;
125+ associatedProgramId = await loadProgram (
126+ connection ,
127+ '../../target/bpfel-unknown-unknown/release/spl_associated_token_account.so' ,
128+ ) ;
106129 await store . save ( 'config.json' , {
107- tokenProgramId : tokenProgramId . toString ( ) ,
130+ tokenProgramId : programId . toString ( ) ,
131+ associatedTokenProgramId : associatedProgramId . toString ( ) ,
108132 } ) ;
109133 }
110- return tokenProgramId ;
111134}
112135
113136export async function loadTokenProgram ( ) : Promise < void > {
114137 const connection = await getConnection ( ) ;
115- programId = await GetPrograms ( connection ) ;
138+ await GetPrograms ( connection ) ;
116139
117140 console . log ( 'Token Program ID' , programId . toString ( ) ) ;
141+ console . log ( 'Associated Token Program ID' , associatedProgramId . toString ( ) ) ;
118142}
119143
120144export async function createMint ( ) : Promise < void > {
@@ -126,9 +150,12 @@ export async function createMint(): Promise<void> {
126150 payer ,
127151 testMintAuthority . publicKey ,
128152 testMintAuthority . publicKey ,
129- 2 ,
153+ testTokenDecimals ,
130154 programId ,
131155 ) ;
156+ // HACK: override hard-coded ASSOCIATED_TOKEN_PROGRAM_ID with corresponding
157+ // custom test fixture
158+ testToken . associatedProgramId = associatedProgramId ;
132159
133160 const mintInfo = await testToken . getMintInfo ( ) ;
134161 if ( mintInfo . mintAuthority !== null ) {
@@ -137,7 +164,7 @@ export async function createMint(): Promise<void> {
137164 assert ( mintInfo . mintAuthority !== null ) ;
138165 }
139166 assert ( mintInfo . supply . toNumber ( ) === 0 ) ;
140- assert ( mintInfo . decimals === 2 ) ;
167+ assert ( mintInfo . decimals === testTokenDecimals ) ;
141168 assert ( mintInfo . isInitialized === true ) ;
142169 if ( mintInfo . freezeAuthority !== null ) {
143170 assert ( mintInfo . freezeAuthority . equals ( testMintAuthority . publicKey ) ) ;
@@ -160,6 +187,48 @@ export async function createAccount(): Promise<void> {
160187 assert ( accountInfo . isNative === false ) ;
161188 assert ( accountInfo . rentExemptReserve === null ) ;
162189 assert ( accountInfo . closeAuthority === null ) ;
190+
191+ // you can create as many accounts as with same owner
192+ const testAccount2 = await testToken . createAccount (
193+ testAccountOwner . publicKey ,
194+ ) ;
195+ assert ( ! testAccount2 . equals ( testAccount ) ) ;
196+ }
197+
198+ export async function createAssociatedAccount ( ) : Promise < void > {
199+ let info ;
200+ const connection = await getConnection ( ) ;
201+
202+ const owner = new Account ( ) ;
203+ const associatedAddress = await Token . getAssociatedTokenAddress (
204+ associatedProgramId ,
205+ programId ,
206+ testToken . publicKey ,
207+ owner . publicKey ,
208+ ) ;
209+
210+ // associated account shouldn't exist
211+ info = await connection . getAccountInfo ( associatedAddress ) ;
212+ assert ( info == null ) ;
213+
214+ const createdAddress = await testToken . createAssociatedTokenAccount (
215+ owner . publicKey ,
216+ ) ;
217+ assert ( createdAddress . equals ( associatedAddress ) ) ;
218+
219+ // associated account should exist now
220+ info = await testToken . getAccountInfo ( associatedAddress ) ;
221+ assert ( info != null ) ;
222+ assert ( info . mint . equals ( testToken . publicKey ) ) ;
223+ assert ( info . owner . equals ( owner . publicKey ) ) ;
224+ assert ( info . amount . toNumber ( ) === 0 ) ;
225+
226+ // creating again should cause TX error for the associated token account
227+ assert (
228+ await didThrow ( testToken , testToken . createAssociatedTokenAccount , [
229+ owner . publicKey ,
230+ ] ) ,
231+ ) ;
163232}
164233
165234export async function mintTo ( ) : Promise < void > {
@@ -219,7 +288,7 @@ export async function transferChecked(): Promise<void> {
219288 testAccountOwner ,
220289 [ ] ,
221290 100 ,
222- 1 ,
291+ testTokenDecimals - 1 ,
223292 ] ) ,
224293 ) ;
225294
@@ -229,7 +298,7 @@ export async function transferChecked(): Promise<void> {
229298 testAccountOwner ,
230299 [ ] ,
231300 100 ,
232- 2 ,
301+ testTokenDecimals ,
233302 ) ;
234303
235304 const mintInfo = await testToken . getMintInfo ( ) ;
@@ -242,6 +311,26 @@ export async function transferChecked(): Promise<void> {
242311 assert ( testAccountInfo . amount . toNumber ( ) === 1800 ) ;
243312}
244313
314+ export async function transferCheckedAssociated ( ) : Promise < void > {
315+ const dest = new Account ( ) . publicKey ;
316+ let associatedAccount ;
317+
318+ associatedAccount = await testToken . getOrCreateAssociatedAccountInfo ( dest ) ;
319+ assert ( associatedAccount . amount . toNumber ( ) === 0 ) ;
320+
321+ await testToken . transferChecked (
322+ testAccount ,
323+ associatedAccount . address ,
324+ testAccountOwner ,
325+ [ ] ,
326+ 123 ,
327+ testTokenDecimals ,
328+ ) ;
329+
330+ associatedAccount = await testToken . getOrCreateAssociatedAccountInfo ( dest ) ;
331+ assert ( associatedAccount . amount . toNumber ( ) === 123 ) ;
332+ }
333+
245334export async function approveRevoke ( ) : Promise < void > {
246335 const delegate = new Account ( ) . publicKey ;
247336
0 commit comments