feat: Add ERC20-like Token
This commit is contained in:
@ -18,13 +18,15 @@ type RpcResponse = {
|
||||
|
||||
export const mockRpc: Array<[string, RpcRequest, RpcResponse]> = [];
|
||||
|
||||
// Define DOITLIVE in the environment to test against the real full node
|
||||
// identified by `url` instead of using the mock
|
||||
export const mockRpcEnabled = !process.env.DOITLIVE;
|
||||
|
||||
// Suppress lint: 'JestMockFn' is not defined
|
||||
// eslint-disable-next-line no-undef
|
||||
const mock: JestMockFn<any, any> = jest.fn(
|
||||
(fetchUrl, fetchOptions) => {
|
||||
// Define DOITLIVE in the environment to test against the real full node
|
||||
// identified by `url` instead of using the mock
|
||||
if (process.env.DOITLIVE) {
|
||||
if (!mockRpcEnabled) {
|
||||
console.log(`Note: node-fetch mock is disabled, testing live against ${fetchUrl}`);
|
||||
return fetch(fetchUrl, fetchOptions);
|
||||
}
|
||||
|
@ -4,10 +4,7 @@ import {Account} from '../src/account';
|
||||
import {Connection} from '../src/connection';
|
||||
import {SystemProgram} from '../src/system-program';
|
||||
import {mockRpc} from './__mocks__/node-fetch';
|
||||
|
||||
let url = 'http://localhost:8899';
|
||||
//url = 'http://testnet.solana.com:8899';
|
||||
//url = 'http://master.testnet.solana.com:8899';
|
||||
import {url} from './url.js';
|
||||
|
||||
const errorMessage = 'Invalid request';
|
||||
const errorResponse = {
|
||||
|
514
web3.js/test/token-program.test.js
Normal file
514
web3.js/test/token-program.test.js
Normal file
@ -0,0 +1,514 @@
|
||||
// @flow
|
||||
|
||||
import {Account} from '../src/account';
|
||||
import {Connection} from '../src/connection';
|
||||
import {Token, TokenAmount} from '../src/token-program';
|
||||
import {PublicKey} from '../src/publickey';
|
||||
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
|
||||
import {url} from './url.js';
|
||||
import type {SignatureStatus} from '../src/connection';
|
||||
|
||||
if (!mockRpcEnabled) {
|
||||
// The default of 5 seconds is too slow for live testing sometimes
|
||||
jest.setTimeout(10000);
|
||||
}
|
||||
|
||||
function mockGetLastId() {
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getLastId',
|
||||
params: [],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: '2BjEqiiT43J6XskiHdz7aoocjPeWkCPiKD72SiFQsrA2',
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
function mockGetSignatureStatus(result: SignatureStatus = 'Confirmed') {
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getSignatureStatus',
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result,
|
||||
},
|
||||
]);
|
||||
}
|
||||
function mockSendTransaction() {
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'sendTransaction',
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
async function newAccountWithTokens(connection: Connection, amount: number = 10): Promise<Account> {
|
||||
const account = new Account();
|
||||
|
||||
{
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'requestAirdrop',
|
||||
params: [account.publicKey.toBase58(), amount],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
await connection.requestAirdrop(account.publicKey, amount);
|
||||
return account;
|
||||
}
|
||||
|
||||
// A token created by the first test and used by all subsequent tests
|
||||
let testToken: Token;
|
||||
|
||||
// Initial owner of the token supply
|
||||
let initialOwner;
|
||||
let initialOwnerTokenAccount: PublicKey;
|
||||
|
||||
test('create new token', async () => {
|
||||
const connection = new Connection(url);
|
||||
|
||||
initialOwner = await newAccountWithTokens(connection);
|
||||
|
||||
{
|
||||
// mock SystemProgram.createAccount transaction for Token.createNewToken()
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
|
||||
// mock Token.newAccount() transaction
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus('SignatureNotFound');
|
||||
mockGetSignatureStatus();
|
||||
|
||||
// mock SystemProgram.createAccount transaction for Token.createNewToken()
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
|
||||
// mock Token.createNewToken() transaction
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus('SignatureNotFound');
|
||||
mockGetSignatureStatus();
|
||||
}
|
||||
|
||||
[testToken, initialOwnerTokenAccount] = await Token.createNewToken(
|
||||
connection,
|
||||
initialOwner,
|
||||
new TokenAmount(10000),
|
||||
'Test token',
|
||||
'TEST',
|
||||
2
|
||||
);
|
||||
|
||||
{
|
||||
// mock Token.tokenInfo()'s getAccountInfo
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getAccountInfo',
|
||||
params: [testToken.token.toBase58()],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: {
|
||||
program_id: [...Token.programId.toBuffer()],
|
||||
tokens: 1,
|
||||
userdata: [
|
||||
1,
|
||||
16, 39, 0, 0, 0, 0, 0, 0,
|
||||
2,
|
||||
10, 0, 0, 0, 0, 0, 0, 0, 84, 101, 115, 116, 32, 116, 111, 107, 101, 110,
|
||||
4, 0, 0, 0, 0, 0, 0, 0, 84, 69, 83, 84
|
||||
],
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
const tokenInfo = await testToken.tokenInfo();
|
||||
|
||||
expect(tokenInfo.supply.toNumber()).toBe(10000);
|
||||
expect(tokenInfo.decimals).toBe(2);
|
||||
expect(tokenInfo.name).toBe('Test token');
|
||||
expect(tokenInfo.symbol).toBe('TEST');
|
||||
|
||||
|
||||
{
|
||||
// mock Token.accountInfo()'s getAccountInfo
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getAccountInfo',
|
||||
params: [initialOwnerTokenAccount.toBase58()],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: {
|
||||
program_id: [...Token.programId.toBuffer()],
|
||||
tokens: 1,
|
||||
userdata: [
|
||||
2,
|
||||
...testToken.token.toBuffer(),
|
||||
...initialOwner.publicKey.toBuffer(),
|
||||
16, 39, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
],
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
const accountInfo = await testToken.accountInfo(initialOwnerTokenAccount);
|
||||
|
||||
expect(accountInfo.token.equals(testToken.token)).toBe(true);
|
||||
expect(accountInfo.owner.equals(initialOwner.publicKey)).toBe(true);
|
||||
expect(accountInfo.amount.toNumber()).toBe(10000);
|
||||
expect(accountInfo.source).toBe(null);
|
||||
});
|
||||
|
||||
|
||||
test('create new token account', async () => {
|
||||
const connection = new Connection(url);
|
||||
const destOwner = await newAccountWithTokens(connection);
|
||||
|
||||
{
|
||||
// mock SystemProgram.createAccount transaction for Token.newAccount()
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
|
||||
// mock Token.newAccount() transaction
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
}
|
||||
|
||||
const dest = await testToken.newAccount(destOwner);
|
||||
{
|
||||
// mock Token.accountInfo()'s getAccountInfo
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getAccountInfo',
|
||||
params: [dest.toBase58()],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: {
|
||||
program_id: [...Token.programId.toBuffer()],
|
||||
tokens: 1,
|
||||
userdata: [
|
||||
2,
|
||||
...testToken.token.toBuffer(),
|
||||
...destOwner.publicKey.toBuffer(),
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
],
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
const accountInfo = await testToken.accountInfo(dest);
|
||||
|
||||
expect(accountInfo.token.equals(testToken.token)).toBe(true);
|
||||
expect(accountInfo.owner.equals(destOwner.publicKey)).toBe(true);
|
||||
expect(accountInfo.amount.toNumber()).toBe(0);
|
||||
expect(accountInfo.source).toBe(null);
|
||||
});
|
||||
|
||||
|
||||
test('transfer', async () => {
|
||||
const connection = new Connection(url);
|
||||
const destOwner = await newAccountWithTokens(connection);
|
||||
|
||||
{
|
||||
// mock SystemProgram.createAccount transaction for Token.newAccount()
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
|
||||
// mock Token.newAccount() transaction
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
}
|
||||
|
||||
const dest = await testToken.newAccount(destOwner);
|
||||
|
||||
{
|
||||
// mock Token.transfer()'s getAccountInfo
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getAccountInfo',
|
||||
params: [initialOwnerTokenAccount.toBase58()],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: {
|
||||
program_id: [...Token.programId.toBuffer()],
|
||||
tokens: 1,
|
||||
userdata: [
|
||||
2,
|
||||
...testToken.token.toBuffer(),
|
||||
...initialOwner.publicKey.toBuffer(),
|
||||
123, 0, 0, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
],
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
// mock Token.transfer() transaction
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
}
|
||||
|
||||
await testToken.transfer(
|
||||
initialOwner,
|
||||
initialOwnerTokenAccount,
|
||||
dest,
|
||||
123
|
||||
);
|
||||
|
||||
{
|
||||
// mock Token.accountInfo()'s getAccountInfo
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getAccountInfo',
|
||||
params: [dest.toBase58()],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: {
|
||||
program_id: [...Token.programId.toBuffer()],
|
||||
tokens: 1,
|
||||
userdata: [
|
||||
2,
|
||||
...testToken.token.toBuffer(),
|
||||
...dest.toBuffer(),
|
||||
123, 0, 0, 0, 0, 0, 0, 0,
|
||||
0,
|
||||
],
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
const destAccountInfo = await testToken.accountInfo(dest);
|
||||
expect(destAccountInfo.amount.toNumber()).toBe(123);
|
||||
});
|
||||
|
||||
|
||||
test('approve/revoke', async () => {
|
||||
const connection = new Connection(url);
|
||||
const delegateOwner = await newAccountWithTokens(connection);
|
||||
|
||||
{
|
||||
// mock SystemProgram.createAccount transaction for Token.newAccount()
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
|
||||
// mock Token.newAccount() transaction
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
}
|
||||
const delegate = await testToken.newAccount(delegateOwner, initialOwnerTokenAccount);
|
||||
|
||||
{
|
||||
// mock Token.approve() transaction
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
}
|
||||
|
||||
await testToken.approve(
|
||||
initialOwner,
|
||||
initialOwnerTokenAccount,
|
||||
delegate,
|
||||
456
|
||||
);
|
||||
|
||||
{
|
||||
// mock Token.accountInfo()'s getAccountInfo
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getAccountInfo',
|
||||
params: [delegate.toBase58()],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: {
|
||||
program_id: [...Token.programId.toBuffer()],
|
||||
tokens: 1,
|
||||
userdata: [
|
||||
2,
|
||||
...testToken.token.toBuffer(),
|
||||
...delegate.toBuffer(),
|
||||
200, 1, 0, 0, 0, 0, 0, 0,
|
||||
1,
|
||||
...initialOwnerTokenAccount.toBuffer(),
|
||||
],
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
let delegateAccountInfo = await testToken.accountInfo(delegate);
|
||||
|
||||
expect(delegateAccountInfo.amount.toNumber()).toBe(456);
|
||||
if (delegateAccountInfo.source === null) {
|
||||
throw new Error('source should not be null');
|
||||
} else {
|
||||
expect(delegateAccountInfo.source.equals(initialOwnerTokenAccount)).toBe(true);
|
||||
}
|
||||
|
||||
{
|
||||
// mock Token.revoke() transaction
|
||||
mockGetLastId();
|
||||
mockSendTransaction();
|
||||
mockGetSignatureStatus();
|
||||
}
|
||||
|
||||
await testToken.revoke(
|
||||
initialOwner,
|
||||
initialOwnerTokenAccount,
|
||||
delegate,
|
||||
);
|
||||
|
||||
{
|
||||
// mock Token.accountInfo()'s getAccountInfo
|
||||
mockRpc.push([
|
||||
url,
|
||||
{
|
||||
method: 'getAccountInfo',
|
||||
params: [delegate.toBase58()],
|
||||
},
|
||||
{
|
||||
error: null,
|
||||
result: {
|
||||
program_id: [...Token.programId.toBuffer()],
|
||||
tokens: 1,
|
||||
userdata: [
|
||||
2,
|
||||
...testToken.token.toBuffer(),
|
||||
...delegate.toBuffer(),
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1,
|
||||
...initialOwnerTokenAccount.toBuffer(),
|
||||
],
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
delegateAccountInfo = await testToken.accountInfo(delegate);
|
||||
|
||||
expect(delegateAccountInfo.amount.toNumber()).toBe(0);
|
||||
if (delegateAccountInfo.source === null) {
|
||||
throw new Error('source should not be null');
|
||||
} else {
|
||||
expect(delegateAccountInfo.source.equals(initialOwnerTokenAccount)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('invalid approve', async () => {
|
||||
if (mockRpcEnabled) {
|
||||
console.log('non-live test skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
const connection = new Connection(url);
|
||||
const owner = await newAccountWithTokens(connection);
|
||||
|
||||
const account1 = await testToken.newAccount(owner);
|
||||
const account2 = await testToken.newAccount(owner);
|
||||
|
||||
// account2 is not a delegate account
|
||||
expect(
|
||||
testToken.approve(
|
||||
owner,
|
||||
account1,
|
||||
account2,
|
||||
123
|
||||
)
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
|
||||
test('fail on approve overspend', async () => {
|
||||
if (mockRpcEnabled) {
|
||||
console.log('non-live test skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
const connection = new Connection(url);
|
||||
const owner = await newAccountWithTokens(connection);
|
||||
|
||||
const account1 = await testToken.newAccount(owner);
|
||||
const account1Delegate = await testToken.newAccount(owner, account1);
|
||||
const account2 = await testToken.newAccount(owner);
|
||||
|
||||
await testToken.transfer(
|
||||
initialOwner,
|
||||
initialOwnerTokenAccount,
|
||||
account1,
|
||||
10,
|
||||
);
|
||||
|
||||
await testToken.approve(
|
||||
owner,
|
||||
account1,
|
||||
account1Delegate,
|
||||
2
|
||||
);
|
||||
|
||||
await testToken.transfer(
|
||||
owner,
|
||||
account1Delegate,
|
||||
account2,
|
||||
1,
|
||||
);
|
||||
|
||||
await testToken.transfer(
|
||||
owner,
|
||||
account1Delegate,
|
||||
account2,
|
||||
1,
|
||||
);
|
||||
|
||||
expect(
|
||||
testToken.transfer(
|
||||
owner,
|
||||
account1Delegate,
|
||||
account2,
|
||||
1,
|
||||
)
|
||||
).rejects.toThrow();
|
||||
});
|
9
web3.js/test/url.js
Normal file
9
web3.js/test/url.js
Normal file
@ -0,0 +1,9 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* The connection url to use when running unit tests against a live network
|
||||
*/
|
||||
export const url = 'http://localhost:8899';
|
||||
//export const url = 'http://testnet.solana.com:8899';
|
||||
//export const url = 'http://master.testnet.solana.com:8899';
|
||||
|
Reference in New Issue
Block a user