Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 180 additions & 1 deletion __tests__/integration/template/transaction/template.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import SendTransaction from '../../../../src/new/sendTransaction';
import transactionUtils from '../../../../src/utils/transaction';
import { TransactionTemplateBuilder } from '../../../../src/template/transaction/builder';
import { WalletTxTemplateInterpreter } from '../../../../src/template/transaction/interpreter';
import { TOKEN_AUTHORITY_MASK, TOKEN_MELT_MASK, TOKEN_MINT_MASK } from '../../../../src/constants';
import {
NATIVE_TOKEN_UID,
TOKEN_AUTHORITY_MASK,
TOKEN_MELT_MASK,
TOKEN_MINT_MASK,
} from '../../../../src/constants';
import { TokenVersion } from '../../../../src/types';

const DEBUG = true;

Expand Down Expand Up @@ -438,3 +444,176 @@ describe('Template execution', () => {
);
});
});

describe('Template execution with fee tokens', () => {
let hWallet: HathorWallet;
let interpreter: WalletTxTemplateInterpreter;

beforeAll(async () => {
hWallet = await generateWalletHelper(null);
interpreter = new WalletTxTemplateInterpreter(hWallet);
const address = await hWallet.getAddressAtIndex(0);
await GenesisWalletHelper.injectFunds(hWallet, address, 10n, {});
});

afterAll(async () => {
await hWallet.stop();
await stopAllWallets();
await GenesisWalletHelper.clearListeners();
});

it('should be not able to create a custom fee token without providing a fee header', async () => {
const template = new TransactionTemplateBuilder()
.addConfigAction({
createToken: true,
tokenName: 'Tmpl Test Fee Token 01',
tokenSymbol: 'TTT01',
tokenVersion: TokenVersion.FEE,
})
.addSetVarAction({ name: 'addr', call: { method: 'get_wallet_address' } })
.addUtxoSelect({ fill: 5 })
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true })
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true })
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true })
.addTokenOutput({ address: '{addr}', amount: 100, useCreatedToken: true })
.build();

const tx = await interpreter.build(template, DEBUG);

expect(tx.outputs).toHaveLength(5);

// HTR change
expect(tx.outputs[0].tokenData).toBe(0);
expect(tx.outputs[0].value).toBe(5n);

// Created token
expect(tx.outputs[1].tokenData).toBe(1);
expect(tx.outputs[1].value).toBe(100n);

expect(tx.outputs[2].tokenData).toBe(1);
expect(tx.outputs[2].value).toBe(100n);

expect(tx.outputs[3].tokenData).toBe(1);
expect(tx.outputs[3].value).toBe(100n);

expect(tx.outputs[4].tokenData).toBe(1);
expect(tx.outputs[4].value).toBe(100n);

// Not have a fee header
expect(tx.headers).toHaveLength(0);

await transactionUtils.signTransaction(tx, hWallet.storage, DEFAULT_PIN_CODE);
tx.prepareToSend();
const sendTx = new SendTransaction({ storage: hWallet.storage, transaction: tx });

// Expecting the transaction to fail validation with negative HTR balance
await expect(sendTx.runFromMining()).rejects.toThrow(
'full validation failed: HTR balance is different than expected. (amount=-5, expected=0)'
);

// Check the wallet balance to verify nothing was spent from the wallet
const htrBalance = await hWallet.getBalance(NATIVE_TOKEN_UID);
expect(htrBalance[0]).toHaveProperty('balance.unlocked', 10n);
});

it('should be able to create a custom fee token providing a fee header', async () => {
const mintAmount = 8582n;
const template = new TransactionTemplateBuilder()
.addConfigAction({
createToken: true,
tokenName: 'Tmpl Test Fee Token 01',
tokenSymbol: 'TTT01',
tokenVersion: TokenVersion.FEE,
})
.addSetVarAction({ name: 'addr', call: { method: 'get_wallet_address' } })
.addUtxoSelect({ fill: 1 })
.addTokenOutput({ address: '{addr}', amount: mintAmount, useCreatedToken: true })
.addFee({ amount: 1 })
.build();

const tx = await interpreter.build(template, DEBUG);

expect(tx.outputs).toHaveLength(2);

// HTR change
expect(tx.outputs[0].tokenData).toBe(0);
expect(tx.outputs[0].value).toBe(9n);

// Created token
expect(tx.outputs[1].tokenData).toBe(1);
expect(tx.outputs[1].value).toBe(mintAmount);

// Have a fee header
expect(tx.headers).toHaveLength(1);
expect(tx.getFeeHeader()).not.toBeNull();
expect(tx.getFeeHeader()!.entries).toHaveLength(1);
expect(tx.getFeeHeader()!.entries[0].tokenIndex).toBe(0);
expect(tx.getFeeHeader()!.entries[0].amount).toBe(1n);

// after validate send the tx to the mining service
await transactionUtils.signTransaction(tx, hWallet.storage, DEFAULT_PIN_CODE);
tx.prepareToSend();
const sendTx = new SendTransaction({ storage: hWallet.storage, transaction: tx });
await sendTx.runFromMining();
expect(tx.hash).not.toBeNull();
if (tx.hash === null) {
throw new Error('Transaction does not have a hash');
}
await waitForTxReceived(hWallet, tx.hash, undefined);

const htrBalance = await hWallet.getBalance(NATIVE_TOKEN_UID);
expect(htrBalance[0]).toHaveProperty('balance.unlocked', 9n);
});
it('should be able to complete the fees of a transaction', async () => {
const template = new TransactionTemplateBuilder()
.addConfigAction({
createToken: true,
tokenName: 'Tmpl Test Fee Token 01',
tokenSymbol: 'TTT01',
tokenVersion: TokenVersion.FEE,
})
.addSetVarAction({ name: 'addr', call: { method: 'get_wallet_address' } })
.addUtxoSelect({ fill: 5 })
.addTokenOutput({ address: '{addr}', amount: 9999999, useCreatedToken: true })
.addTokenOutput({ address: '{addr}', amount: 9999999, useCreatedToken: true })
.addTokenOutput({ address: '{addr}', amount: 9999999, useCreatedToken: true })
.addTokenOutput({ address: '{addr}', amount: 9999999, useCreatedToken: true })
.addCompleteAction({ calculateFee: true, token: '00' })
.build();

const tx = await interpreter.build(template, DEBUG);

// check the outputs
expect(tx.outputs).toHaveLength(6);

// HTR change
expect(tx.outputs[0].tokenData).toBe(0);
expect(tx.outputs[0].value).toBe(4n);

// Created token
expect(tx.outputs[1].tokenData).toBe(1);
expect(tx.outputs[1].value).toBe(9999999n);

// Have a fee header
expect(tx.headers).toHaveLength(1);
expect(tx.getFeeHeader()).not.toBeNull();
expect(tx.getFeeHeader()!.entries).toHaveLength(1);
expect(tx.getFeeHeader()!.entries[0].tokenIndex).toBe(0);
expect(tx.getFeeHeader()!.entries[0].amount).toBe(4n);

// after validate send the tx to the mining service
await transactionUtils.signTransaction(tx, hWallet.storage, DEFAULT_PIN_CODE);
tx.prepareToSend();
const sendTx = new SendTransaction({ storage: hWallet.storage, transaction: tx });
await sendTx.runFromMining();
expect(tx.hash).not.toBeNull();
if (tx.hash === null) {
throw new Error('Transaction does not have a hash');
}

await waitForTxReceived(hWallet, tx.hash, undefined);

const htrBalance = await hWallet.getBalance(NATIVE_TOKEN_UID);
expect(htrBalance[0]).toHaveProperty('balance.unlocked', 5n);
});
});
Loading
Loading