Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion modules/sdk-coin-flrp/src/lib/ExportInCTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import {
import utils from './utils';
import { Tx, FlareTransactionType, ExportEVMOptions, DecodedUtxoObj } from './iface';

/**
* Buffer applied to a caller-supplied base fee to absorb C-chain base-fee volatility
* between transaction signing and broadcast. Expressed in basis points (1000 = 10%).
* Mirrors the padding applied to Flare atomic imports (CECHO-1821).
*/
const BASE_FEE_PADDING_BPS = 1000n;

export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
private _nonce: bigint;

Expand Down Expand Up @@ -147,7 +154,13 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
throw new BuildTransactionError('context is required');
}

const fee = BigInt(this.transaction._fee.fee);
// The supplied fee is a C-chain base fee (per gas), not a literal total fee: flarejs's
// newExportTxFromBaseFee derives the actual export cost from the real tx size/inputs.
// Pad it to absorb base-fee volatility between signing and broadcast, mirroring the
// padding applied to Flare atomic imports (CECHO-1821), and fixing insufficient-funds
// broadcast failures caused by an unpadded base fee (CECHO-2004).
const suppliedBaseFee = BigInt(this.transaction._fee.fee);
const fee = (suppliedBaseFee * (10000n + BASE_FEE_PADDING_BPS)) / 10000n;
const fromAddressBytes = this.transaction._fromAddresses[0];
const sortedToAddresses = [...this.transaction._to].sort((a, b) => {
const aHex = Buffer.from(a).toString('hex');
Expand Down
31 changes: 31 additions & 0 deletions modules/sdk-coin-flrp/test/unit/lib/exportInCTxBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,37 @@ describe('ExportInCTxBuilder', function () {
});
});

it('should pad the supplied base fee by 10% to absorb C-chain base-fee volatility (CECHO-2004)', async () => {
const buildWithFee = async (fee: string) => {
const tx = await factory
.getExportInCBuilder()
.fromPubKey(testData.cHexAddress)
.nonce(testData.nonce)
.amount(testData.amount)
.threshold(testData.threshold)
.locktime(testData.locktime)
.to(testData.pAddresses)
.fee(fee)
.context(CONTEXT as FlrpContext)
.build();
const json = tx.toJson();
return BigInt(json.inputs[0].value) - BigInt(json.outputs[0].value);
};

const suppliedBaseFee = BigInt(testData.fee);
const actualFee = await buildWithFee(testData.fee);
// actualFee = paddedBaseFee * gasUnits, where paddedBaseFee = suppliedBaseFee * 1.10.
// gasUnits is recovered by dividing by the padded base fee and must be a whole number.
const paddedBaseFee = (suppliedBaseFee * 11000n) / 10000n;
const gasUnits = actualFee / paddedBaseFee;
const expectedFee = paddedBaseFee * gasUnits;
actualFee.should.equal(expectedFee);

// Sanity check the padding is actually non-trivial (not silently a no-op).
const unpaddedEquivalentFee = suppliedBaseFee * gasUnits;
(actualFee > unpaddedEquivalentFee).should.be.true();
});

it('should compute correct tx id for on-chain verified signed export', async () => {
const signedExportHex =
'0x0000000000010000007278db5c30bed04c05ce209179812850bbb3fe6d46d7eef3744d814c0da555247900000000000000000000000000000000000000000000000000000000000000000000000117dbd11b9dd1c9be337353db7c14f9fb3662e5b50000000002ff3d1658734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd00000000000000050000000158734f94af871c3d131b56131b6fb7a0291eacadd261e69dfb42a9cdf6f7fddd000000070000000002faf080000000000000000000000002000000033329be7d01cd3ebaae6654d7327dd9f17a2e15817e918a5e8083ae4c9f2f0ed77055c24bf3665001c7324437c96c7c8a6a152da2385c1db5c3ab1f910000000100000009000000018d1ac79d2e26d1c9689ca93b3b191c077dced2f201bdda132e74c3fc5ab9b10b6c85fd318dd6c0a99b327145977ac6ea6ff54cb8e9b7093b6bbe3545b3cc126400';
Expand Down
Loading