@@ -9,18 +9,25 @@ import {
9
9
BPF_LOADER_PROGRAM_ID ,
10
10
} from '@solana/web3.js' ;
11
11
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' ;
13
18
import { url } from '../url' ;
14
19
import { newAccountWithLamports } from '../client/util/new-account-with-lamports' ;
15
20
import { sleep } from '../client/util/sleep' ;
16
21
import { Store } from './store' ;
17
22
18
23
// Loaded token program's program id
19
24
let programId : PublicKey ;
25
+ let associatedProgramId : PublicKey ;
20
26
21
27
// Accounts setup in createMint and used by all subsequent tests
22
28
let testMintAuthority : Account ;
23
29
let testToken : Token ;
30
+ let testTokenDecimals : number = 2 ;
24
31
25
32
// Accounts setup in createAccount and used by all subsequent tests
26
33
let testAccountOwner : Account ;
@@ -78,43 +85,60 @@ async function loadProgram(
78
85
return program_account . publicKey ;
79
86
}
80
87
81
- async function GetPrograms ( connection : Connection ) : Promise < PublicKey > {
88
+ async function GetPrograms ( connection : Connection ) : Promise < void > {
82
89
const programVersion = process . env . PROGRAM_VERSION ;
83
90
if ( programVersion ) {
84
91
switch ( programVersion ) {
85
92
case '2.0.4' :
86
- return new PublicKey ( 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' ) ;
93
+ programId = TOKEN_PROGRAM_ID ;
94
+ associatedProgramId = ASSOCIATED_TOKEN_PROGRAM_ID ;
95
+ return ;
87
96
default :
88
97
throw new Error ( 'Unknown program version' ) ;
89
98
}
90
99
}
91
100
92
101
const store = new Store ( ) ;
93
- let tokenProgramId = null ;
94
102
try {
95
103
const config = await store . load ( 'config.json' ) ;
96
- console . log ( 'Using pre-loaded Token program ' ) ;
104
+ console . log ( 'Using pre-loaded Token programs ' ) ;
97
105
console . log (
98
- ' Note: To reload program remove client/util/store/ config.json',
106
+ ` Note: To reload program remove ${ Store . getFilename ( ' config.json') } ` ,
99
107
) ;
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 ) ;
101
115
} 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 (
103
122
connection ,
104
123
'../../target/bpfel-unknown-unknown/release/spl_token.so' ,
105
124
) ;
125
+ associatedProgramId = await loadProgram (
126
+ connection ,
127
+ '../../target/bpfel-unknown-unknown/release/spl_associated_token_account.so' ,
128
+ ) ;
106
129
await store . save ( 'config.json' , {
107
- tokenProgramId : tokenProgramId . toString ( ) ,
130
+ tokenProgramId : programId . toString ( ) ,
131
+ associatedTokenProgramId : associatedProgramId . toString ( ) ,
108
132
} ) ;
109
133
}
110
- return tokenProgramId ;
111
134
}
112
135
113
136
export async function loadTokenProgram ( ) : Promise < void > {
114
137
const connection = await getConnection ( ) ;
115
- programId = await GetPrograms ( connection ) ;
138
+ await GetPrograms ( connection ) ;
116
139
117
140
console . log ( 'Token Program ID' , programId . toString ( ) ) ;
141
+ console . log ( 'Associated Token Program ID' , associatedProgramId . toString ( ) ) ;
118
142
}
119
143
120
144
export async function createMint ( ) : Promise < void > {
@@ -126,9 +150,12 @@ export async function createMint(): Promise<void> {
126
150
payer ,
127
151
testMintAuthority . publicKey ,
128
152
testMintAuthority . publicKey ,
129
- 2 ,
153
+ testTokenDecimals ,
130
154
programId ,
131
155
) ;
156
+ // HACK: override hard-coded ASSOCIATED_TOKEN_PROGRAM_ID with corresponding
157
+ // custom test fixture
158
+ testToken . associatedProgramId = associatedProgramId ;
132
159
133
160
const mintInfo = await testToken . getMintInfo ( ) ;
134
161
if ( mintInfo . mintAuthority !== null ) {
@@ -137,7 +164,7 @@ export async function createMint(): Promise<void> {
137
164
assert ( mintInfo . mintAuthority !== null ) ;
138
165
}
139
166
assert ( mintInfo . supply . toNumber ( ) === 0 ) ;
140
- assert ( mintInfo . decimals === 2 ) ;
167
+ assert ( mintInfo . decimals === testTokenDecimals ) ;
141
168
assert ( mintInfo . isInitialized === true ) ;
142
169
if ( mintInfo . freezeAuthority !== null ) {
143
170
assert ( mintInfo . freezeAuthority . equals ( testMintAuthority . publicKey ) ) ;
@@ -160,6 +187,48 @@ export async function createAccount(): Promise<void> {
160
187
assert ( accountInfo . isNative === false ) ;
161
188
assert ( accountInfo . rentExemptReserve === null ) ;
162
189
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
+ ) ;
163
232
}
164
233
165
234
export async function mintTo ( ) : Promise < void > {
@@ -219,7 +288,7 @@ export async function transferChecked(): Promise<void> {
219
288
testAccountOwner ,
220
289
[ ] ,
221
290
100 ,
222
- 1 ,
291
+ testTokenDecimals - 1 ,
223
292
] ) ,
224
293
) ;
225
294
@@ -229,7 +298,7 @@ export async function transferChecked(): Promise<void> {
229
298
testAccountOwner ,
230
299
[ ] ,
231
300
100 ,
232
- 2 ,
301
+ testTokenDecimals ,
233
302
) ;
234
303
235
304
const mintInfo = await testToken . getMintInfo ( ) ;
@@ -242,6 +311,26 @@ export async function transferChecked(): Promise<void> {
242
311
assert ( testAccountInfo . amount . toNumber ( ) === 1800 ) ;
243
312
}
244
313
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
+
245
334
export async function approveRevoke ( ) : Promise < void > {
246
335
const delegate = new Account ( ) . publicKey ;
247
336
0 commit comments