@avalabs/avalanchejs
Version:
Avalanche Platform JS Library
1,273 lines (1,108 loc) • 38 kB
text/typescript
import { testContext } from '../../../fixtures/context';
import { describe, test, expect, it } from 'vitest';
import {
getLockedUTXO,
getNotTransferOutput,
getTransferableInputForTest,
getTransferableOutForTest,
getValidUtxo,
testAvaxAssetID,
testGenesisData,
testOwnerXAddress,
testSubnetId,
testUtxos,
testVMId,
} from '../../../fixtures/transactions';
import { expectTxs } from '../../../fixtures/utils/expectTx';
import {
BigIntPr,
BlsSignature,
Bytes,
Id,
Input,
Int,
NodeId,
OutputOwners,
Stringpr,
TransferableInput,
TransferableOutput,
} from '../../../serializable';
import {
AddSubnetValidatorTx,
SubnetValidator,
type BaseTx as PVMBaseTx,
RemoveSubnetValidatorTx,
ImportTx,
ExportTx,
CreateSubnetTx,
CreateChainTx,
AddPermissionlessValidatorTx,
Signer,
TransferSubnetOwnershipTx,
AddPermissionlessDelegatorTx,
ConvertSubnetToL1Tx,
ProofOfPossession,
IncreaseL1ValidatorBalanceTx,
DisableL1ValidatorTx,
SetL1ValidatorWeightTx,
RegisterL1ValidatorTx,
} from '../../../serializable/pvm';
import { BaseTx as AvaxBaseTx } from '../../../serializable/avax';
import { hexToBuffer } from '../../../utils';
import {
newAddPermissionlessDelegatorTx,
newAddPermissionlessValidatorTx,
newAddSubnetValidatorTx,
newBaseTx,
newConvertSubnetToL1Tx,
newCreateChainTx,
newCreateSubnetTx,
newDisableL1ValidatorTx,
newExportTx,
newImportTx,
newIncreaseL1ValidatorBalanceTx,
newRegisterL1ValidatorTx,
newRemoveSubnetValidatorTx,
newSetL1ValidatorWeightTx,
newTransferSubnetOwnershipTx,
} from './builder';
import { testAddress1 } from '../../../fixtures/vms';
import { AvaxToNAvax } from '../../../utils/avaxToNAvax';
import { PrimaryNetworkID } from '../../../constants/networkIDs';
import {
blsPublicKeyBytes,
blsSignatureBytes,
warpMessageBytes,
} from '../../../fixtures/primitives';
import {
feeState as testFeeState,
proofOfPossession,
} from '../../../fixtures/pvm';
import { L1Validator } from '../../../serializable/fxs/pvm/L1Validator';
import { PChainOwner } from '../../../serializable/fxs/pvm/pChainOwner';
import { checkFeeIsCorrect } from './utils/feeForTesting';
describe('./src/vms/pvm/etna-builder/builder.test.ts', () => {
const nodeId = 'NodeID-2m38qc95mhHXtrhjyGbe7r2NhniqHHJRB';
const toAddress = hexToBuffer('0x5432112345123451234512');
const feeState = testFeeState();
const fromAddressesBytes = [testOwnerXAddress.toBytes()];
const getRewardsOwners = () => OutputOwners.fromNative([toAddress]);
describe.each([
{
name: 'no memo',
memo: undefined,
},
{
name: 'with memo',
memo: Buffer.from('memo'),
},
])('$name', ({ memo }) => {
test('newBaseTx', () => {
const utxos = testUtxos();
const transferableOutput = TransferableOutput.fromNative(
testAvaxAssetID.toString(),
1_000_000_000n,
[toAddress],
);
const utx = newBaseTx(
{
fromAddressesBytes,
feeState,
outputs: [transferableOutput],
memo,
utxos,
},
testContext,
);
const { baseTx } = utx.getTx() as PVMBaseTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(inputs.length).toEqual(1);
expect(outputs.length).toEqual(2);
expect(outputs).toContain(transferableOutput);
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed] = checkFeeIsCorrect({
unsignedTx: utx,
inputs,
outputs,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
});
test('newImportTx', () => {
const VALID_AMOUNT = BigInt(50 * 1e9);
const utxos = [
getLockedUTXO(),
getNotTransferOutput(),
getValidUtxo(new BigIntPr(VALID_AMOUNT)),
];
const unsignedTx = newImportTx(
{
fromAddressesBytes,
feeState,
memo,
sourceChainId: testContext.cBlockchainID,
toAddressesBytes: [testAddress1],
utxos,
},
testContext,
);
const { baseTx, ins: importedIns } = unsignedTx.getTx() as ImportTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
additionalInputs: importedIns,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new ImportTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[
TransferableOutput.fromNative(
testContext.avaxAssetID,
VALID_AMOUNT - expectedFee,
[testAddress1],
),
],
[],
memo ?? new Uint8Array(),
),
Id.fromString(testContext.cBlockchainID),
[TransferableInput.fromUtxoAndSigindicies(utxos[2], [0])],
);
expectTxs(unsignedTx.getTx(), expectedTx);
// Ensure that the unsigned tx utxos are the filtered utxos,
// and not the inputUtxos registered in the spend helper.
// This is only relevant for the ImportTx.
expect(unsignedTx.utxos).toHaveLength(1);
expect(unsignedTx.utxos).not.toContain(utxos[0]);
expect(unsignedTx.utxos).not.toContain(utxos[1]);
});
test('newExportTx', () => {
const VALID_AMOUNT = BigInt(50 * 1e9);
const OUT_AMOUNT = BigInt(5 * 1e9);
const utxos = [
getLockedUTXO(),
getNotTransferOutput(),
getValidUtxo(new BigIntPr(VALID_AMOUNT)),
];
const tnsOut = TransferableOutput.fromNative(
testContext.avaxAssetID,
OUT_AMOUNT,
[toAddress],
);
const unsignedTx = newExportTx(
{
destinationChainId: testContext.cBlockchainID,
feeState,
fromAddressesBytes,
memo,
outputs: [tnsOut],
utxos,
},
testContext,
);
const { baseTx, outs: exportedOuts } = unsignedTx.getTx() as ExportTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
additionalOutputs: exportedOuts,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new ExportTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[
TransferableOutput.fromNative(
testContext.avaxAssetID,
VALID_AMOUNT - OUT_AMOUNT - expectedFee,
fromAddressesBytes,
),
],
[getTransferableInputForTest()],
memo ?? new Uint8Array(),
),
Id.fromString(testContext.cBlockchainID),
[tnsOut],
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
test('newCreateSubnetTx', () => {
const utxoInputAmt = BigInt(2 * 1e9);
const unsignedTx = newCreateSubnetTx(
{
fromAddressesBytes,
feeState,
memo,
subnetOwners: [toAddress],
utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))],
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as PVMBaseTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState });
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new CreateSubnetTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
memo ?? new Uint8Array(),
),
getRewardsOwners(),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
test('newCreateChainTx', () => {
const utxoInputAmt = BigInt(2 * 1e9);
const unsignedTx = newCreateChainTx(
{
chainName: 'Random Chain Name',
feeState,
fromAddressesBytes,
fxIds: [],
genesisData: testGenesisData,
memo,
subnetAuth: [0],
subnetId: Id.fromHex(testSubnetId).toString(),
utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))],
vmId: Id.fromHex(testVMId).toString(),
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as PVMBaseTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState });
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new CreateChainTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
memo ?? new Uint8Array(),
),
Id.fromHex(testSubnetId),
new Stringpr('Random Chain Name'),
Id.fromHex(testVMId),
[],
new Bytes(new TextEncoder().encode(JSON.stringify(testGenesisData))),
Input.fromNative([0]),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
test('newAddSubnetValidatorTx', () => {
const utxoInputAmt = BigInt(2 * 1e9);
const unsignedTx = newAddSubnetValidatorTx(
{
end: 190_000_000n,
feeState,
fromAddressesBytes,
nodeId,
memo,
subnetAuth: [0],
subnetId: Id.fromHex(testSubnetId).toString(),
start: 100n,
utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))],
weight: 1_800_000n,
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as PVMBaseTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState });
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new AddSubnetValidatorTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
memo ?? new Uint8Array(),
),
SubnetValidator.fromNative(
nodeId,
100n,
190_000_000n,
1_800_000n,
Id.fromHex(testSubnetId),
),
Input.fromNative([0]),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
test('newRemoveSubnetValidatorTx', () => {
const utxoInputAmt = BigInt(2 * 1e9);
const unsignedTx = newRemoveSubnetValidatorTx(
{
fromAddressesBytes,
feeState,
nodeId,
memo,
subnetAuth: [0],
subnetId: Id.fromHex(testSubnetId).toString(),
utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))],
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as PVMBaseTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState });
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new RemoveSubnetValidatorTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
memo ?? new Uint8Array(),
),
NodeId.fromString(nodeId),
Id.fromHex(testSubnetId),
Input.fromNative([0]),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
test('newAddPermissionlessValidatorTx - primary network', () => {
const utxoInputAmt = AvaxToNAvax(2);
const stakeAmount = 1_800_000n;
const unsignedTx = newAddPermissionlessValidatorTx(
{
delegatorRewardsOwner: [],
end: 120n,
feeState,
fromAddressesBytes,
nodeId,
memo,
publicKey: blsPublicKeyBytes(),
rewardAddresses: [],
shares: 1,
signature: blsSignatureBytes(),
start: 0n,
subnetId: PrimaryNetworkID.toString(),
utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))],
weight: stakeAmount,
},
testContext,
);
const { baseTx, stake } =
unsignedTx.getTx() as AddPermissionlessValidatorTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
additionalOutputs: stake,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new AddPermissionlessValidatorTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - stakeAmount - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
memo ?? new Uint8Array(),
),
SubnetValidator.fromNative(
NodeId.fromString(nodeId).toString(),
0n,
120n,
stakeAmount,
PrimaryNetworkID,
),
new Signer(proofOfPossession()),
[getTransferableOutForTest(stakeAmount)], //stake
OutputOwners.fromNative([], 0n, 1),
OutputOwners.fromNative([], 0n, 1),
new Int(1),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
test('newAddPermissionlessValidatorTx - subnet', () => {
const utxoInputAmt = AvaxToNAvax(2);
const stakeAmount = 1_800_000n;
const unsignedTx = newAddPermissionlessValidatorTx(
{
delegatorRewardsOwner: [],
end: 120n,
feeState,
fromAddressesBytes,
nodeId,
memo,
publicKey: blsPublicKeyBytes(),
rewardAddresses: [],
shares: 1,
signature: blsSignatureBytes(),
start: 0n,
subnetId: Id.fromHex(testSubnetId).toString(),
utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))],
weight: stakeAmount,
},
testContext,
);
const { baseTx, stake } =
unsignedTx.getTx() as AddPermissionlessValidatorTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
additionalOutputs: stake,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new AddPermissionlessValidatorTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - stakeAmount - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
memo ?? new Uint8Array(),
),
SubnetValidator.fromNative(
NodeId.fromString(nodeId).toString(),
0n,
120n,
stakeAmount,
Id.fromHex(testSubnetId),
),
new Signer(proofOfPossession()),
[getTransferableOutForTest(stakeAmount)], //stake
OutputOwners.fromNative([], 0n, 1),
OutputOwners.fromNative([], 0n, 1),
new Int(1),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
test('newAddPermissionlessValidatorTx - subnet with non avax staking token', () => {
const utxoInputAmt = AvaxToNAvax(2);
const stakingAssetId = Id.fromHex('0102');
const stakeAmount = 1_000_000n;
const unsignedTx = newAddPermissionlessValidatorTx(
{
delegatorRewardsOwner: [],
end: 120n,
feeState,
fromAddressesBytes,
nodeId,
memo,
publicKey: blsPublicKeyBytes(),
rewardAddresses: [],
shares: 1,
signature: blsSignatureBytes(),
stakingAssetId: stakingAssetId.toString(),
start: 0n,
subnetId: Id.fromHex(testSubnetId).toString(),
utxos: [
getValidUtxo(new BigIntPr(utxoInputAmt)),
getValidUtxo(new BigIntPr(2n * stakeAmount), stakingAssetId),
],
weight: stakeAmount,
},
testContext,
);
const { baseTx, stake } =
unsignedTx.getTx() as AddPermissionlessValidatorTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
additionalOutputs: stake,
feeState,
});
expect(stake.length).toEqual(1);
// Expect correct stake out
expect(stake[0].assetId.toString()).toEqual(stakingAssetId.toString());
expect(stake[0].amount()).toEqual(stakeAmount);
// Expect correct change utxos
expect(outputs.length).toEqual(2);
// Stake token change
expect(outputs[0].assetId.toString()).toEqual(stakingAssetId.toString());
expect(outputs[0].amount()).toEqual(stakeAmount);
// AVAX Change
expect(outputs[1].assetId.toString()).toEqual(testContext.avaxAssetID);
expect(outputs[1].amount()).toEqual(utxoInputAmt - expectedFee);
expect(amountConsumed).toEqual(expectedAmountConsumed);
});
test('newAddPermissionlessDelegator - primary network', () => {
const utxoInputAmt = AvaxToNAvax(2);
const stakeAmount = 1_800_000n;
const unsignedTx = newAddPermissionlessDelegatorTx(
{
end: 120n,
feeState,
fromAddressesBytes,
nodeId,
memo,
rewardAddresses: [],
start: 0n,
subnetId: PrimaryNetworkID.toString(),
utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))],
weight: stakeAmount,
},
testContext,
);
const { baseTx, stake } =
unsignedTx.getTx() as AddPermissionlessDelegatorTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
additionalOutputs: stake,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new AddPermissionlessDelegatorTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - stakeAmount - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
memo ?? new Uint8Array(),
),
SubnetValidator.fromNative(
NodeId.fromString(nodeId).toString(),
0n,
120n,
stakeAmount,
PrimaryNetworkID,
),
[getTransferableOutForTest(stakeAmount)], //stake
OutputOwners.fromNative([], 0n, 1),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
test('newAddPermissionlessDelegator - subnet', () => {
const utxoInputAmt = AvaxToNAvax(2);
const stakeAmount = 1_800_000n;
const unsignedTx = newAddPermissionlessDelegatorTx(
{
end: 120n,
feeState,
fromAddressesBytes,
nodeId,
memo,
rewardAddresses: [],
start: 0n,
subnetId: Id.fromHex(testSubnetId).toString(),
utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))],
weight: stakeAmount,
},
testContext,
);
const { baseTx, stake } =
unsignedTx.getTx() as AddPermissionlessDelegatorTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
additionalOutputs: stake,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new AddPermissionlessDelegatorTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - stakeAmount - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
memo ?? new Uint8Array(),
),
SubnetValidator.fromNative(
NodeId.fromString(nodeId).toString(),
0n,
120n,
stakeAmount,
Id.fromHex(testSubnetId),
),
[getTransferableOutForTest(stakeAmount)], //stake
OutputOwners.fromNative([], 0n, 1),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
test('newAddPermissionlessDelegator - subnet with non avax staking token', () => {
const utxoInputAmt = AvaxToNAvax(2);
const stakingAssetId = Id.fromHex('0102');
const stakeAmount = 1_000_000n;
const unsignedTx = newAddPermissionlessDelegatorTx(
{
end: 120n,
feeState,
fromAddressesBytes,
nodeId,
memo,
rewardAddresses: [],
stakingAssetId: stakingAssetId.toString(),
start: 0n,
subnetId: Id.fromHex(testSubnetId).toString(),
utxos: [
getValidUtxo(new BigIntPr(utxoInputAmt)),
getValidUtxo(new BigIntPr(2n * stakeAmount), stakingAssetId),
],
weight: stakeAmount,
},
testContext,
);
const { baseTx, stake } =
unsignedTx.getTx() as AddPermissionlessDelegatorTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
additionalOutputs: stake,
feeState,
});
expect(stake.length).toEqual(1);
// Expect correct stake out
expect(stake[0].assetId.toString()).toEqual(stakingAssetId.toString());
expect(stake[0].amount()).toEqual(stakeAmount);
// Expect correct change utxos
expect(outputs.length).toEqual(2);
// Stake token change
expect(outputs[0].assetId.toString()).toEqual(stakingAssetId.toString());
expect(outputs[0].amount()).toEqual(stakeAmount);
// AVAX Change
expect(outputs[1].assetId.toString()).toEqual(testContext.avaxAssetID);
expect(outputs[1].amount()).toEqual(utxoInputAmt - expectedFee);
expect(amountConsumed).toEqual(expectedAmountConsumed);
});
test('newTransferSubnetOwnershipTx', () => {
const utxoInputAmt = BigInt(2 * 1e9);
const subnetAuth = [0, 1];
const unsignedTx = newTransferSubnetOwnershipTx(
{
fromAddressesBytes,
feeState,
memo,
subnetAuth,
subnetId: Id.fromHex(testSubnetId).toString(),
subnetOwners: [toAddress],
utxos: [getValidUtxo(new BigIntPr(utxoInputAmt))],
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as PVMBaseTx;
const { inputs, outputs, memo: txMemo } = baseTx;
expect(txMemo.toString()).toEqual(memo ? 'memo' : '');
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({ unsignedTx, inputs, outputs, feeState });
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new TransferSubnetOwnershipTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
memo ?? new Uint8Array(),
),
Id.fromHex(testSubnetId),
Input.fromNative(subnetAuth),
getRewardsOwners(),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
});
describe('ImportTx', () => {
it('should create an ImportTx with only AVAX and not non-AVAX assets', () => {
const utxos = [
getLockedUTXO(), // Locked and should be ignored.
getNotTransferOutput(), // Invalid and should be ignored.
// AVAX Assets
getValidUtxo(new BigIntPr(BigInt(35 * 1e9)), testAvaxAssetID),
getValidUtxo(new BigIntPr(BigInt(28 * 1e9)), testAvaxAssetID),
// Non-AVAX Assets (Jupiter)
getValidUtxo(new BigIntPr(BigInt(15 * 1e9)), Id.fromString('jupiter')),
getValidUtxo(new BigIntPr(BigInt(11 * 1e9)), Id.fromString('jupiter')),
// Non-AVAX Asset (Mars)
getValidUtxo(new BigIntPr(BigInt(9 * 1e9)), Id.fromString('mars')),
];
const unsignedTx = newImportTx(
{
fromAddressesBytes,
feeState,
sourceChainId: testContext.cBlockchainID,
toAddressesBytes: [testAddress1],
utxos,
},
testContext,
);
const { baseTx, ins: importedIns } = unsignedTx.getTx() as ImportTx;
const { inputs, outputs } = baseTx;
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
additionalInputs: importedIns,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new ImportTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[
// Only AVAX asset here.
// _If_ we did p-chain did support other assets, they would come first,
// sorted by TransferableInput.compare.
TransferableOutput.fromNative(
testContext.avaxAssetID,
BigInt((35 + 28) * 1e9) - expectedFee,
[testAddress1],
),
],
[],
new Uint8Array(),
),
Id.fromString(testContext.cBlockchainID),
[
TransferableInput.fromUtxoAndSigindicies(utxos[2], [0]),
TransferableInput.fromUtxoAndSigindicies(utxos[3], [0]),
],
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
});
describe('ConvertSubnetToL1Tx', () => {
it('should create an ConvertSubnetToL1Tx', () => {
const utxoInputAmt = BigInt(50 * 1e9);
const utxos = testUtxos();
const signer = new ProofOfPossession(
blsPublicKeyBytes(),
blsSignatureBytes(),
);
const pChainOwner = PChainOwner.fromNative([testAddress1], 1);
const validatorBalanceAmount = BigInt(1 * 1e9);
const validator = L1Validator.fromNative(
nodeId,
BigInt(1 * 1e9),
validatorBalanceAmount,
signer,
pChainOwner,
pChainOwner,
);
const unsignedTx = newConvertSubnetToL1Tx(
{
fromAddressesBytes,
feeState,
utxos,
subnetAuth: [0],
subnetId: Id.fromHex(testSubnetId).toString(),
address: testAddress1,
validators: [validator],
chainId: 'h5vH4Zz53MTN2jf72axZCfo1VbG1cMR6giR4Ra2TTpEmqxDWB',
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as ConvertSubnetToL1Tx;
const { inputs, outputs } = baseTx;
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
feeState,
additionalFee: validatorBalanceAmount,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new ConvertSubnetToL1Tx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(utxoInputAmt - expectedFee)],
[getTransferableInputForTest(utxoInputAmt)],
new Uint8Array(),
),
Id.fromHex(testSubnetId),
Id.fromString('h5vH4Zz53MTN2jf72axZCfo1VbG1cMR6giR4Ra2TTpEmqxDWB'),
new Bytes(testAddress1),
[validator],
Input.fromNative([0]),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
it('should throw error if weight on a validator is 0', () => {
const validator = L1Validator.fromNative(
nodeId,
BigInt(0 * 1e9),
BigInt(0 * 1e9),
new ProofOfPossession(blsPublicKeyBytes(), blsSignatureBytes()),
PChainOwner.fromNative([testAddress1], 1),
PChainOwner.fromNative([testAddress1], 1),
);
const utxos = testUtxos();
try {
newConvertSubnetToL1Tx(
{
fromAddressesBytes,
feeState,
utxos,
subnetAuth: [0],
subnetId: Id.fromHex(testSubnetId).toString(),
address: testAddress1,
validators: [validator],
chainId: 'h5vH4Zz53MTN2jf72axZCfo1VbG1cMR6giR4Ra2TTpEmqxDWB',
},
testContext,
);
} catch (error) {
expect((error as Error).message).toEqual(
'Validator weight must be greater than 0',
);
}
});
});
describe('RegisterL1ValidatorTx', () => {
it('should create a RegisterL1ValidatorTx', () => {
const balance = BigInt(10 * 1e9);
const signatureBytes = blsSignatureBytes();
const message = warpMessageBytes();
const validUtxoAmount = BigInt(30 * 1e9);
const utxos = [
getValidUtxo(new BigIntPr(validUtxoAmount), testAvaxAssetID),
];
const unsignedTx = newRegisterL1ValidatorTx(
{
balance,
blsSignature: signatureBytes,
fromAddressesBytes,
feeState,
message,
utxos,
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as SetL1ValidatorWeightTx;
const { inputs, outputs } = baseTx;
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
feeState,
additionalFee: balance,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new RegisterL1ValidatorTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(validUtxoAmount - expectedFee)],
[getTransferableInputForTest(validUtxoAmount)],
new Uint8Array(),
),
new BigIntPr(balance),
BlsSignature.fromSignatureBytes(signatureBytes),
new Bytes(message),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
});
describe('SetL1ValidatorWeightTx', () => {
it('should create a SetL1ValidatorWeightTx', () => {
// Example Warp Message
const message = warpMessageBytes();
const validUtxoAmount = BigInt(30 * 1e9);
const utxos = [
getValidUtxo(new BigIntPr(validUtxoAmount), testAvaxAssetID),
];
const unsignedTx = newSetL1ValidatorWeightTx(
{
fromAddressesBytes,
feeState,
message,
utxos,
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as SetL1ValidatorWeightTx;
const { inputs, outputs } = baseTx;
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new SetL1ValidatorWeightTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(validUtxoAmount - expectedFee)],
[getTransferableInputForTest(validUtxoAmount)],
new Uint8Array(),
),
new Bytes(message),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
});
describe('IncreaseL1ValidatorBalanceTx', () => {
it('should create an IncreaseL1ValidatorBalanceTx', () => {
const validUtxoAmount = BigInt(30 * 1e9);
const balance = BigInt(1 * 1e9);
const validationId = 'test';
const utxos = [
getLockedUTXO(), // Locked and should be ignored.
getNotTransferOutput(), // Invalid and should be ignored.
// AVAX Assets
getValidUtxo(new BigIntPr(validUtxoAmount), testAvaxAssetID),
// Non-AVAX Assets (Jupiter)
getValidUtxo(new BigIntPr(BigInt(15 * 1e9)), Id.fromString('jupiter')),
getValidUtxo(new BigIntPr(BigInt(11 * 1e9)), Id.fromString('jupiter')),
// Non-AVAX Asset (Mars)
getValidUtxo(new BigIntPr(BigInt(9 * 1e9)), Id.fromString('mars')),
];
const unsignedTx = newIncreaseL1ValidatorBalanceTx(
{
balance,
fromAddressesBytes,
feeState,
utxos,
validationId,
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as IncreaseL1ValidatorBalanceTx;
const { inputs, outputs } = baseTx;
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
feeState,
additionalFee: balance,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new IncreaseL1ValidatorBalanceTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(validUtxoAmount - expectedFee)],
[getTransferableInputForTest(validUtxoAmount)],
new Uint8Array(),
),
Id.fromString(validationId),
new BigIntPr(balance),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
});
it('should throw an error if the balance is less than or equal to 0', () => {
const validationId = 'test';
const utxos = testUtxos();
expect(() => {
newIncreaseL1ValidatorBalanceTx(
{
balance: 0n,
fromAddressesBytes,
feeState,
utxos,
validationId,
},
testContext,
);
}).toThrow('Balance must be greater than 0');
expect(() => {
newIncreaseL1ValidatorBalanceTx(
{
balance: -1n,
fromAddressesBytes,
feeState,
utxos,
validationId,
},
testContext,
);
}).toThrow('Balance must be greater than 0');
});
describe('DisableL1ValidatorTx', () => {
it('should create a DisabledSubnetValidatorTx', () => {
const validUtxoAmount = BigInt(30 * 1e9);
const validationId = 'test';
const utxos = [
getLockedUTXO(), // Locked and should be ignored.
getNotTransferOutput(), // Invalid and should be ignored.
// AVAX Assets
getValidUtxo(new BigIntPr(validUtxoAmount), testAvaxAssetID),
// Non-AVAX Assets (Jupiter)
getValidUtxo(new BigIntPr(BigInt(15 * 1e9)), Id.fromString('jupiter')),
getValidUtxo(new BigIntPr(BigInt(11 * 1e9)), Id.fromString('jupiter')),
// Non-AVAX Asset (Mars)
getValidUtxo(new BigIntPr(BigInt(9 * 1e9)), Id.fromString('mars')),
];
const unsignedTx = newDisableL1ValidatorTx(
{
disableAuth: [0],
fromAddressesBytes,
feeState,
utxos,
validationId,
},
testContext,
);
const { baseTx } = unsignedTx.getTx() as DisableL1ValidatorTx;
const { inputs, outputs } = baseTx;
const [amountConsumed, expectedAmountConsumed, expectedFee] =
checkFeeIsCorrect({
unsignedTx,
inputs,
outputs,
feeState,
});
expect(amountConsumed).toEqual(expectedAmountConsumed);
const expectedTx = new DisableL1ValidatorTx(
AvaxBaseTx.fromNative(
testContext.networkID,
testContext.pBlockchainID,
[getTransferableOutForTest(validUtxoAmount - expectedFee)],
[getTransferableInputForTest(validUtxoAmount)],
new Uint8Array(),
),
Id.fromString(validationId),
Input.fromNative([0]),
);
expectTxs(unsignedTx.getTx(), expectedTx);
});
});
});