Skip to content

Commit b685484

Browse files
feat: add validateAvaxBurnedAmountEtna
1 parent 5b7868a commit b685484

10 files changed

+706
-300
lines changed

src/fixtures/info.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export const upgradesInfo = {
2+
apricotPhaselTime: '2020-12-05T05:00:00Z',
3+
apricotPhase2Time: '2020-12-05T05:00:00Z',
4+
apricotPhase3Time: '2020-12-05T05:00:00Z',
5+
apricotPhase4Time: '2020-12-05T05:00:00Z',
6+
apricotPhase4MinPChainHeight: 0,
7+
apricotPhase5Time: '2020-12-05T05:00:00Z',
8+
apricotPhasePre6Time: '2020-12-05T05:00:00Z',
9+
apricotPhase6Time: '2020-12-05T05:00:00Z',
10+
apricotPhasePost6Time: '2020-12-05T05:00:00Z',
11+
banffTime: '2020-12-05T05:00:00Z',
12+
cortinaTime: '2020-12-05T05:00:00Z',
13+
cortinaXChainStopVertexID: '11111111111111111111111111111111LpoYY',
14+
durangoTime: '2020-12-05T05:00:00Z',
15+
etnaTime: '2020-12-05T05:00:00Z',
16+
};

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export * from './getTransferableOutputsByTx';
1414
export * from './getUtxoInfo';
1515
export * from './getBurnedAmountByTx';
1616
export * from './validateBurnedAmount';
17+
export * from './isEtnaEnabled';
1718
export { unpackWithManager, getManagerForVM, packTx } from './packTx';

src/utils/isEtnaEnabled.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { GetUpgradesInfoResponse } from '../info/model';
2+
3+
export const isEtnaEnabled = (
4+
upgradesInfo: GetUpgradesInfoResponse,
5+
): boolean => {
6+
const { etnaTime } = upgradesInfo;
7+
return new Date(etnaTime) < new Date();
8+
};
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import { testAddress1, testAddress2 } from '../fixtures/vms';
2+
import { testContext } from '../fixtures/context';
3+
import { Utxo } from '../serializable/avax/utxo';
4+
import { utxoId } from '../fixtures/avax';
5+
import { Address, Id } from '../serializable/fxs/common';
6+
import { OutputOwners, TransferOutput } from '../serializable/fxs/secp256k1';
7+
import { BigIntPr, Int } from '../serializable/primitives';
8+
import {
9+
newBaseTx as avmBaseTx,
10+
newExportTx as avmExportTx,
11+
newImportTx as avmImportTx,
12+
} from '../vms/avm';
13+
import {
14+
newBaseTx as pvmBaseTx,
15+
newExportTx as pvmExportTx,
16+
newImportTx as pvmImportTx,
17+
newCreateSubnetTx,
18+
newCreateBlockchainTx,
19+
newAddSubnetValidatorTx,
20+
newAddPermissionlessValidatorTx,
21+
newAddPermissionlessDelegatorTx,
22+
newRemoveSubnetValidatorTx,
23+
newTransferSubnetOwnershipTx,
24+
} from '../vms/pvm';
25+
import { TransferableOutput } from '../serializable';
26+
import { nodeId } from '../fixtures/common';
27+
import { feeState as testFeeState } from '../fixtures/pvm';
28+
import { testSubnetId } from '../fixtures/transactions';
29+
import { blsPublicKeyBytes, blsSignatureBytes } from '../fixtures/primitives';
30+
import { validateAvaxBurnedAmountEtna } from './validateAvaxBurnedAmountEtna';
31+
32+
const incorrectBurnedAmount = 1n;
33+
const correctBurnedAmount = 1000000n;
34+
35+
const utxoMock = new Utxo(
36+
utxoId(),
37+
Id.fromString(testContext.avaxAssetID),
38+
new TransferOutput(
39+
new BigIntPr(1000000000000n),
40+
new OutputOwners(new BigIntPr(0n), new Int(1), [
41+
Address.fromBytes(testAddress1)[0],
42+
]),
43+
),
44+
);
45+
46+
const outputMock = new TransferableOutput(
47+
Id.fromString(testContext.avaxAssetID),
48+
new TransferOutput(
49+
new BigIntPr(100000000n),
50+
new OutputOwners(new BigIntPr(0n), new Int(1), [
51+
Address.fromBytes(testAddress2)[0],
52+
]),
53+
),
54+
);
55+
56+
describe('validateAvaxBurnedAmountEtna', () => {
57+
describe('unsupported tx types post-enta', () => {
58+
const unsupportedTestData = [
59+
{
60+
name: 'base tx on X',
61+
unsignedTx: avmBaseTx(
62+
testContext,
63+
[testAddress1],
64+
[utxoMock],
65+
[outputMock],
66+
),
67+
},
68+
{
69+
name: 'export from X',
70+
unsignedTx: avmExportTx(
71+
testContext,
72+
'P',
73+
[testAddress1],
74+
[utxoMock],
75+
[outputMock],
76+
),
77+
},
78+
{
79+
name: 'import from X',
80+
unsignedTx: avmImportTx(
81+
testContext,
82+
'P',
83+
[utxoMock],
84+
[testAddress2],
85+
[testAddress1],
86+
),
87+
},
88+
];
89+
describe.each(unsupportedTestData)('$name', ({ unsignedTx }) => {
90+
it('throws an error if tx type is not supported', () => {
91+
try {
92+
validateAvaxBurnedAmountEtna({
93+
unsignedTx,
94+
context: testContext,
95+
burnedAmount: correctBurnedAmount,
96+
feeState: testFeeState(),
97+
});
98+
} catch (error) {
99+
expect((error as Error).message).toEqual(
100+
'Unsupported transaction type.',
101+
);
102+
}
103+
});
104+
});
105+
});
106+
107+
const testData = [
108+
{
109+
name: 'base tx on P',
110+
unsignedTx: pvmBaseTx(
111+
testContext,
112+
[testAddress1],
113+
[utxoMock],
114+
[outputMock],
115+
),
116+
},
117+
{
118+
name: 'export from P',
119+
unsignedTx: pvmExportTx(
120+
testContext,
121+
'C',
122+
[testAddress1],
123+
[utxoMock],
124+
[outputMock],
125+
),
126+
},
127+
{
128+
name: 'import to P',
129+
unsignedTx: pvmImportTx(
130+
testContext,
131+
'C',
132+
[utxoMock],
133+
[testAddress2],
134+
[testAddress1],
135+
),
136+
},
137+
{
138+
name: 'create subnet',
139+
unsignedTx: newCreateSubnetTx(
140+
testContext,
141+
[utxoMock],
142+
[testAddress1],
143+
[testAddress1],
144+
),
145+
},
146+
{
147+
name: 'create blockchain',
148+
unsignedTx: newCreateBlockchainTx(
149+
testContext,
150+
[utxoMock],
151+
[testAddress1],
152+
'subnet',
153+
'chain',
154+
'vm',
155+
['fx1', 'fx2'],
156+
{},
157+
[0],
158+
),
159+
},
160+
{
161+
name: 'add subnet validator',
162+
unsignedTx: newAddSubnetValidatorTx(
163+
testContext,
164+
[utxoMock],
165+
[testAddress1],
166+
nodeId().toString(),
167+
0n,
168+
1n,
169+
2n,
170+
'subnet',
171+
[0],
172+
),
173+
},
174+
{
175+
name: 'remove subnet validator',
176+
unsignedTx: newRemoveSubnetValidatorTx(
177+
testContext,
178+
[utxoMock],
179+
[testAddress1],
180+
nodeId().toString(),
181+
Id.fromHex(testSubnetId).toString(),
182+
[0],
183+
),
184+
},
185+
{
186+
name: 'add permissionless validator (subnet)',
187+
unsignedTx: newAddPermissionlessValidatorTx(
188+
testContext,
189+
[utxoMock],
190+
[testAddress1],
191+
nodeId().toString(),
192+
Id.fromHex(testSubnetId).toString(),
193+
0n,
194+
120n,
195+
1800000n,
196+
[],
197+
[],
198+
1,
199+
{},
200+
1,
201+
0n,
202+
blsPublicKeyBytes(),
203+
blsSignatureBytes(),
204+
),
205+
},
206+
{
207+
name: 'add permissionless delegator (subnet)',
208+
unsignedTx: newAddPermissionlessDelegatorTx(
209+
testContext,
210+
[utxoMock],
211+
[testAddress1],
212+
nodeId().toString(),
213+
Id.fromHex(testSubnetId).toString(),
214+
0n,
215+
120n,
216+
1800000n,
217+
[],
218+
{},
219+
1,
220+
0n,
221+
),
222+
},
223+
{
224+
name: 'transfer subnet ownership',
225+
unsignedTx: newTransferSubnetOwnershipTx(
226+
testContext,
227+
[utxoMock],
228+
[testAddress1],
229+
Id.fromHex(testSubnetId).toString(),
230+
[0, 2],
231+
[testAddress2],
232+
),
233+
},
234+
];
235+
236+
describe.each(testData)('$name', ({ unsignedTx }) => {
237+
it('returns true if burned amount is correct', () => {
238+
const result = validateAvaxBurnedAmountEtna({
239+
unsignedTx,
240+
context: testContext,
241+
burnedAmount: correctBurnedAmount,
242+
feeState: testFeeState(),
243+
});
244+
245+
expect(result).toStrictEqual({
246+
isValid: true,
247+
txFee: correctBurnedAmount,
248+
});
249+
});
250+
251+
it('returns false if burned amount is not correct', () => {
252+
const result = validateAvaxBurnedAmountEtna({
253+
unsignedTx,
254+
context: testContext,
255+
burnedAmount: incorrectBurnedAmount,
256+
feeState: { ...testFeeState(), price: 10_000n },
257+
});
258+
259+
expect(result).toStrictEqual({
260+
isValid: false,
261+
txFee: incorrectBurnedAmount,
262+
});
263+
});
264+
});
265+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { Context } from '../vms/context/model';
2+
import {
3+
isAddPermissionlessDelegatorTx,
4+
isAddPermissionlessValidatorTx,
5+
isAddSubnetValidatorTx,
6+
isCreateChainTx,
7+
isCreateSubnetTx,
8+
isPvmBaseTx,
9+
isExportTx as isPvmExportTx,
10+
isImportTx as isPvmImportTx,
11+
isRemoveSubnetValidatorTx,
12+
isTransferSubnetOwnershipTx,
13+
} from '../serializable/pvm';
14+
import type { UnsignedTx } from '../vms/common';
15+
import type { FeeState } from '../vms/pvm';
16+
import { calculateFee } from '../vms/pvm/txs/fee/calculator';
17+
18+
export const validateAvaxBurnedAmountEtna = ({
19+
unsignedTx,
20+
context,
21+
burnedAmount,
22+
feeState,
23+
}: {
24+
unsignedTx: UnsignedTx;
25+
context: Context;
26+
burnedAmount: bigint;
27+
feeState: FeeState;
28+
}): { isValid: boolean; txFee: bigint } => {
29+
const tx = unsignedTx.getTx();
30+
31+
const expectedFee = calculateFee(
32+
unsignedTx.getTx(),
33+
context.platformFeeConfig.weights,
34+
feeState.price < context.platformFeeConfig.minPrice
35+
? context.platformFeeConfig.minPrice
36+
: feeState.price,
37+
);
38+
39+
if (
40+
isPvmBaseTx(tx) ||
41+
isPvmExportTx(tx) ||
42+
isPvmImportTx(tx) ||
43+
isAddPermissionlessValidatorTx(tx) ||
44+
isAddPermissionlessDelegatorTx(tx) ||
45+
isAddSubnetValidatorTx(tx) ||
46+
isCreateChainTx(tx) ||
47+
isCreateSubnetTx(tx) ||
48+
isRemoveSubnetValidatorTx(tx) ||
49+
isTransferSubnetOwnershipTx(tx)
50+
) {
51+
return {
52+
isValid: burnedAmount >= expectedFee,
53+
txFee: burnedAmount,
54+
};
55+
}
56+
57+
throw new Error(`tx type is not supported`);
58+
};

0 commit comments

Comments
 (0)