Skip to content

Commit

Permalink
Faucet EIP1559 support (#4109)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
MrLuit and FrederikBolding authored Sep 21, 2021
1 parent 0a10e9a commit 13ca7e0
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 76 deletions.
18 changes: 9 additions & 9 deletions src/features/Faucet/Faucet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,14 @@ export default function Faucet() {

const [network, setNetwork] = useState<Network | undefined>(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
Expand Down Expand Up @@ -226,13 +231,8 @@ export default function Faucet() {
<>
{faucetState.txResult && (
<TxReceipt
txConfig={makeTxConfig(
faucetState.txResult,
networks,
assets,
getContactByAddressAndNetworkId
)}
txReceipt={makeTxReceipt(faucetState.txResult, networks, assets)}
txConfig={txConfig}
txReceipt={txReceipt}
onComplete={() => reset()}
resetFlow={() => reset()}
queryStringsDisabled={true}
Expand Down
115 changes: 105 additions & 10 deletions src/features/Faucet/helpers.spec.tsx
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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', () => {
Expand All @@ -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],
Expand All @@ -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
Expand All @@ -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],
Expand All @@ -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')
Expand Down
77 changes: 23 additions & 54 deletions src/features/Faucet/helpers.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
};
18 changes: 17 additions & 1 deletion src/features/Faucet/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ITxData, ITxGasLimit, ITxGasPrice, ITxHash, TAddress } from '@types';

export interface ITxFaucetResult {
interface ITxFaucetResultV1 {
chainId: number;
data: ITxData;
from: TAddress;
Expand All @@ -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?: {
Expand Down
6 changes: 5 additions & 1 deletion src/helpers/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ type TxBeforeGasLimit = DistributiveOmit<ITxObject, 'nonce' | 'gasLimit'> & {
gasLimit?: ITxGasLimit;
};
type TxBeforeNonce = DistributiveOmit<ITxObject, 'nonce'> & { nonce?: ITxNonce };
type TxResponseBeforeBroadcast = DistributiveOmit<TransactionResponse, 'confirmations' | 'wait'> & {
confirmations?: number;
wait?(confirmations?: number): Promise<TransactionReceipt>;
};

const formatGas = (tx: ITxObject) =>
isType2Tx(tx)
Expand Down Expand Up @@ -251,7 +255,7 @@ export const makeTxConfigFromSignedTx = (

// needs testing
export const makeTxConfigFromTx = (
decodedTx: TransactionResponse | ITxObject,
decodedTx: TxResponseBeforeBroadcast | ITxObject,
assets: ExtendedAsset[],
network: Network,
accounts: StoreAccount[]
Expand Down
23 changes: 22 additions & 1 deletion src/services/ApiService/Faucet/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface FaucetChallengeResponse {
};
}

export interface FaucetSolvedChallengeResponse {
interface FaucetSolvedChallengeResponseV1 {
success: true;
result: {
chainId: number;
Expand All @@ -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;

0 comments on commit 13ca7e0

Please sign in to comment.