Skip to content

Commit

Permalink
feat: add validateAvaxBurnedAmountEtna
Browse files Browse the repository at this point in the history
  • Loading branch information
ruijialin-avalabs committed Oct 9, 2024
1 parent 47f216e commit 876c744
Show file tree
Hide file tree
Showing 11 changed files with 710 additions and 305 deletions.
9 changes: 4 additions & 5 deletions examples/p-chain/etna/utils/etna-context.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Context, Info } from '../../../../src';
import { isEtnaEnabled } from '../../../../src/utils';
/**
* Gets the context from URI and then modifies the context
* to be used for testing example Etna transactions until Etna is enabled.
Expand All @@ -10,12 +11,10 @@ export const getEtnaContextFromURI = async (

const info = new Info(uri);

const { etnaTime } = await info.getUpgradesInfo();
const upgradesInfo = await info.getUpgradesInfo();
const enabled = isEtnaEnabled(upgradesInfo);

const etnaDateTime = new Date(etnaTime);
const now = new Date();

if (etnaDateTime < now) {
if (enabled) {
return context;
}

Expand Down
16 changes: 16 additions & 0 deletions src/fixtures/info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const upgradesInfo = {
apricotPhaselTime: '2020-12-05T05:00:00Z',
apricotPhase2Time: '2020-12-05T05:00:00Z',
apricotPhase3Time: '2020-12-05T05:00:00Z',
apricotPhase4Time: '2020-12-05T05:00:00Z',
apricotPhase4MinPChainHeight: 0,
apricotPhase5Time: '2020-12-05T05:00:00Z',
apricotPhasePre6Time: '2020-12-05T05:00:00Z',
apricotPhase6Time: '2020-12-05T05:00:00Z',
apricotPhasePost6Time: '2020-12-05T05:00:00Z',
banffTime: '2020-12-05T05:00:00Z',
cortinaTime: '2020-12-05T05:00:00Z',
cortinaXChainStopVertexID: '11111111111111111111111111111111LpoYY',
durangoTime: '2020-12-05T05:00:00Z',
etnaTime: '2020-12-05T05:00:00Z',
};
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export * from './getTransferableOutputsByTx';
export * from './getUtxoInfo';
export * from './getBurnedAmountByTx';
export * from './validateBurnedAmount';
export * from './isEtnaEnabled';
export { unpackWithManager, getManagerForVM, packTx } from './packTx';
8 changes: 8 additions & 0 deletions src/utils/isEtnaEnabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { GetUpgradesInfoResponse } from '../info/model';

export const isEtnaEnabled = (
upgradesInfo: GetUpgradesInfoResponse,
): boolean => {
const { etnaTime } = upgradesInfo;
return new Date(etnaTime) < new Date();
};
265 changes: 265 additions & 0 deletions src/utils/validateAvaxBurnedAmountEtna.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import { testAddress1, testAddress2 } from '../fixtures/vms';
import { testContext } from '../fixtures/context';
import { Utxo } from '../serializable/avax/utxo';
import { utxoId } from '../fixtures/avax';
import { Address, Id } from '../serializable/fxs/common';
import { OutputOwners, TransferOutput } from '../serializable/fxs/secp256k1';
import { BigIntPr, Int } from '../serializable/primitives';
import {
newBaseTx as avmBaseTx,
newExportTx as avmExportTx,
newImportTx as avmImportTx,
} from '../vms/avm';
import {
newBaseTx as pvmBaseTx,
newExportTx as pvmExportTx,
newImportTx as pvmImportTx,
newCreateSubnetTx,
newCreateBlockchainTx,
newAddSubnetValidatorTx,
newAddPermissionlessValidatorTx,
newAddPermissionlessDelegatorTx,
newRemoveSubnetValidatorTx,
newTransferSubnetOwnershipTx,
} from '../vms/pvm';
import { TransferableOutput } from '../serializable';
import { nodeId } from '../fixtures/common';
import { feeState as testFeeState } from '../fixtures/pvm';
import { testSubnetId } from '../fixtures/transactions';
import { blsPublicKeyBytes, blsSignatureBytes } from '../fixtures/primitives';
import { validateAvaxBurnedAmountEtna } from './validateAvaxBurnedAmountEtna';

const incorrectBurnedAmount = 1n;
const correctBurnedAmount = 1000000n;

const utxoMock = new Utxo(
utxoId(),
Id.fromString(testContext.avaxAssetID),
new TransferOutput(
new BigIntPr(1000000000000n),
new OutputOwners(new BigIntPr(0n), new Int(1), [
Address.fromBytes(testAddress1)[0],
]),
),
);

const outputMock = new TransferableOutput(
Id.fromString(testContext.avaxAssetID),
new TransferOutput(
new BigIntPr(100000000n),
new OutputOwners(new BigIntPr(0n), new Int(1), [
Address.fromBytes(testAddress2)[0],
]),
),
);

describe('validateAvaxBurnedAmountEtna', () => {
describe('unsupported tx types post-enta', () => {
const unsupportedTestData = [
{
name: 'base tx on X',
unsignedTx: avmBaseTx(
testContext,
[testAddress1],
[utxoMock],
[outputMock],
),
},
{
name: 'export from X',
unsignedTx: avmExportTx(
testContext,
'P',
[testAddress1],
[utxoMock],
[outputMock],
),
},
{
name: 'import from X',
unsignedTx: avmImportTx(
testContext,
'P',
[utxoMock],
[testAddress2],
[testAddress1],
),
},
];
describe.each(unsupportedTestData)('$name', ({ unsignedTx }) => {
it('throws an error if tx type is not supported', () => {
try {
validateAvaxBurnedAmountEtna({
unsignedTx,
context: testContext,
burnedAmount: correctBurnedAmount,
feeState: testFeeState(),
});
} catch (error) {
expect((error as Error).message).toEqual(
'Unsupported transaction type.',
);
}
});
});
});

const testData = [
{
name: 'base tx on P',
unsignedTx: pvmBaseTx(
testContext,
[testAddress1],
[utxoMock],
[outputMock],
),
},
{
name: 'export from P',
unsignedTx: pvmExportTx(
testContext,
'C',
[testAddress1],
[utxoMock],
[outputMock],
),
},
{
name: 'import to P',
unsignedTx: pvmImportTx(
testContext,
'C',
[utxoMock],
[testAddress2],
[testAddress1],
),
},
{
name: 'create subnet',
unsignedTx: newCreateSubnetTx(
testContext,
[utxoMock],
[testAddress1],
[testAddress1],
),
},
{
name: 'create blockchain',
unsignedTx: newCreateBlockchainTx(
testContext,
[utxoMock],
[testAddress1],
'subnet',
'chain',
'vm',
['fx1', 'fx2'],
{},
[0],
),
},
{
name: 'add subnet validator',
unsignedTx: newAddSubnetValidatorTx(
testContext,
[utxoMock],
[testAddress1],
nodeId().toString(),
0n,
1n,
2n,
'subnet',
[0],
),
},
{
name: 'remove subnet validator',
unsignedTx: newRemoveSubnetValidatorTx(
testContext,
[utxoMock],
[testAddress1],
nodeId().toString(),
Id.fromHex(testSubnetId).toString(),
[0],
),
},
{
name: 'add permissionless validator (subnet)',
unsignedTx: newAddPermissionlessValidatorTx(
testContext,
[utxoMock],
[testAddress1],
nodeId().toString(),
Id.fromHex(testSubnetId).toString(),
0n,
120n,
1800000n,
[],
[],
1,
{},
1,
0n,
blsPublicKeyBytes(),
blsSignatureBytes(),
),
},
{
name: 'add permissionless delegator (subnet)',
unsignedTx: newAddPermissionlessDelegatorTx(
testContext,
[utxoMock],
[testAddress1],
nodeId().toString(),
Id.fromHex(testSubnetId).toString(),
0n,
120n,
1800000n,
[],
{},
1,
0n,
),
},
{
name: 'transfer subnet ownership',
unsignedTx: newTransferSubnetOwnershipTx(
testContext,
[utxoMock],
[testAddress1],
Id.fromHex(testSubnetId).toString(),
[0, 2],
[testAddress2],
),
},
];

describe.each(testData)('$name', ({ unsignedTx }) => {
it('returns true if burned amount is correct', () => {
const result = validateAvaxBurnedAmountEtna({
unsignedTx,
context: testContext,
burnedAmount: correctBurnedAmount,
feeState: testFeeState(),
});

expect(result).toStrictEqual({
isValid: true,
txFee: correctBurnedAmount,
});
});

it('returns false if burned amount is not correct', () => {
const result = validateAvaxBurnedAmountEtna({
unsignedTx,
context: testContext,
burnedAmount: incorrectBurnedAmount,
feeState: { ...testFeeState(), price: 10_000n },
});

expect(result).toStrictEqual({
isValid: false,
txFee: incorrectBurnedAmount,
});
});
});
});
58 changes: 58 additions & 0 deletions src/utils/validateAvaxBurnedAmountEtna.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Context } from '../vms/context/model';
import {
isAddPermissionlessDelegatorTx,
isAddPermissionlessValidatorTx,
isAddSubnetValidatorTx,
isCreateChainTx,
isCreateSubnetTx,
isPvmBaseTx,
isExportTx as isPvmExportTx,
isImportTx as isPvmImportTx,
isRemoveSubnetValidatorTx,
isTransferSubnetOwnershipTx,
} from '../serializable/pvm';
import type { UnsignedTx } from '../vms/common';
import type { FeeState } from '../vms/pvm';
import { calculateFee } from '../vms/pvm/txs/fee/calculator';

export const validateAvaxBurnedAmountEtna = ({
unsignedTx,
context,
burnedAmount,
feeState,
}: {
unsignedTx: UnsignedTx;
context: Context;
burnedAmount: bigint;
feeState: FeeState;
}): { isValid: boolean; txFee: bigint } => {
const tx = unsignedTx.getTx();

const expectedFee = calculateFee(
unsignedTx.getTx(),
context.platformFeeConfig.weights,
feeState.price < context.platformFeeConfig.minPrice
? context.platformFeeConfig.minPrice
: feeState.price,
);

if (
isPvmBaseTx(tx) ||
isPvmExportTx(tx) ||
isPvmImportTx(tx) ||
isAddPermissionlessValidatorTx(tx) ||
isAddPermissionlessDelegatorTx(tx) ||
isAddSubnetValidatorTx(tx) ||
isCreateChainTx(tx) ||
isCreateSubnetTx(tx) ||
isRemoveSubnetValidatorTx(tx) ||
isTransferSubnetOwnershipTx(tx)
) {
return {
isValid: burnedAmount >= expectedFee,
txFee: burnedAmount,
};
}

throw new Error(`tx type is not supported`);
};
Loading

0 comments on commit 876c744

Please sign in to comment.