From 13ca7e06415d62564cdd74f3994293c664837044 Mon Sep 17 00:00:00 2001 From: Luit Hollander Date: Tue, 21 Sep 2021 16:23:39 +0200 Subject: [PATCH] Faucet EIP1559 support (#4109) * Faucet: EIP1559 support * Fix faucet API constant * Improve v1/v2 if statements * Increase coverage * Faucet helper changes * Fix tests * Fix tests * Fix issue after rebase * Fix linting * Remove unneeded hexpad Co-authored-by: Frederik Bolding --- src/features/Faucet/Faucet.tsx | 18 ++-- src/features/Faucet/helpers.spec.tsx | 115 +++++++++++++++++++++--- src/features/Faucet/helpers.tsx | 77 +++++----------- src/features/Faucet/types.ts | 18 +++- src/helpers/transaction.ts | 6 +- src/services/ApiService/Faucet/types.ts | 23 ++++- 6 files changed, 181 insertions(+), 76 deletions(-) diff --git a/src/features/Faucet/Faucet.tsx b/src/features/Faucet/Faucet.tsx index f5be289c6c3..413fb56772b 100644 --- a/src/features/Faucet/Faucet.tsx +++ b/src/features/Faucet/Faucet.tsx @@ -110,9 +110,14 @@ export default function Faucet() { const [network, setNetwork] = useState(undefined); + const txConfig = + faucetState.txResult && + makeTxConfig(faucetState.txResult, networks, assets, accounts, getContactByAddressAndNetworkId); + + const txReceipt = txConfig && makeTxReceipt(faucetState.txResult, txConfig); + useEffect(() => { - if (faucetState.txResult) { - const txReceipt = makeTxReceipt(faucetState.txResult, networks, assets); + if (txReceipt) { const recipientAccount = getStoreAccount(accounts)( txReceipt.to, txReceipt.baseAsset.networkId @@ -226,13 +231,8 @@ export default function Faucet() { <> {faucetState.txResult && ( reset()} resetFlow={() => reset()} queryStringsDisabled={true} diff --git a/src/features/Faucet/helpers.spec.tsx b/src/features/Faucet/helpers.spec.tsx index 61c1c44bbfd..bd18920d54c 100644 --- a/src/features/Faucet/helpers.spec.tsx +++ b/src/features/Faucet/helpers.spec.tsx @@ -1,12 +1,13 @@ import { BigNumber } from '@ethersproject/bignumber'; +import { hexlify } from '@ethersproject/bytes'; -import { fAccount, fAssets, fNetworks } from '@fixtures'; +import { fAccount, fAccounts, fAssets, fNetworks } from '@fixtures'; import { ITxStatus, ITxType } from '@types'; import { makeTxConfig, makeTxReceipt, possibleSolution } from './helpers'; import { ITxFaucetResult } from './types'; -const exampleTXResult = { +const exampleTXResultV1 = { chainId: 3, data: '0x', from: '0xa500B2427458D12Ef70dd7b1E031ef99d1cc09f7', @@ -19,6 +20,20 @@ const exampleTXResult = { value: '1' } as ITxFaucetResult; +const exampleTXResultV2 = { + chainId: 3, + data: '0x', + from: '0xa500B2427458D12Ef70dd7b1E031ef99d1cc09f7', + gasLimit: '21000', + maxFeePerGas: '1000000000', + maxPriorityFeePerGas: '1000000000', + hash: '0x5049c0847681402db2c303847f2f66ac7f3a6caf63119b676374d5781b8d11e9', + network: 'ropsten', + nonce: 39, + to: fAccount.address, + value: '1' +} as ITxFaucetResult; + describe('Faucet helpers', () => { describe('Captcha solution regex', () => { test('AbC0 should be a valid solution', () => { @@ -31,9 +46,15 @@ describe('Faucet helpers', () => { describe('makeTxConfig', () => { const getContactByAddressAndNetworkId = jest.fn(); - test('returns expected value', async () => { + test('returns expected value for type 1 tx', async () => { expect( - makeTxConfig(exampleTXResult, fNetworks, fAssets, getContactByAddressAndNetworkId) + makeTxConfig( + exampleTXResultV1, + fNetworks, + fAssets, + fAccounts, + getContactByAddressAndNetworkId + ) ).toEqual({ amount: '0.000000000000000001', asset: fAssets[1], @@ -44,11 +65,43 @@ describe('Faucet helpers', () => { chainId: 3, data: '0x', from: '0xa500B2427458D12Ef70dd7b1E031ef99d1cc09f7', - gasLimit: '21000', - gasPrice: '1000000000', - nonce: '39', + gasLimit: hexlify(21000), + gasPrice: hexlify(1000000000), + nonce: hexlify(39), to: fAccount.address, - value: '1' + type: 0, + value: hexlify(1) + }, + receiverAddress: fAccount.address, + senderAccount: undefined + }); + }); + test('returns expected value for type 2 tx', async () => { + expect( + makeTxConfig( + exampleTXResultV2, + fNetworks, + fAssets, + fAccounts, + getContactByAddressAndNetworkId + ) + ).toEqual({ + amount: '0.000000000000000001', + asset: fAssets[1], + baseAsset: fAssets[1], + from: '0xa500B2427458D12Ef70dd7b1E031ef99d1cc09f7', + networkId: 'Ropsten', + rawTransaction: { + chainId: 3, + data: '0x', + from: '0xa500B2427458D12Ef70dd7b1E031ef99d1cc09f7', + gasLimit: hexlify(21000), + maxFeePerGas: hexlify(1000000000), + maxPriorityFeePerGas: hexlify(1000000000), + type: 2, + nonce: hexlify(39), + to: fAccount.address, + value: hexlify(1) }, receiverAddress: fAccount.address, senderAccount: undefined @@ -57,8 +110,17 @@ describe('Faucet helpers', () => { }); describe('makeTxReceipt', () => { - test('returns expected value', async () => { - expect(makeTxReceipt(exampleTXResult, fNetworks, fAssets)).toEqual({ + const getContactByAddressAndNetworkId = jest.fn(); + + test('returns expected value for type 1 tx', async () => { + const txConfig = makeTxConfig( + exampleTXResultV1, + fNetworks, + fAssets, + fAccounts, + getContactByAddressAndNetworkId + ); + expect(makeTxReceipt(exampleTXResultV1, txConfig)).toEqual({ amount: '0.000000000000000001', asset: fAssets[1], baseAsset: fAssets[1], @@ -70,6 +132,39 @@ describe('Faucet helpers', () => { nonce: BigNumber.from(39), receiverAddress: fAccount.address, status: ITxStatus.PENDING, + blockNumber: 0, + timestamp: 0, + metadata: undefined, + to: fAccount.address, + txType: ITxType.FAUCET, + value: BigNumber.from('1') + }); + }); + test('returns expected value for type 2 tx', async () => { + const txConfig = makeTxConfig( + exampleTXResultV2, + fNetworks, + fAssets, + fAccounts, + getContactByAddressAndNetworkId + ); + expect(makeTxReceipt(exampleTXResultV2, txConfig)).toEqual({ + amount: '0.000000000000000001', + asset: fAssets[1], + baseAsset: fAssets[1], + data: '0x', + from: '0xa500B2427458D12Ef70dd7b1E031ef99d1cc09f7', + gasLimit: BigNumber.from(21000), + maxFeePerGas: BigNumber.from(1000000000), + maxPriorityFeePerGas: BigNumber.from(1000000000), + type: 2, + hash: '0x5049c0847681402db2c303847f2f66ac7f3a6caf63119b676374d5781b8d11e9', + nonce: BigNumber.from(39), + receiverAddress: fAccount.address, + status: ITxStatus.PENDING, + blockNumber: 0, + timestamp: 0, + metadata: undefined, to: fAccount.address, txType: ITxType.FAUCET, value: BigNumber.from('1') diff --git a/src/features/Faucet/helpers.tsx b/src/features/Faucet/helpers.tsx index 1ff7dc4d373..dfee2607c7f 100644 --- a/src/features/Faucet/helpers.tsx +++ b/src/features/Faucet/helpers.tsx @@ -1,17 +1,13 @@ import { BigNumber } from '@ethersproject/bignumber'; -import { formatEther } from '@ethersproject/units'; -import { getBaseAssetByNetwork } from '@services/Store'; +import { makeTxConfigFromTx, toTxReceipt } from '@helpers'; import { - Asset, ExtendedAsset, ExtendedContact, ITxConfig, - ITxNonce, ITxReceipt, ITxStatus, ITxType, - ITxValue, Network, NetworkId, StoreAccount, @@ -33,71 +29,44 @@ export const makeTxConfig = ( txResult: ITxFaucetResult, networks: Network[], assets: ExtendedAsset[], + accounts: StoreAccount[], getContactByAddressAndNetworkId: ( address: TAddress, networkId: NetworkId ) => ExtendedContact | undefined ): ITxConfig => { const network = getNetworkByLowercaseId(txResult.network, networks); - const baseAsset: Asset = getBaseAssetByNetwork({ - network, - assets - })!; // Guaranteed to work as Faucet address is in STATIC_CONTACTS const senderContact = getContactByAddressAndNetworkId(txResult.from, network.id)!; + const newTxResult = { + ...txResult, + gasLimit: BigNumber.from(txResult.gasLimit), + gasPrice: 'gasPrice' in txResult ? BigNumber.from(txResult.gasPrice) : undefined, + maxPriorityFeePerGas: + 'maxPriorityFeePerGas' in txResult + ? BigNumber.from(txResult.maxPriorityFeePerGas) + : undefined, + maxFeePerGas: 'maxFeePerGas' in txResult ? BigNumber.from(txResult.maxFeePerGas) : undefined, + type: 'maxPriorityFeePerGas' in txResult && 'maxFeePerGas' in txResult ? 2 : 0, + value: BigNumber.from(txResult.value) + }; + /* * ITxConfig.senderAccount uses type StoreAccount, but in this case the user is the recipient and the faucet is the sender. * getContactByAddressAndNetworkId() returns ExtendedContact, which is the closest we can get. * The result is casted to make it compatible with ITxConfig. */ - return { - rawTransaction: { - to: txResult.to, - value: txResult.value as ITxValue, - gasLimit: txResult.gasLimit, - data: txResult.data, - gasPrice: txResult.gasPrice, - nonce: txResult.nonce.toString() as ITxNonce, - chainId: txResult.chainId, - from: txResult.from - }, - amount: formatEther(txResult.value), - receiverAddress: txResult.to, - senderAccount: (senderContact as unknown) as StoreAccount, - from: txResult.from, - asset: baseAsset, - baseAsset, - networkId: network.id + const txConfig = { + ...makeTxConfigFromTx(newTxResult, assets, network, accounts), + senderAccount: (senderContact as unknown) as StoreAccount }; -}; -export const makeTxReceipt = ( - txResult: ITxFaucetResult, - networks: Network[], - assets: ExtendedAsset[] -): ITxReceipt => { - const network = getNetworkByLowercaseId(txResult.network, networks); - const baseAsset: Asset = getBaseAssetByNetwork({ - network, - assets - })!; + return txConfig; +}; - return { - asset: baseAsset, - baseAsset, - txType: ITxType.FAUCET, - status: ITxStatus.PENDING, - receiverAddress: txResult.to, - amount: formatEther(txResult.value), - data: txResult.data, - gasPrice: BigNumber.from(txResult.gasPrice), - gasLimit: BigNumber.from(txResult.gasLimit), - to: txResult.to, - from: txResult.from, - value: BigNumber.from(txResult.value), - nonce: BigNumber.from(txResult.nonce), - hash: txResult.hash - }; +export const makeTxReceipt = (txResult: ITxFaucetResult, txConfig: ITxConfig): ITxReceipt => { + const txReceipt = toTxReceipt(txResult.hash, ITxStatus.PENDING)(ITxType.FAUCET, txConfig); + return txReceipt; }; diff --git a/src/features/Faucet/types.ts b/src/features/Faucet/types.ts index d74c84c9a57..3e444083016 100644 --- a/src/features/Faucet/types.ts +++ b/src/features/Faucet/types.ts @@ -1,6 +1,6 @@ import { ITxData, ITxGasLimit, ITxGasPrice, ITxHash, TAddress } from '@types'; -export interface ITxFaucetResult { +interface ITxFaucetResultV1 { chainId: number; data: ITxData; from: TAddress; @@ -13,6 +13,22 @@ export interface ITxFaucetResult { value: string; } +interface ITxFaucetResultV2 { + chainId: number; + data: ITxData; + from: TAddress; + gasLimit: ITxGasLimit; + maxFeePerGas: ITxGasPrice; + maxPriorityFeePerGas: ITxGasPrice; + hash: ITxHash; + network: string; + nonce: number; + to: TAddress; + value: string; +} + +export type ITxFaucetResult = ITxFaucetResultV1 | ITxFaucetResultV2; + export interface FaucetState { step: number; challenge?: { diff --git a/src/helpers/transaction.ts b/src/helpers/transaction.ts index 25ee5768c0e..ffa40fb9cf5 100644 --- a/src/helpers/transaction.ts +++ b/src/helpers/transaction.ts @@ -86,6 +86,10 @@ type TxBeforeGasLimit = DistributiveOmit & { gasLimit?: ITxGasLimit; }; type TxBeforeNonce = DistributiveOmit & { nonce?: ITxNonce }; +type TxResponseBeforeBroadcast = DistributiveOmit & { + confirmations?: number; + wait?(confirmations?: number): Promise; +}; const formatGas = (tx: ITxObject) => isType2Tx(tx) @@ -251,7 +255,7 @@ export const makeTxConfigFromSignedTx = ( // needs testing export const makeTxConfigFromTx = ( - decodedTx: TransactionResponse | ITxObject, + decodedTx: TxResponseBeforeBroadcast | ITxObject, assets: ExtendedAsset[], network: Network, accounts: StoreAccount[] diff --git a/src/services/ApiService/Faucet/types.ts b/src/services/ApiService/Faucet/types.ts index b8a20c12421..ed594d492a6 100644 --- a/src/services/ApiService/Faucet/types.ts +++ b/src/services/ApiService/Faucet/types.ts @@ -15,7 +15,7 @@ export interface FaucetChallengeResponse { }; } -export interface FaucetSolvedChallengeResponse { +interface FaucetSolvedChallengeResponseV1 { success: true; result: { chainId: number; @@ -30,3 +30,24 @@ export interface FaucetSolvedChallengeResponse { value: string; }; } + +interface FaucetSolvedChallengeResponseV2 { + success: true; + result: { + chainId: number; + data: string; + from: string; + gasLimit: string; + maxFeePerGas: string; + maxPriorityFeePerGas: string; + hash: string; + network: string; + nonce: number; + to: string; + value: string; + }; +} + +export type FaucetSolvedChallengeResponse = + | FaucetSolvedChallengeResponseV1 + | FaucetSolvedChallengeResponseV2;