Skip to content

Commit 0969a11

Browse files
fix/payment-gas-estimation (#91)
* feat(utils): estimatePaymentGas * refactor(utils): estimation values * refactor(RelayClient): isCustom handler * refactor(rRlayClient): method name and test
1 parent 7a464c6 commit 0969a11

File tree

9 files changed

+403
-66
lines changed

9 files changed

+403
-66
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rsksmart/rif-relay-client",
3-
"version": "2.2.0",
3+
"version": "2.2.1",
44
"private": false,
55
"description": "This project contains all the client code for the rif relay system.",
66
"license": "MIT",

src/RelayClient.ts

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ import EnvelopingEventEmitter, {
5959
import { standardMaxPossibleGasEstimation } from './gasEstimator';
6060
import {
6161
estimateInternalCallGas,
62-
estimateTokenTransferGas,
62+
estimatePaymentGas,
6363
getSmartWalletAddress,
64+
isDataEmpty,
6465
maxPossibleGasVerification,
6566
validateRelayResponse,
6667
} from './utils';
@@ -92,7 +93,8 @@ class RelayClient extends EnvelopingEventEmitter {
9293
}
9394

9495
private _getEnvelopingRequestDetails = async (
95-
envelopingRequest: UserDefinedEnvelopingRequest
96+
envelopingRequest: UserDefinedEnvelopingRequest,
97+
isCustom = false
9698
): Promise<EnvelopingRequest> => {
9799
const isDeployment: boolean = isDeployRequest(
98100
envelopingRequest as EnvelopingRequest
@@ -132,7 +134,7 @@ class RelayClient extends EnvelopingEventEmitter {
132134
const tokenAmount =
133135
(await envelopingRequest.request.tokenAmount) ?? constants.Zero;
134136

135-
if (this._isContractCallInvalid(to, data, value)) {
137+
if (!isCustom && this._isCallInvalid(to, data, value)) {
136138
throw new Error('Contract execution needs data or value to be sent.');
137139
}
138140

@@ -264,14 +266,14 @@ class RelayClient extends EnvelopingEventEmitter {
264266
return completeRequest;
265267
};
266268

267-
private _isContractCallInvalid(
269+
private _isCallInvalid(
268270
to: string,
269271
data: BytesLike,
270272
value: BigNumberish
271273
): boolean {
272274
return (
273275
to != constants.AddressZero &&
274-
data === '0x00' &&
276+
isDataEmpty(data.toString()) &&
275277
BigNumber.from(value).isZero()
276278
);
277279
}
@@ -324,6 +326,7 @@ class RelayClient extends EnvelopingEventEmitter {
324326
relayHubAddress: await relayHub,
325327
signature: await accountManager.sign(updatedRelayRequest, signerWallet),
326328
relayMaxNonce,
329+
isCustom: options?.isCustom,
327330
};
328331
const httpRequest: EnvelopingTxRequest = {
329332
relayRequest: updatedRelayRequest,
@@ -350,8 +353,8 @@ class RelayClient extends EnvelopingEventEmitter {
350353
isCustom?: boolean
351354
): Promise<BigNumber> {
352355
const {
353-
request: { tokenGas, tokenAmount, tokenContract },
354-
relayData: { gasPrice, callForwarder },
356+
request: { tokenGas, tokenAmount },
357+
relayData: { callForwarder },
355358
} = envelopingRequest;
356359

357360
const currentTokenAmount = BigNumber.from(tokenAmount);
@@ -387,25 +390,16 @@ class RelayClient extends EnvelopingEventEmitter {
387390
})
388391
: await callForwarder;
389392

390-
const isNativePayment = (await tokenContract) === constants.AddressZero;
391-
392-
return isNativePayment
393-
? await estimateInternalCallGas({
394-
from: origin,
395-
to,
396-
gasPrice,
397-
data,
398-
})
399-
: await estimateTokenTransferGas({
400-
relayRequest: {
401-
...envelopingRequest,
402-
relayData: {
403-
...envelopingRequest.relayData,
404-
feesReceiver,
405-
},
406-
},
407-
preDeploySWAddress: origin,
408-
});
393+
return await estimatePaymentGas({
394+
relayRequest: {
395+
...envelopingRequest,
396+
relayData: {
397+
...envelopingRequest.relayData,
398+
feesReceiver,
399+
},
400+
},
401+
preDeploySWAddress: origin,
402+
});
409403
}
410404

411405
private async _calculateGasPrice(): Promise<BigNumber> {
@@ -491,7 +485,8 @@ class RelayClient extends EnvelopingEventEmitter {
491485
options?: RelayTxOptions
492486
): Promise<HubEnvelopingTx> {
493487
const envelopingRequestDetails = await this._getEnvelopingRequestDetails(
494-
envelopingRequest
488+
envelopingRequest,
489+
options?.isCustom
495490
);
496491

497492
//FIXME we should implement the relay selection strategy

src/common/relayClient.types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,19 @@ type RequestConfig = {
2121
estimatedGasCorrectionFactor?: BigNumberish;
2222
};
2323

24+
type PaymentGasEstimationParams = Pick<EnvelopingTxRequest, 'relayRequest'> &
25+
Pick<
26+
RequestConfig,
27+
| 'isSmartWalletDeploy'
28+
| 'preDeploySWAddress'
29+
| 'internalEstimationCorrection'
30+
| 'estimatedGasCorrectionFactor'
31+
>;
32+
2433
//FIXME name standardization
34+
/**
35+
* @deprecated The type was replaced by {@link PaymentGasEstimationParams}
36+
*/
2537
type TokenGasEstimationParams = Pick<EnvelopingTxRequest, 'relayRequest'> &
2638
Pick<
2739
RequestConfig,
@@ -36,6 +48,7 @@ type EstimateInternalGasParams = Pick<
3648
RelayRequestBody,
3749
'data' | 'to' | 'from'
3850
> &
51+
Partial<Pick<RelayRequestBody, 'value'>> &
3952
Pick<EnvelopingRequestData, 'gasPrice'> &
4053
Pick<
4154
RequestConfig,
@@ -67,6 +80,7 @@ type SmartWalletAddressTxOptions = {
6780

6881
export type {
6982
RequestConfig,
83+
PaymentGasEstimationParams,
7084
TokenGasEstimationParams,
7185
EstimateInternalGasParams,
7286
HubEnvelopingTx,

src/common/relayHub.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type EnvelopingMetadata = {
3030
relayHubAddress: RelayRequestBody['relayHub'];
3131
relayMaxNonce: number;
3232
signature: string;
33+
isCustom?: boolean;
3334
};
3435

3536
export type { HubInfo, EnvelopingMetadata, RelayInfo, RelayManagerData };

src/gasEstimator/gasEstimator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BigNumber, utils } from 'ethers';
2-
import { getSmartWalletAddress, estimateTokenTransferGas } from '../utils';
2+
import { getSmartWalletAddress, estimatePaymentGas } from '../utils';
33
import { isDeployRequest } from '../common/relayRequest.utils';
44
import type { EnvelopingTxRequest } from '../common/relayTransaction.types';
55
import {
@@ -39,7 +39,7 @@ const estimateRelayMaxPossibleGas = async (
3939
})
4040
: undefined;
4141

42-
const tokenEstimation = await estimateTokenTransferGas({
42+
const tokenEstimation = await estimatePaymentGas({
4343
relayRequest: {
4444
...relayRequest,
4545
request: {

src/utils.ts

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import type {
2929
EnvelopingTxRequest,
3030
DeployRequest,
3131
RelayRequest,
32+
PaymentGasEstimationParams,
3233
} from './common';
3334
import {
3435
MISSING_SMART_WALLET_ADDRESS,
@@ -38,9 +39,10 @@ import {
3839
import RelayClient from './RelayClient';
3940
import type { HttpClient } from './api/common';
4041

41-
const INTERNAL_TRANSACTION_ESTIMATED_CORRECTION = 20000; // When estimating the gas an internal call is going to spend, we need to substract some gas inherent to send the parameters to the blockchain
42+
const INTERNAL_TRANSACTION_ESTIMATED_CORRECTION = 18500; // When estimating the gas an internal call is going to spend, we need to substract some gas inherent to send the parameters to the blockchain
43+
const INTERNAL_TRANSACTION_NATIVE_ESTIMATED_CORRECTION = 10500;
4244
const ESTIMATED_GAS_CORRECTION_FACTOR = 1;
43-
const SHA3_NULL_S = utils.keccak256('0x');
45+
const SHA3_NULL_S = utils.keccak256('0x00');
4446
const FACTOR = 0.25;
4547
const GAS_VERIFICATION_ATTEMPTS = 4;
4648

@@ -61,6 +63,13 @@ const estimateInternalCallGas = async ({
6163

6264
let estimation: BigNumber = await provider.estimateGas(estimateGasParams);
6365

66+
const data = await estimateGasParams.data;
67+
68+
if (isDataEmpty(data.toString())) {
69+
internalEstimationCorrection =
70+
INTERNAL_TRANSACTION_NATIVE_ESTIMATED_CORRECTION;
71+
}
72+
6473
estimation = applyInternalEstimationCorrection(
6574
estimation,
6675
internalEstimationCorrection
@@ -69,6 +78,73 @@ const estimateInternalCallGas = async ({
6978
return applyGasCorrectionFactor(estimation, estimatedGasCorrectionFactor);
7079
};
7180

81+
const estimatePaymentGas = async ({
82+
internalEstimationCorrection,
83+
estimatedGasCorrectionFactor,
84+
preDeploySWAddress,
85+
relayRequest,
86+
}: PaymentGasEstimationParams): Promise<BigNumber> => {
87+
const {
88+
request: { tokenContract, tokenAmount, to },
89+
relayData: { callForwarder, gasPrice, feesReceiver },
90+
} = relayRequest;
91+
92+
if (tokenAmount.toString() === '0') {
93+
return constants.Zero;
94+
}
95+
96+
let tokenOrigin: PromiseOrValue<string> | undefined;
97+
98+
if (isDeployRequest(relayRequest)) {
99+
tokenOrigin = preDeploySWAddress;
100+
101+
// If it is a deploy and tokenGas was not defined, then the smartwallet address
102+
// is required to estimate the token gas. This value should be calculated prior to
103+
// the call to this function
104+
if (!tokenOrigin || tokenOrigin === constants.AddressZero) {
105+
throw Error(MISSING_SMART_WALLET_ADDRESS);
106+
}
107+
} else {
108+
tokenOrigin = callForwarder;
109+
110+
if (tokenOrigin === constants.AddressZero) {
111+
throw Error(MISSING_CALL_FORWARDER);
112+
}
113+
}
114+
115+
const isNativePayment = (await tokenContract) === constants.AddressZero;
116+
117+
if (isNativePayment) {
118+
return await estimateInternalCallGas({
119+
from: tokenOrigin,
120+
to,
121+
gasPrice,
122+
value: tokenAmount,
123+
data: '0x',
124+
});
125+
}
126+
127+
const provider = getProvider();
128+
const erc20 = IERC20__factory.connect(await tokenContract, provider);
129+
const gasCost = await erc20.estimateGas.transfer(feesReceiver, tokenAmount, {
130+
from: tokenOrigin,
131+
gasPrice,
132+
});
133+
134+
const internalCallCost = applyInternalEstimationCorrection(
135+
gasCost,
136+
internalEstimationCorrection
137+
);
138+
139+
return applyGasCorrectionFactor(
140+
internalCallCost,
141+
estimatedGasCorrectionFactor
142+
);
143+
};
144+
145+
/**
146+
* @deprecated The method was replaced by {@link estimatePaymentGas}
147+
*/
72148
const estimateTokenTransferGas = async ({
73149
internalEstimationCorrection,
74150
estimatedGasCorrectionFactor,
@@ -142,7 +218,7 @@ const getSmartWalletAddress = async ({
142218

143219
const recovererAddress = recoverer ?? constants.AddressZero;
144220

145-
const logicParamsHash = data ?? SHA3_NULL_S;
221+
const logicParamsHash = data ? utils.keccak256(data) : SHA3_NULL_S;
146222

147223
const logic = to ?? constants.AddressZero;
148224

@@ -362,17 +438,24 @@ const applyFactor = (value: BigNumberish, factor: number) => {
362438
);
363439
};
364440

441+
const isDataEmpty = (data: string) => {
442+
return ['', '0x', '0x00'].includes(data);
443+
};
444+
365445
export {
366446
estimateInternalCallGas,
447+
estimatePaymentGas,
367448
estimateTokenTransferGas,
368449
getSmartWalletAddress,
369450
applyGasCorrectionFactor,
370451
applyInternalEstimationCorrection,
371452
INTERNAL_TRANSACTION_ESTIMATED_CORRECTION,
453+
INTERNAL_TRANSACTION_NATIVE_ESTIMATED_CORRECTION,
372454
ESTIMATED_GAS_CORRECTION_FACTOR,
373455
SHA3_NULL_S,
374456
validateRelayResponse,
375457
useEnveloping,
376458
getRelayClientGenerator,
377459
maxPossibleGasVerification,
460+
isDataEmpty,
378461
};

0 commit comments

Comments
 (0)