Skip to content

Commit

Permalink
check tx gas against block gas limit
Browse files Browse the repository at this point in the history
  • Loading branch information
alejoacosta74 committed Dec 13, 2024
1 parent f5e5dfd commit 4f46d1d
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
126 changes: 126 additions & 0 deletions src/_tests/unit/qihdwallet-check-gas-limit.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import assert from 'assert';
import { QiHDWallet } from '../../wallet/qi-hdwallet.js';
import { Mnemonic } from '../../wallet/mnemonic.js';
import { Zone } from '../../constants/zones.js';
import { MockProvider } from './mockProvider.js';
import { QiTransaction } from '../../transaction/index.js';
import { Block } from '../../providers/index.js';

class TestQiHDWallet extends QiHDWallet {
public async checkGasLimit(tx: QiTransaction, zone: Zone): Promise<boolean> {
return this['_checkGasLimit'](tx, zone);
}
}

interface GasLimitTestCase {
name: string;
mnemonic: string;
zone: Zone;
blockGasLimit: bigint;
estimatedGas: bigint;
expectedResult: boolean;
}

const testMnemonic = 'test test test test test test test test test test test junk';

describe('QiHDWallet: Gas Limit Tests', () => {
const testCases: GasLimitTestCase[] = [
{
name: 'Gas limit is sufficient (well below 90%)',
mnemonic: testMnemonic,
zone: Zone.Cyprus1,
blockGasLimit: BigInt(30000),
estimatedGas: BigInt(21000), // 70% of block gas limit
expectedResult: true,
},
{
name: 'Gas limit is insufficient (above 90%)',
mnemonic: testMnemonic,
zone: Zone.Cyprus1,
blockGasLimit: BigInt(20000),
estimatedGas: BigInt(19000), // 95% of block gas limit
expectedResult: false,
},
{
name: 'Gas limit exactly at 90%',
mnemonic: testMnemonic,
zone: Zone.Cyprus1,
blockGasLimit: BigInt(20000),
estimatedGas: BigInt(18000), // exactly 90% of block gas limit
expectedResult: true,
},
{
name: 'Gas limit slightly below 90%',
mnemonic: testMnemonic,
zone: Zone.Cyprus1,
blockGasLimit: BigInt(20000),
estimatedGas: BigInt(17900), // 89.5% of block gas limit
expectedResult: true,
},
{
name: 'Gas limit slightly above 90%',
mnemonic: testMnemonic,
zone: Zone.Cyprus1,
blockGasLimit: BigInt(20000),
estimatedGas: BigInt(18100), // 90.5% of block gas limit
expectedResult: false,
},
];

testCases.forEach((testCase) => {
it(testCase.name, async () => {
const mnemonic = Mnemonic.fromPhrase(testCase.mnemonic);
const wallet = TestQiHDWallet.fromMnemonic(mnemonic);

const mockProvider = new MockProvider({ network: BigInt(1) });

mockProvider.getBlock = async () => {
return {
header: {
gasLimit: testCase.blockGasLimit,
},
} as Block;
};

mockProvider.estimateGas = async () => {
return testCase.estimatedGas;
};

wallet.connect(mockProvider);

const tx = new QiTransaction();

const result = await wallet.checkGasLimit(tx, testCase.zone);
assert.equal(
result,
testCase.expectedResult,
`Expected gas limit check to return ${testCase.expectedResult} but got ${result}`,
);
});
});

it('should throw error when provider is not set', async () => {
const mnemonic = Mnemonic.fromPhrase(testMnemonic);
const wallet = TestQiHDWallet.fromMnemonic(mnemonic);
const tx = new QiTransaction();

await assert.rejects(async () => await wallet.checkGasLimit(tx, Zone.Cyprus1), {
message: 'Provider is not set',
});
});

it('should throw error when block cannot be retrieved', async () => {
const mnemonic = Mnemonic.fromPhrase(testMnemonic);
const wallet = TestQiHDWallet.fromMnemonic(mnemonic);
const mockProvider = new MockProvider({ network: BigInt(1) });

mockProvider.getBlock = async () => null;

wallet.connect(mockProvider);
const tx = new QiTransaction();

await assert.rejects(async () => await wallet.checkGasLimit(tx, Zone.Cyprus1), {
message: 'Failed to get the current block',
});
});
});
35 changes: 35 additions & 0 deletions src/wallet/qi-hdwallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,11 @@ export class QiHDWallet extends AbstractHDWallet<QiAddressInfo> {
Number(chainId),
);

// verify tx gas is under block gas limit
if (await this._checkGasLimit(tx, zone)) {
throw new Error('Transaction gas limit exceeds block gas limit');
}

// Sign the transaction
const signedTx = await this.signTransaction(tx);

Expand Down Expand Up @@ -895,6 +900,11 @@ export class QiHDWallet extends AbstractHDWallet<QiAddressInfo> {
Number(chainId),
);

// verify tx gas is under block gas limit
if (await this._checkGasLimit(tx, originZone)) {
throw new Error('Transaction gas limit exceeds block gas limit');
}

// Sign the transaction
const signedTx = await this.signTransaction(tx);
// Broadcast the transaction to the network using the provider
Expand Down Expand Up @@ -989,6 +999,31 @@ export class QiHDWallet extends AbstractHDWallet<QiAddressInfo> {
txOut,
};
}
/**
* Checks if the estimated gas for a transaction is within the current block's gas limit.
*
* @private
* @param {QiTransaction} tx - The Qi transaction to check
* @param {Zone} zone - The zone where the transaction will be executed
* @returns {Promise<boolean>} Returns true if the estimated gas is within block limit, false otherwise
* @throws {Error} If provider is not set or block cannot be retrieved
*/
private async _checkGasLimit(tx: QiTransaction, zone: Zone): Promise<boolean> {
if (!this.provider) {
throw new Error('Provider is not set');
}
const currentBlock = await this.provider.getBlock(toShard(zone), 'latest')!;
if (!currentBlock) {
throw new Error('Failed to get the current block');
}

const blockGasLimit = currentBlock.header.gasLimit;

const txEstimatedGas = await this.provider.estimateGas(tx);

// return true if txEstimatedGas is 90% or less of blockGasLimit
return txEstimatedGas <= (blockGasLimit * 9n) / 10n;
}

/**
* Gets a set of unused BIP44 addresses from the specified derivation path. It first checks if there are any unused
Expand Down

0 comments on commit 4f46d1d

Please sign in to comment.