Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support EIP-2718 transaction types, EIP-2930 and EIP-1559 support #11288

Merged
merged 5 commits into from
Jun 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 65 additions & 9 deletions app/scripts/controllers/transactions/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import EventEmitter from 'safe-event-emitter';
import { ObservableStore } from '@metamask/obs-store';
import { bufferToHex, keccak, toBuffer } from 'ethereumjs-util';
import Transaction from 'ethereumjs-tx';
import EthQuery from 'ethjs-query';
import { ethErrors } from 'eth-rpc-errors';
import abi from 'human-standard-token-abi';
import Common from '@ethereumjs/common';
import { TransactionFactory } from '@ethereumjs/tx';
import { ethers } from 'ethers';
import NonceTracker from 'nonce-tracker';
import log from 'loglevel';
Expand All @@ -24,11 +25,17 @@ import {
} from '../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { GAS_LIMITS } from '../../../../shared/constants/gas';
import {
MAINNET,
NETWORK_TYPE_RPC,
} from '../../../../shared/constants/network';
import TransactionStateManager from './tx-state-manager';
import TxGasUtil from './tx-gas-utils';
import PendingTransactionTracker from './pending-tx-tracker';
import * as txUtils from './lib/util';

const HARDFORK = 'berlin';

const hstInterface = new ethers.utils.Interface(abi);

const MAX_MEMSTORE_TX_LIST_SIZE = 100; // Number of transactions (by unique nonces) to keep in memory
Expand All @@ -53,7 +60,7 @@ const MAX_MEMSTORE_TX_LIST_SIZE = 100; // Number of transactions (by unique nonc
@param {Object} opts.networkStore - an observable store for network number
@param {Object} opts.blockTracker - An instance of eth-blocktracker
@param {Object} opts.provider - A network provider.
@param {Function} opts.signTransaction - function the signs an ethereumjs-tx
@param {Function} opts.signTransaction - function the signs an @ethereumjs/tx
@param {Object} opts.getPermittedAccounts - get accounts that an origin has permissions for
@param {Function} opts.signTransaction - ethTx signer that returns a rawTx
@param {number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state
Expand All @@ -65,6 +72,7 @@ export default class TransactionController extends EventEmitter {
super();
this.networkStore = opts.networkStore || new ObservableStore({});
this._getCurrentChainId = opts.getCurrentChainId;
this.getProviderConfig = opts.getProviderConfig;
this.preferencesStore = opts.preferencesStore || new ObservableStore({});
this.provider = opts.provider;
this.getPermittedAccounts = opts.getPermittedAccounts;
Expand Down Expand Up @@ -146,6 +154,49 @@ export default class TransactionController extends EventEmitter {
return integerChainId;
}

/**
* @ethereumjs/tx uses @ethereumjs/common as a configuration tool for
* specifying which chain, network, hardfork and EIPs to support for
* a transaction. By referencing this configuration, and analyzing the fields
* specified in txParams, @ethereumjs/tx is able to determine which EIP-2718
* transaction type to use.
* @returns {Common} common configuration object
*/
getCommonConfiguration() {
const { type, nickname: name } = this.getProviderConfig();

// type will be one of our default network names or 'rpc'. the default
// network names are sufficient configuration, simply pass the name as the
// chain argument in the constructor.
if (type !== NETWORK_TYPE_RPC) {
return new Common({ chain: type, hardfork: HARDFORK });
}

// For 'rpc' we need to use the same basic configuration as mainnet,
// since we only support EVM compatible chains, and then override the
// name, chainId and networkId properties. This is done using the
// `forCustomChain` static method on the Common class.
const chainId = parseInt(this._getCurrentChainId(), 16);
const networkId = this.networkStore.getState();

const customChainParams = {
name,
chainId,
// It is improbable for a transaction to be signed while the network
// is loading for two reasons.
// 1. Pending, unconfirmed transactions are wiped on network change
// 2. The UI is unusable (loading indicator) when network is loading.
// setting the networkId to 0 is for type safety and to explicity lead
// the transaction to failing if a user is able to get to this branch
// on a custom network that requires valid network id. I have not ran
// into this limitation on any network I have attempted, even when
// hardcoding networkId to 'loading'.
networkId: networkId === 'loading' ? 0 : parseInt(networkId, 10),
};

return Common.forCustomChain(MAINNET, customChainParams, HARDFORK);
}

/**
Adds a tx to the txlist
@emits ${txMeta.id}:unapproved
Expand Down Expand Up @@ -561,17 +612,22 @@ export default class TransactionController extends EventEmitter {
const txMeta = this.txStateManager.getTransaction(txId);
// add network/chain id
const chainId = this.getChainId();
const txParams = { ...txMeta.txParams, chainId };
const txParams = {
...txMeta.txParams,
chainId,
gasLimit: txMeta.txParams.gas,
};
// sign tx
const fromAddress = txParams.from;
const ethTx = new Transaction(txParams);
await this.signEthTx(ethTx, fromAddress);
const common = this.getCommonConfiguration();
const unsignedEthTx = TransactionFactory.fromTxData(txParams, { common });
const signedEthTx = await this.signEthTx(unsignedEthTx, fromAddress);

// add r,s,v values for provider request purposes see createMetamaskMiddleware
// and JSON rpc standard for further explanation
txMeta.r = bufferToHex(ethTx.r);
txMeta.s = bufferToHex(ethTx.s);
txMeta.v = bufferToHex(ethTx.v);
txMeta.r = bufferToHex(signedEthTx.r);
txMeta.s = bufferToHex(signedEthTx.s);
txMeta.v = bufferToHex(signedEthTx.v);

this.txStateManager.updateTransaction(
txMeta,
Expand All @@ -580,7 +636,7 @@ export default class TransactionController extends EventEmitter {

// set state to signed
this.txStateManager.setTxStatusSigned(txMeta.id);
const rawTx = bufferToHex(ethTx.serialize());
const rawTx = bufferToHex(signedEthTx.serialize());
return rawTx;
}

Expand Down
14 changes: 9 additions & 5 deletions app/scripts/controllers/transactions/index.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { strict as assert } from 'assert';
import EventEmitter from 'events';
import { toBuffer } from 'ethereumjs-util';
import EthTx from 'ethereumjs-tx';
import { TransactionFactory } from '@ethereumjs/tx';
import { ObservableStore } from '@metamask/obs-store';
import sinon from 'sinon';

Expand All @@ -20,6 +20,9 @@ import TransactionController from '.';
const noop = () => true;
const currentNetworkId = '42';
const currentChainId = '0x2a';
const providerConfig = {
type: 'kovan',
};

const VALID_ADDRESS = '0x0000000000000000000000000000000000000000';
const VALID_ADDRESS_TWO = '0x0000000000000000000000000000000000000001';
Expand All @@ -36,6 +39,7 @@ describe('Transaction Controller', function () {
};
provider = createTestProviderTools({ scaffold: providerResultStub })
.provider;

fromAccount = getTestAccounts()[0];
const blockTrackerStub = new EventEmitter();
blockTrackerStub.getCurrentBlock = noop;
Expand All @@ -50,9 +54,9 @@ describe('Transaction Controller', function () {
blockTracker: blockTrackerStub,
signTransaction: (ethTx) =>
new Promise((resolve) => {
ethTx.sign(fromAccount.key);
resolve();
resolve(ethTx.sign(fromAccount.key));
}),
getProviderConfig: () => providerConfig,
getPermittedAccounts: () => undefined,
getCurrentChainId: () => currentChainId,
getParticipateInMetrics: () => false,
Expand Down Expand Up @@ -519,8 +523,8 @@ describe('Transaction Controller', function () {
noop,
);
const rawTx = await txController.signTransaction('1');
const ethTx = new EthTx(toBuffer(rawTx));
assert.equal(ethTx.getChainId(), 42);
const ethTx = TransactionFactory.fromSerializedData(toBuffer(rawTx));
assert.equal(ethTx.common.chainIdBN().toNumber(), 42);
});
});

Expand Down
13 changes: 10 additions & 3 deletions app/scripts/controllers/transactions/tx-gas-utils.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { strict as assert } from 'assert';
import Transaction from 'ethereumjs-tx';
import { TransactionFactory } from '@ethereumjs/tx';
import Common from '@ethereumjs/common';
import { hexToBn, bnToHex } from '../../lib/util';
import TxUtils from './tx-gas-utils';

Expand Down Expand Up @@ -31,8 +32,14 @@ describe('txUtils', function () {
nonce: '0x3',
chainId: 42,
};
const ethTx = new Transaction(txParams);
assert.equal(ethTx.getChainId(), 42, 'chainId is set from tx params');
const ethTx = TransactionFactory.fromTxData(txParams, {
common: new Common({ chain: 'kovan' }),
});
assert.equal(
ethTx.common.chainIdBN().toNumber(),
42,
'chainId is set from tx params',
);
});
});

Expand Down
3 changes: 3 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ export default class MetamaskController extends EventEmitter {
getPermittedAccounts: this.permissionsController.getAccounts.bind(
this.permissionsController,
),
getProviderConfig: this.networkController.getProviderConfig.bind(
this.networkController,
),
networkStore: this.networkController.networkStore,
getCurrentChainId: this.networkController.getCurrentChainId.bind(
this.networkController,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
"3box": "^1.10.2",
"@babel/runtime": "^7.5.5",
"@download/blockies": "^1.0.3",
"@ethereumjs/common": "^2.3.1",
"@ethereumjs/tx": "^3.2.1",
"@formatjs/intl-relativetimeformat": "^5.2.6",
"@fortawesome/fontawesome-free": "^5.13.0",
"@lavamoat/preinstall-always-fail": "^1.0.0",
Expand Down Expand Up @@ -137,7 +139,6 @@
"eth-trezor-keyring": "^0.7.0",
"ethereum-ens-network-map": "^1.0.2",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-tx": "1.3.7",
"ethereumjs-util": "^7.0.10",
"ethereumjs-wallet": "^0.6.4",
"ethers": "^5.0.8",
Expand Down
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1529,7 +1529,7 @@
crc-32 "^1.2.0"
ethereumjs-util "^7.0.10"

"@ethereumjs/tx@^3.1.1", "@ethereumjs/tx@^3.1.4":
"@ethereumjs/tx@^3.1.1", "@ethereumjs/tx@^3.1.4", "@ethereumjs/tx@^3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@ethereumjs/tx/-/tx-3.2.1.tgz#65f5f1c11541764f08377a94ba4b0dcbbd67739e"
integrity sha512-i9V39OtKvwWos1uVNZxdVhd7zFOyzFLjgt69CoiOY0EmXugS0HjO3uxpLBSglDKFMRriuGqw6ddKEv+RP1UNEw==
Expand Down Expand Up @@ -10900,14 +10900,6 @@ ethereumjs-common@^1.1.0, ethereumjs-common@^1.3.2, ethereumjs-common@^1.5.0:
resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.5.1.tgz#4e75042473a64daec0ed9fe84323dd9576aa5dba"
integrity sha512-aVUPRLgmXORGXXEVkFYgPhr9TGtpBY2tGhZ9Uh0A3lIUzUDr1x6kQx33SbjPUkLkX3eniPQnIL/2psjkjrOfcQ==

ethereumjs-tx@1.3.7, ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3, ethereumjs-tx@^1.3.4, ethereumjs-tx@^1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a"
integrity sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==
dependencies:
ethereum-common "^0.0.18"
ethereumjs-util "^5.0.0"

ethereumjs-tx@2.1.2, ethereumjs-tx@^2.1.1, ethereumjs-tx@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz#5dfe7688bf177b45c9a23f86cf9104d47ea35fed"
Expand All @@ -10916,6 +10908,14 @@ ethereumjs-tx@2.1.2, ethereumjs-tx@^2.1.1, ethereumjs-tx@^2.1.2:
ethereumjs-common "^1.5.0"
ethereumjs-util "^6.0.0"

ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3, ethereumjs-tx@^1.3.4, ethereumjs-tx@^1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a"
integrity sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA==
dependencies:
ethereum-common "^0.0.18"
ethereumjs-util "^5.0.0"

ethereumjs-util@6.2.1, ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0, ethereumjs-util@^6.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz#fcb4e4dd5ceacb9d2305426ab1a5cd93e3163b69"
Expand Down