@avalabs/avalanchejs
Version:
Avalanche Platform JS Library
216 lines (178 loc) • 6.59 kB
text/typescript
import { testContext } from '../../../../fixtures/context';
import { describe, test, expect, it } from 'vitest';
import {
getUsableUTXOsFilter,
useSpendableLockedUTXOs,
} from './useSpendableLockedUTXOs';
import { getInitialReducerState, getSpendHelper } from './fixtures/reducers';
import {
getLockedUTXO,
getStakeableLockoutOutput,
testAvaxAssetID,
testOwnerXAddress,
testUTXOID1,
testUTXOID2,
} from '../../../../fixtures/transactions';
import {
Address,
BigIntPr,
Id,
Int,
TransferableOutput,
} from '../../../../serializable';
import { Utxo } from '../../../../serializable/avax/utxo';
import {
StakeableLockIn,
StakeableLockOut,
} from '../../../../serializable/pvm';
import { IncorrectStakeableLockOutError } from './errors';
import { hexToBuffer } from '../../../../utils';
import { UTXOID } from '../../../../serializable/avax';
import { NoSigMatchError } from '../../../utils/calculateSpend/utils';
describe('useSpendableLockedUTXOs', () => {
describe('getUsableUTXOsFilter', () => {
test('returns `false` if UTXO output not a stakeable lockout', () => {
expect(
getUsableUTXOsFilter(getInitialReducerState())(getLockedUTXO()),
).toBe(false);
});
test('returns `false` if UTXO output is a stakeable lockout but locktime is greater than minIssuanceTime', () => {
const state = getInitialReducerState({
minIssuanceTime: 100n,
});
const utxo = getStakeableLockoutOutput(testUTXOID1, 50n, 200n);
expect(getUsableUTXOsFilter(state)(utxo)).toBe(false);
});
test('returns `false` if UTXO output is a stakeable lockout with valid locktime but not used in toStake', () => {
const state = getInitialReducerState({
minIssuanceTime: 300n,
});
const utxo = getStakeableLockoutOutput(testUTXOID1, 50n, 100n);
expect(getUsableUTXOsFilter(state)(utxo)).toBe(false);
});
test('returns `true` if UTXO output is a stakeable lockout with valid locktime and used in toStake', () => {
const testAssetId = Id.fromString('testasset');
const state = getInitialReducerState({
minIssuanceTime: 100n,
toStake: new Map([[testAssetId.toString(), 100n]]),
});
const utxo = getStakeableLockoutOutput(
testUTXOID1,
50n,
300n,
testAssetId,
);
expect(getUsableUTXOsFilter(state)(utxo)).toBe(true);
});
test('throws an error if UTXO output is a StakeableLockOut and the transferOut is not a TransferOutput', () => {
const state = getInitialReducerState({
minIssuanceTime: 100n,
});
const invalidUTXO = new Utxo(
new UTXOID(testUTXOID2, new Int(0)),
testAvaxAssetID,
new StakeableLockOut(
new BigIntPr(300n),
new StakeableLockIn(
new BigIntPr(2000000000n),
TransferableOutput.fromNative(testAvaxAssetID.toString(), 20n, [
hexToBuffer('0x12345678901234578901234567890123457890'),
]),
),
),
);
expect(() => getUsableUTXOsFilter(state)(invalidUTXO)).toThrow(
IncorrectStakeableLockOutError,
);
});
});
it('should ignore UTXOs that signatures do not match', () => {
const toBurn = new Map([[testContext.avaxAssetID, 4_900n]]);
const toStake = new Map([[testContext.avaxAssetID, 4_900n]]);
const initialState = getInitialReducerState({
fromAddresses: [
Address.fromString('P-fuji1y50xa9363pn3d5gjhcz3ltp3fj6vq8x8a5txxg'),
],
excessAVAX: 0n,
minIssuanceTime: 100n,
toBurn,
toStake,
utxos: [getStakeableLockoutOutput(testUTXOID1, 10_000n, 300n)],
});
const spendHelper = getSpendHelper({ toBurn, toStake });
expect(() =>
useSpendableLockedUTXOs(initialState, spendHelper, testContext),
).toThrow(NoSigMatchError);
});
it('should do nothing if UTXO has no remaining amount to stake', () => {
const toBurn = new Map();
const toStake = new Map();
const initialState = getInitialReducerState({
excessAVAX: 0n,
minIssuanceTime: 100n,
toBurn,
toStake,
utxos: [getStakeableLockoutOutput(testUTXOID1, 10_000n, 300n)],
});
const spendHelper = getSpendHelper({ toBurn, toStake });
const state = useSpendableLockedUTXOs(
initialState,
spendHelper,
testContext,
);
const { changeOutputs, inputs, inputUTXOs, stakeOutputs } =
spendHelper.getInputsOutputs();
expect(state).toEqual(initialState);
expect(changeOutputs).toHaveLength(0);
expect(inputs).toHaveLength(0);
expect(inputUTXOs).toHaveLength(0);
expect(stakeOutputs).toHaveLength(0);
});
it('should add spendable locked UTXO with change', () => {
const toBurn = new Map();
const toStake = new Map([[testAvaxAssetID.toString(), 1_000n]]);
const initialState = getInitialReducerState({
fromAddresses: [testOwnerXAddress],
excessAVAX: 0n,
minIssuanceTime: 100n,
toBurn,
toStake,
utxos: [
getStakeableLockoutOutput(testUTXOID1, 10_000n, 300n, testAvaxAssetID),
],
});
const spendHelper = getSpendHelper({ toBurn, toStake });
useSpendableLockedUTXOs(initialState, spendHelper, testContext);
const { changeOutputs, inputs, inputUTXOs, stakeOutputs } =
spendHelper.getInputsOutputs();
expect(inputs).toHaveLength(1);
expect(inputUTXOs).toHaveLength(1);
expect(changeOutputs).toHaveLength(1);
expect(stakeOutputs).toHaveLength(1);
expect(stakeOutputs[0].amount()).toEqual(1_000n);
expect(changeOutputs[0].amount()).toEqual(9_000n);
});
it('should add spendable locked UTXO without change', () => {
const toBurn = new Map();
const toStake = new Map([[testAvaxAssetID.toString(), 1_000n]]);
const initialState = getInitialReducerState({
fromAddresses: [testOwnerXAddress],
excessAVAX: 0n,
minIssuanceTime: 100n,
toBurn,
toStake,
utxos: [
getStakeableLockoutOutput(testUTXOID1, 1_000n, 300n, testAvaxAssetID),
],
});
const spendHelper = getSpendHelper({ toBurn, toStake });
useSpendableLockedUTXOs(initialState, spendHelper, testContext);
const { changeOutputs, inputs, inputUTXOs, stakeOutputs } =
spendHelper.getInputsOutputs();
expect(inputs).toHaveLength(1);
expect(inputUTXOs).toHaveLength(1);
expect(changeOutputs).toHaveLength(0);
expect(stakeOutputs).toHaveLength(1);
expect(stakeOutputs[0].amount()).toEqual(1_000n);
});
});