Skip to content

Commit

Permalink
feat: make fee margins configurable
Browse files Browse the repository at this point in the history
  • Loading branch information
penovicp committed Mar 14, 2024
1 parent dd34cdb commit cedd984
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 21 deletions.
28 changes: 28 additions & 0 deletions __tests__/rpcProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Block,
CallData,
Contract,
FeeEstimate,
RPC,
RPC06,
RpcProvider,
Expand Down Expand Up @@ -79,6 +80,33 @@ describeIfRpc('RPCProvider', () => {
expect(typeof spec).toBe('string');
});

test('configurable margin', async () => {
const p = new RpcProvider({
nodeUrl: provider.channel.nodeUrl,
feeMarginPercentage: {
l1BoundMaxAmount: 0,
l1BoundMaxPricePerUnit: 0,
maxFee: 0,
},
});
const estimateSpy = jest.spyOn(p.channel as any, 'getEstimateFee');
const mockFeeEstimate: FeeEstimate = {
gas_consumed: '0x2',
gas_price: '0x1',
data_gas_consumed: '0x2',
data_gas_price: '0x1',
overall_fee: '0x4',
unit: 'WEI',
};
estimateSpy.mockResolvedValue([mockFeeEstimate]);
const result = (await p.getEstimateFeeBulk([{} as any], {}))[0];
expect(estimateSpy).toHaveBeenCalledTimes(1);
expect(result.suggestedMaxFee).toBe(4n);
expect(result.resourceBounds.l1_gas.max_amount).toBe('0x4');
expect(result.resourceBounds.l1_gas.max_price_per_unit).toBe('0x1');
estimateSpy.mockRestore();
});

describe('Test Estimate message fee', () => {
const L1_ADDRESS = '0x8359E4B0152ed5A731162D3c7B0D8D56edB165A0';
let l1l2ContractAddress: string;
Expand Down
4 changes: 2 additions & 2 deletions __tests__/utils/stark.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ describe('stark', () => {
};
expect(stark.estimateFeeToBounds(estimateFeeResponse)).toStrictEqual({
l2_gas: { max_amount: '0x0', max_price_per_unit: '0x0' },
l1_gas: { max_amount: '0x6e', max_price_per_unit: '0xf' },
l1_gas: { max_amount: '0x96', max_price_per_unit: '0xf' },
});
expect(stark.estimateFeeToBounds(estimateFeeResponse07)).toStrictEqual({
l2_gas: { max_amount: '0x0', max_price_per_unit: '0x0' },
l1_gas: { max_amount: '0xdc', max_price_per_unit: '0xf' },
l1_gas: { max_amount: '0x12c', max_price_per_unit: '0xf' },
});
});

Expand Down
4 changes: 2 additions & 2 deletions __tests__/utils/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ describe('computeHashOnElements()', () => {
});
describe('estimatedFeeToMaxFee()', () => {
test('should return maxFee for 0', () => {
const res = stark.estimatedFeeToMaxFee(0, 0.15);
const res = stark.estimatedFeeToMaxFee(0, 15);
expect(res).toBe(0n);
});
test('should return maxFee for 10_000', () => {
const res = stark.estimatedFeeToMaxFee(10_000, 0.15);
const res = stark.estimatedFeeToMaxFee(10_000, 15);
expect(res).toBe(11500n);
});
});
Expand Down
6 changes: 6 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ export enum TransactionHashPrefix {
L1_HANDLER = '0x6c315f68616e646c6572', // encodeShortString('l1_handler'),
}

export const enum feeMarginPercentage {
L1_BOUND_MAX_AMOUNT = 50,
L1_BOUND_MAX_PRICE_PER_UNIT = 50,
MAX_FEE = 50,
}

export const UDC = {
ADDRESS: '0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf',
ENTRYPOINT: 'deployContract',
Expand Down
14 changes: 8 additions & 6 deletions src/provider/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ import { isSierra } from '../utils/contract';
import { RPCResponseParser } from '../utils/responseParser/rpc';

export class RpcProvider implements ProviderInterface {
private responseParser = new RPCResponseParser();
private responseParser: RPCResponseParser;

public channel: RPC07.RpcChannel | RPC06.RpcChannel;

constructor(optionsOrProvider?: RpcProviderOptions | ProviderInterface | RpcProvider) {
if (optionsOrProvider && 'channel' in optionsOrProvider) {
this.channel = optionsOrProvider.channel;
this.responseParser = (optionsOrProvider as any).responseParser;
} else {
this.channel = new RpcChannel({ ...optionsOrProvider, waitMode: false });
this.responseParser = new RPCResponseParser(optionsOrProvider?.feeMarginPercentage);
}
}

Expand Down Expand Up @@ -176,7 +178,7 @@ export class RpcProvider implements ProviderInterface {
// can't be named simulateTransaction because of argument conflict with account
return this.channel
.simulateTransaction(invocations, options)
.then(this.responseParser.parseSimulateTransactionResponse);
.then((r) => this.responseParser.parseSimulateTransactionResponse(r));
}

public async waitForTransaction(txHash: BigNumberish, options?: waitForTransactionOptions) {
Expand Down Expand Up @@ -278,7 +280,7 @@ export class RpcProvider implements ProviderInterface {
],
{ blockIdentifier, skipValidate }
)
.then(this.responseParser.parseFeeEstimateResponse);
.then((r) => this.responseParser.parseFeeEstimateResponse(r));
}

public async getDeclareEstimateFee(
Expand All @@ -298,7 +300,7 @@ export class RpcProvider implements ProviderInterface {
],
{ blockIdentifier, skipValidate }
)
.then(this.responseParser.parseFeeEstimateResponse);
.then((r) => this.responseParser.parseFeeEstimateResponse(r));
}

public async getDeployAccountEstimateFee(
Expand All @@ -318,7 +320,7 @@ export class RpcProvider implements ProviderInterface {
],
{ blockIdentifier, skipValidate }
)
.then(this.responseParser.parseFeeEstimateResponse);
.then((r) => this.responseParser.parseFeeEstimateResponse(r));
}

public async getEstimateFeeBulk(
Expand All @@ -327,7 +329,7 @@ export class RpcProvider implements ProviderInterface {
) {
return this.channel
.getEstimateFee(invocations, options)
.then(this.responseParser.parseFeeEstimateBulkResponse);
.then((r) => this.responseParser.parseFeeEstimateBulkResponse(r));
}

public async invokeFunction(
Expand Down
5 changes: 5 additions & 0 deletions src/types/provider/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ export type RpcProviderOptions = {
specVersion?: string;
default?: boolean;
waitMode?: boolean;
feeMarginPercentage?: {
l1BoundMaxAmount: number;
l1BoundMaxPricePerUnit: number;
maxFee: number;
};
};
31 changes: 25 additions & 6 deletions src/utils/responseParser/rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
FeeEstimate,
SimulateTransactionResponse,
SimulatedTransaction,
RpcProviderOptions,
} from '../../types/provider';
import { toBigInt } from '../num';
import { estimateFeeToBounds, estimatedFeeToMaxFee } from '../stark';
Expand All @@ -31,6 +32,24 @@ export class RPCResponseParser
| 'parseCallContractResponse'
>
{
private margin: RpcProviderOptions['feeMarginPercentage'];

constructor(margin?: RpcProviderOptions['feeMarginPercentage']) {
this.margin = margin;
}

private estimatedFeeToMaxFee(estimatedFee: Parameters<typeof estimatedFeeToMaxFee>[0]) {
return estimatedFeeToMaxFee(estimatedFee, this.margin?.maxFee);
}

private estimateFeeToBounds(estimate: Parameters<typeof estimateFeeToBounds>[0]) {
return estimateFeeToBounds(
estimate,
this.margin?.l1BoundMaxAmount,
this.margin?.l1BoundMaxPricePerUnit
);
}

public parseGetBlockResponse(res: BlockWithTxHashes): GetBlockResponse {
return { status: 'PENDING', ...res } as GetBlockResponse;
}
Expand Down Expand Up @@ -58,8 +77,8 @@ export class RPCResponseParser
gas_consumed: toBigInt(val.gas_consumed),
gas_price: toBigInt(val.gas_price),
unit: val.unit,
suggestedMaxFee: estimatedFeeToMaxFee(val.overall_fee),
resourceBounds: estimateFeeToBounds(val),
suggestedMaxFee: this.estimatedFeeToMaxFee(val.overall_fee),
resourceBounds: this.estimateFeeToBounds(val),
};
}

Expand All @@ -69,8 +88,8 @@ export class RPCResponseParser
gas_consumed: toBigInt(val.gas_consumed),
gas_price: toBigInt(val.gas_price),
unit: val.unit,
suggestedMaxFee: estimatedFeeToMaxFee(val.overall_fee),
resourceBounds: estimateFeeToBounds(val),
suggestedMaxFee: this.estimatedFeeToMaxFee(val.overall_fee),
resourceBounds: this.estimateFeeToBounds(val),
}));
}

Expand All @@ -85,8 +104,8 @@ export class RPCResponseParser
return res.map((it: SimulatedTransaction) => {
return {
...it,
suggestedMaxFee: estimatedFeeToMaxFee(BigInt(it.fee_estimation.overall_fee)),
resourceBounds: estimateFeeToBounds(it.fee_estimation),
suggestedMaxFee: this.estimatedFeeToMaxFee(it.fee_estimation.overall_fee),
resourceBounds: this.estimateFeeToBounds(it.fee_estimation),
};
});
}
Expand Down
13 changes: 8 additions & 5 deletions src/utils/stark.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getStarkKey, utils } from '@scure/starknet';
import { gzip, ungzip } from 'pako';

import { ZERO } from '../constants';
import { ZERO, feeMarginPercentage } from '../constants';
import {
ArraySignatureType,
BigNumberish,
Expand Down Expand Up @@ -95,14 +95,17 @@ export function signatureToHexArray(sig?: Signature): ArraySignatureType {
/**
* Convert estimated fee to max fee with overhead
*/
export function estimatedFeeToMaxFee(estimatedFee: BigNumberish, overhead: number = 0.5): bigint {
return addPercent(estimatedFee, overhead * 100);
export function estimatedFeeToMaxFee(
estimatedFee: BigNumberish,
overhead: number = feeMarginPercentage.MAX_FEE
): bigint {
return addPercent(estimatedFee, overhead);
}

export function estimateFeeToBounds(
estimate: FeeEstimate | 0n,
amountOverhead: number = 10,
priceOverhead = 50
amountOverhead: number = feeMarginPercentage.L1_BOUND_MAX_AMOUNT,
priceOverhead: number = feeMarginPercentage.L1_BOUND_MAX_PRICE_PER_UNIT
): ResourceBounds {
if (typeof estimate === 'bigint') {
return {
Expand Down

0 comments on commit cedd984

Please sign in to comment.