Skip to content

Commit

Permalink
Merge pull request #915 from ava-labs/fix/validate-burn-for-new-txs
Browse files Browse the repository at this point in the history
fix: burn amount validation for new tx types
  • Loading branch information
meeh0w authored Nov 14, 2024
2 parents d36033b + d54bb02 commit 5be659b
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 25 deletions.
122 changes: 99 additions & 23 deletions src/utils/getBurnedAmountByTx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { newExportTxFromBaseFee, newImportTxFromBaseFee } from '../vms/evm';
import { getBurnedAmountByTx } from './getBurnedAmountByTx';
import { testContext } from '../fixtures/context';
import { testEthAddress1, testAddress1, testAddress2 } from '../fixtures/vms';
import { nodeId } from '../fixtures/common';
import { id, nodeId } from '../fixtures/common';
import { Utxo } from '../serializable/avax/utxo';
import { OutputOwners, TransferOutput } from '../serializable/fxs/secp256k1';
import { Address, Id } from '../serializable/fxs/common';
Expand All @@ -30,6 +30,18 @@ import type { EVMTx } from '../serializable/evm/abstractTx';
import { testUTXOID1, testUTXOID2 } from '../fixtures/transactions';
import { costCorethTx } from './costs';
import { StakeableLockOut } from '../serializable/pvm';
import {
newConvertSubnetToL1Tx,
newIncreaseL1ValidatorBalanceTx,
newRegisterL1ValidatorTx,
} from '../vms/pvm/etna-builder';
import { feeState, l1Validator } from '../fixtures/pvm';
import {
bigIntPr,
blsSignatureBytes,
bytesBytes,
stringPr,
} from '../fixtures/primitives';

const getUtxoMock = (
utxoId: Id,
Expand Down Expand Up @@ -95,7 +107,7 @@ describe('getBurnedAmountByTx', () => {
1n,
);

const amounts = getBurnedAmountByTx(tx.getTx() as EVMTx);
const amounts = getBurnedAmountByTx(tx.getTx() as EVMTx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
baseFee * costCorethTx(tx),
Expand All @@ -116,7 +128,7 @@ describe('getBurnedAmountByTx', () => {
baseFee,
);

const amounts = getBurnedAmountByTx(tx.getTx() as EVMTx);
const amounts = getBurnedAmountByTx(tx.getTx() as EVMTx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
baseFee * costCorethTx(tx),
Expand All @@ -139,7 +151,7 @@ describe('getBurnedAmountByTx', () => {
[output],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand All @@ -164,7 +176,7 @@ describe('getBurnedAmountByTx', () => {
[output1, output2, output3],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand Down Expand Up @@ -192,7 +204,7 @@ describe('getBurnedAmountByTx', () => {
[output1, output2, output3],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand Down Expand Up @@ -221,7 +233,7 @@ describe('getBurnedAmountByTx', () => {
[output],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand All @@ -243,7 +255,7 @@ describe('getBurnedAmountByTx', () => {
[output1, output2, output3],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand All @@ -266,7 +278,7 @@ describe('getBurnedAmountByTx', () => {
[output1, output2, output3],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand All @@ -288,7 +300,7 @@ describe('getBurnedAmountByTx', () => {
[testAddress1],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand All @@ -313,7 +325,7 @@ describe('getBurnedAmountByTx', () => {
[output],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand All @@ -335,7 +347,7 @@ describe('getBurnedAmountByTx', () => {
[output1, output2, output3],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand All @@ -358,7 +370,7 @@ describe('getBurnedAmountByTx', () => {
[output1, output2, output3],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand All @@ -380,7 +392,7 @@ describe('getBurnedAmountByTx', () => {
[testAddress1],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.baseTxFee,
Expand All @@ -407,7 +419,7 @@ describe('getBurnedAmountByTx', () => {
3,
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.addPrimaryNetworkValidatorFee,
Expand Down Expand Up @@ -442,7 +454,7 @@ describe('getBurnedAmountByTx', () => {
3,
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.addPrimaryNetworkValidatorFee,
Expand Down Expand Up @@ -473,7 +485,7 @@ describe('getBurnedAmountByTx', () => {
3,
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.addPrimaryNetworkValidatorFee,
Expand All @@ -499,7 +511,7 @@ describe('getBurnedAmountByTx', () => {
[testAddress1],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.addPrimaryNetworkDelegatorFee,
Expand Down Expand Up @@ -533,7 +545,7 @@ describe('getBurnedAmountByTx', () => {
[testAddress1],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.addPrimaryNetworkDelegatorFee,
Expand Down Expand Up @@ -563,7 +575,7 @@ describe('getBurnedAmountByTx', () => {
[testAddress1],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.addPrimaryNetworkDelegatorFee,
Expand All @@ -584,7 +596,7 @@ describe('getBurnedAmountByTx', () => {
[testAddress1],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.createSubnetTxFee,
Expand All @@ -610,7 +622,7 @@ describe('getBurnedAmountByTx', () => {
[0],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.createBlockchainTxFee,
Expand All @@ -637,12 +649,76 @@ describe('getBurnedAmountByTx', () => {
[0],
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx);
const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(
testContext.addSubnetValidatorFee,
);
});
});

it('calculates the burned amount of ConvertSubnetToL1 tx correctly', () => {
const utxo1 = getUtxoMock(testUTXOID1, 5000000n);
const utxo2 = getUtxoMock(testUTXOID2, 6000000n);

const tx = newConvertSubnetToL1Tx(
{
address: testAddress1,
chainId: id().toString(),
feeState: feeState(),
fromAddressesBytes: [testAddress1],
subnetAuth: [0],
subnetId: id().toString(),
utxos: [utxo1, utxo2],
validators: [l1Validator()],
},
testContext,
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(749n);
});
});

it('calculates the burned amount of RegisterL1Validator tx correctly', () => {
const utxo1 = getUtxoMock(testUTXOID1, 5000000n);
const utxo2 = getUtxoMock(testUTXOID2, 6000000n);

const tx = newRegisterL1ValidatorTx(
{
feeState: feeState(),
fromAddressesBytes: [testAddress1],
utxos: [utxo1, utxo2],
balance: bigIntPr().value(),
blsSignature: blsSignatureBytes(),
message: bytesBytes(),
},
testContext,
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(416n);
});

it('calculates the burned amount of RegisterL1Validator tx correctly', () => {
const utxo1 = getUtxoMock(testUTXOID1, 5000000n);
const utxo2 = getUtxoMock(testUTXOID2, 6000000n);

const tx = newIncreaseL1ValidatorBalanceTx(
{
feeState: feeState(),
fromAddressesBytes: [testAddress1],
utxos: [utxo1, utxo2],
balance: bigIntPr().value(),
validationId: stringPr().value(),
},
testContext,
).getTx() as AvaxTx;

const amounts = getBurnedAmountByTx(tx, testContext);
expect(amounts.size).toEqual(1);
expect(amounts.get(testContext.avaxAssetID)).toEqual(342n);
});
});
32 changes: 31 additions & 1 deletion src/utils/getBurnedAmountByTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import { isTransferableOutput } from '../serializable/avax';
import { getTransferableInputsByTx } from './getTransferableInputsByTx';
import { getTransferableOutputsByTx } from './getTransferableOutputsByTx';
import type { EVMTx } from '../serializable/evm/abstractTx';
import {
isConvertSubnetToL1Tx,
isIncreaseL1ValidatorBalanceTx,
isRegisterL1ValidatorTx,
} from '../serializable/pvm';
import type { Context } from '../vms/context';

const _reducer = (
assetAmountMap: Map<string, bigint>,
Expand Down Expand Up @@ -56,15 +62,39 @@ export const getOutputAmounts = (tx: AvaxTx | EVMTx) => {
}, new Map<string, bigint>());
};

export const getBurnedAmountByTx = (tx: AvaxTx | EVMTx) => {
const getValidatorBalanceSpendByTx = (tx: AvaxTx | EVMTx) => {
if (isConvertSubnetToL1Tx(tx)) {
return tx.validators.reduce(
(sum, validator) => sum + validator.balance.value(),
0n,
);
} else if (isRegisterL1ValidatorTx(tx)) {
return tx.balance.value();
} else if (isIncreaseL1ValidatorBalanceTx(tx)) {
return tx.balance.value();
}

return 0n;
};

export const getBurnedAmountByTx = (tx: AvaxTx | EVMTx, context: Context) => {
const inputAmounts = getInputAmounts(tx);
const outputAmounts = getOutputAmounts(tx);
const burnedAmounts = new Map<string, bigint>();
const validatorBalance = getValidatorBalanceSpendByTx(tx);

for (const [id, inputAmount] of inputAmounts.entries()) {
const outputAmount = outputAmounts.get(id) ?? 0n;
burnedAmounts.set(id, inputAmount - outputAmount);
}

if (validatorBalance) {
const burnedAvax = burnedAmounts.get(context.avaxAssetID);

if (burnedAvax) {
burnedAmounts.set(context.avaxAssetID, burnedAvax - validatorBalance);
}
}

return burnedAmounts;
};
2 changes: 1 addition & 1 deletion src/utils/validateBurnedAmount/validateBurnedAmount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
} from '../../serializable/pvm';

const _getBurnedAmount = (tx: Transaction, context: Context) => {
const burnedAmounts = getBurnedAmountByTx(tx as AvaxTx | EVMTx);
const burnedAmounts = getBurnedAmountByTx(tx as AvaxTx | EVMTx, context);
return burnedAmounts.get(context.avaxAssetID) ?? 0n;
};

Expand Down

0 comments on commit 5be659b

Please sign in to comment.