Skip to content

Commit

Permalink
EIP-1559 support for web3.eth.sendTransaction (#4220)
Browse files Browse the repository at this point in the history
* Add txType and pricing helper methods. Refactor logic for sendTransaction

* Add esModuleInterop: true

* Add EIP-1559 test

* Update CHANGELOG

* Add check for ganache network

* add @ethereumjs/common dependency

* Correct if statement conditions

* Update tests

* Debugging ganache tests

* Debugging ganache tests - toHex gasPrice

* Revert debugs

* Debugging e2e_ganache - txPricing all to hex

* Debugging e2e_ganache - revert and console.log

* Remove debugging console.log

* Debugging - console.log

* Debugging - move console logs

* Debug - delete type if 0x0

* Debug - move deletion of type

* Remove debugs

* Debug - add check for method !== eth_sendRawTransaction

* Debug- sanity check

* Debug - minimal code changes

* Debug - forgot to comment old code

* Debug - old code

* Debug - minimal code refactor

* Debug - new code refactored

* Didn't uncomment curly brace
  • Loading branch information
spacesailor24 authored Aug 5, 2021
1 parent d0d82b4 commit c474f8f
Show file tree
Hide file tree
Showing 10 changed files with 5,820 additions and 61 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,4 @@ Released with 1.0.0-beta.37 code base.

- `maxPriorityFeePerGas` and `maxFeePerGas` now included in `_txInputFormatter` (#4217)
- If `maxPriorityFeePerGas` of `maxFeePerGas` present `_txInputFormatter` deletes `tx.gasPrice` (fixes #4211) (#4217)
- Support for EIP-1559 to `web3.eth.sendTransaction` (#4220)
5,598 changes: 5,561 additions & 37 deletions packages/web3-core-method/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/web3-core-method/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"main": "lib/index.js",
"dependencies": {
"@ethereumjs/common": "^2.4.0",
"@ethersproject/transactions": "^5.0.0-beta.135",
"web3-core-helpers": "1.5.0",
"web3-core-promievent": "1.5.0",
Expand Down
145 changes: 131 additions & 14 deletions packages/web3-core-method/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var formatters = require('web3-core-helpers').formatters;
var utils = require('web3-utils');
var promiEvent = require('web3-core-promievent');
var Subscriptions = require('web3-core-subscriptions').subscriptions;
var HardForks = require('@ethereumjs/common').Hardfork;

var EthersTransactionUtils = require('@ethersproject/transactions');

Expand Down Expand Up @@ -770,18 +771,29 @@ Method.prototype.buildCall = function () {
};

// Send the actual transaction
if (isSendTx && !!payload.params[0] && typeof payload.params[0] === 'object' && typeof payload.params[0].gasPrice === 'undefined') {

var getGasPrice = (new Method({
name: 'getGasPrice',
call: 'eth_gasPrice',
params: 0
})).createFunction(method.requestManager);

getGasPrice(function (err, gasPrice) {

if (gasPrice) {
payload.params[0].gasPrice = gasPrice;
if (isSendTx
&& !!payload.params[0]
&& typeof payload.params[0] === 'object'
&& (
typeof payload.params[0].gasPrice === 'undefined'
&& (
typeof payload.params[0].maxPriorityFeePerGas === 'undefined'
|| typeof payload.params[0].maxFeePerGas === 'undefined'
)
)
) {
if (typeof payload.params[0].type === 'undefined')
payload.params[0].type = _handleTxType(payload.params[0]);

_handleTxPricing(method, payload.params[0]).then(txPricing => {
if (txPricing.gasPrice !== undefined) {
payload.params[0].gasPrice = txPricing.gasPrice;
} else if (
txPricing.maxPriorityFeePerGas !== undefined
&& txPricing.maxFeePerGas !== undefined
) {
payload.params[0].maxPriorityFeePerGas = txPricing.maxPriorityFeePerGas;
payload.params[0].maxFeePerGas = txPricing.maxFeePerGas;
}

if (isSendTx) {
Expand All @@ -791,8 +803,7 @@ Method.prototype.buildCall = function () {
}

sendRequest(payload, method);
});

})
} else {
if (isSendTx) {
setTimeout(() => {
Expand All @@ -819,6 +830,112 @@ Method.prototype.buildCall = function () {
return send;
};

function _handleTxType(tx) {
// Taken from https://github.com/ethers-io/ethers.js/blob/2a7ce0e72a1e0c9469e10392b0329e75e341cf18/packages/abstract-signer/src.ts/index.ts#L215
const hasEip1559 = (tx.maxFeePerGas !== undefined || tx.maxPriorityFeePerGas !== undefined);

let txType;

if (tx.type !== undefined) {
txType = utils.toHex(tx.type)
} else if (tx.type === undefined && hasEip1559) {
txType = '0x2'
} else {
txType = '0x0'
}

if (tx.gasPrice !== undefined && (txType === '0x2' || hasEip1559))
throw Error("eip-1559 transactions don't support gasPrice");
if ((txType === '0x1' || txType === '0x0') && hasEip1559)
throw Error("pre-eip-1559 transaction don't support maxFeePerGas/maxPriorityFeePerGas");

if (
hasEip1559 ||
(
(tx.common && tx.common.hardfork && tx.common.hardfork.toLowerCase() === HardForks.London) ||
(tx.hardfork && tx.hardfork.toLowerCase() === HardForks.London)
)
) {
txType = '0x2';
} else if (
tx.accessList ||
(
(tx.common && tx.common.hardfork && tx.common.hardfork.toLowerCase() === HardForks.Berlin) ||
(tx.hardfork && tx.hardfork.toLowerCase() === HardForks.Berlin)
)
) {
txType = '0x1';
}

return txType
}

function _handleTxPricing(method, tx) {
return new Promise((resolve, reject) => {
try {
var getBlockByNumber = (new Method({
name: 'getBlockByNumber',
call: 'eth_getBlockByNumber',
params: 2,
inputFormatter: [function(blockNumber) {
return blockNumber ? utils.toHex(blockNumber) : 'latest'
}, function() {
return false
}]
})).createFunction(method.requestManager);
var getGasPrice = (new Method({
name: 'getGasPrice',
call: 'eth_gasPrice',
params: 0
})).createFunction(method.requestManager);

if (tx.type < '0x2' && tx.gasPrice !== undefined) {
// Legacy transaction, return provided gasPrice
resolve({ gasPrice: tx.gasPrice })
} else {
Promise.all([
getBlockByNumber(),
getGasPrice()
]).then(responses => {
const [block, gasPrice] = responses;
if (
(tx.type === '0x2') &&
block && block.baseFeePerGas
) {
// The network supports EIP-1559

// Taken from https://github.com/ethers-io/ethers.js/blob/ba6854bdd5a912fe873d5da494cb5c62c190adde/packages/abstract-provider/src.ts/index.ts#L230
let maxPriorityFeePerGas, maxFeePerGas;

if (tx.gasPrice) {
// Using legacy gasPrice property on an eip-1559 network,
// so use gasPrice as both fee properties
maxPriorityFeePerGas = tx.gasPrice;
maxFeePerGas = tx.gasPrice;
delete tx.gasPrice;
} else {
maxPriorityFeePerGas = tx.maxPriorityFeePerGas || '0x3B9ACA00'; // 1 Gwei
maxFeePerGas = tx.maxFeePerGas ||
utils.toHex(
utils.toBN(block.baseFeePerGas)
.mul(utils.toBN(2))
.add(utils.toBN(maxPriorityFeePerGas))
);
}
resolve({ maxFeePerGas, maxPriorityFeePerGas });
} else {
if (tx.maxPriorityFeePerGas || tx.maxFeePerGas)
throw Error("Network doesn't support eip-1559")
resolve({ gasPrice });
}
})
}
} catch (error) {
reject(error)
}
})
}

/**
* Returns the revert reason string if existing or otherwise false.
*
Expand Down
3 changes: 2 additions & 1 deletion packages/web3-core-method/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./lib"
"outDir": "./lib",
"esModuleInterop": true
},
"include": [
"./src"
Expand Down
31 changes: 30 additions & 1 deletion test/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -2634,6 +2634,34 @@ var runTests = function(contractFactory) {
var provider = new FakeIpcProvider();
var signature = 'mySend(address,uint256)';

provider.injectResult({
baseFeePerGas: "0x7",
difficulty: "0x6cd6be3a",
extraData: "0x796f75747562652e636f6d2f77617463683f763d6451773477395767586351",
gasLimit: "0x1c9c381",
gasUsed: "0x8dc073",
hash: "0x846880b1158f434884f3637802ed09bac77eafc35b5f03b881ac88ce38a54907",
logsBloom: "0x4020001000000000000000008000010000000000400200000001002140000008000000010000810020000840000204304000081000000b00400010000822200004200020020140000001000882000064000021303200020000400008800000000002202102000084010000090020a8000800002000000010000030300000000000000006001005000040080001010000010040018100004c0050004000000000420000000021000200000010020008100000004000080000000000000040000900080102004002000080210201081014004030200148101000002020108025000018020020102040000204240500010000002200048000401300080088000002",
miner: "0x86864f1edf10eaf105b1bdc6e9aa8232b4c6aa00",
mixHash: "0xa29afb1fa1aea9eeac72ff435a8fc420bbc1fa1be08223eb61f294ee32250bde",
nonce: "0x122af1a5ccd78f3b",
number: "0xa0d600",
parentHash: "0x28f49150e1fe6f245655925b290f59e707d1e5c646dadaa22937169433b30294",
receiptsRoot: "0xc97d4f9980d680053606318a5820261a1dccb556d1056b70f0d48fb384986be5",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: "0x2042",
stateRoot: "0x116981b10423133ade5bd44f03c54cc3c57f4467a1c3d4b0c6d8d33a76c361ad",
timestamp: "0x60dc24ec",
totalDifficulty: "0x78828f2d886cbb",
transactions: [],
transactionsRoot: "0x738f53f745d58169da93ebbd52cc49e0c979d6ca68a6513007b546b19ab78ba4",
uncles: []
});
provider.injectValidation(function (payload) {
assert.equal(payload.method, 'eth_getBlockByNumber');
assert.deepEqual(payload.params, ['latest', false]);
});

provider.injectValidation(function (payload) {
assert.equal(payload.method, 'eth_gasPrice');
assert.deepEqual(payload.params, []);
Expand All @@ -2650,7 +2678,8 @@ var runTests = function(contractFactory) {
'0000000000000000000000000000000000000000000000000000000000000011' ,
to: addressLowercase,
from: addressLowercase,
gasPrice: '0x45656456456456'
gasPrice: '0x45656456456456',
type: '0x0'
}]);

done();
Expand Down
40 changes: 40 additions & 0 deletions test/e2e.method.send.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,26 @@ describe('method.send [ @E2E ]', function () {
assert(web3.utils.isHexStrict(receipt.transactionHash));
});

it('returns a receipt (EIP-1559, maxFeePerGas and maxPriorityFeePerGas specified)', async function () {
// ganache does not support eth_signTransaction
if (process.env.GANACHE || global.window ) return

var nonceVal = await web3.eth.getTransactionCount(accounts[0]);
var receipt = await web3.eth.sendTransaction({
to: accounts[1],
from: accounts[0],
nonce: nonceVal,
value: web3.utils.toHex(web3.utils.toWei('0.1', 'ether')),
gas: web3.utils.toHex(21000),
maxFeePerGas: '0x59682F00', // 1.5 Gwei
maxPriorityFeePerGas: '0x1DCD6500', // .5 Gwei
type: '0x2'
})

assert(receipt.status === true);
assert(web3.utils.isHexStrict(receipt.transactionHash));
});

it('errors on OOG', async function () {
try {
var nonceVal = await web3.eth.getTransactionCount(accounts[0]);
Expand Down Expand Up @@ -137,6 +157,26 @@ describe('method.send [ @E2E ]', function () {
assert(web3.utils.isHexStrict(receipt.transactionHash));
});

it('returns a receipt (EIP-1559, maxFeePerGas and maxPriorityFeePerGas specified)', async function () {
// ganache does not support eth_signTransaction
if (process.env.GANACHE || global.window ) return

var nonceVal = await web3.eth.getTransactionCount(accounts[0]);
var receipt = await web3.eth.sendTransaction({
to: accounts[1],
from: accounts[0],
nonce: nonceVal,
value: web3.utils.toHex(web3.utils.toWei('0.1', 'ether')),
gas: web3.utils.toHex(21000),
maxFeePerGas: '0x59682F00', // 1.5 Gwei
maxPriorityFeePerGas: '0x1DCD6500', // .5 Gwei
type: '0x2'
})

assert(receipt.status === true);
assert(web3.utils.isHexStrict(receipt.transactionHash));
});

it('errors on OOG', async function () {
try {
var nonceVal = await web3.eth.getTransactionCount(accounts[0]);
Expand Down
38 changes: 31 additions & 7 deletions test/eth.sendTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,26 @@ var tests = [{
}
}
},
call: 'eth_gasPrice',
formattedArgs: [],
result: '0x1234567',
formattedResult: '0x1234567',

call2: 'eth_'+ method,
formattedArgs2: [{
call: 'eth_getBlockByNumber',
formattedArgs: ['latest', false],

call2: 'eth_gasPrice',
formattedArgs2: [],
result2: '0x1234567',
formattedResult2: '0x1234567',

call3: 'eth_'+ method,
formattedArgs3: [{
from: "0xdbdbdb2cbd23b783741e8d7fcf51e459b497e4a6",
to: "0xdbdbdb2cbd23b783741e8d7fcf51e459b497e4a6",
value: "0x11f71f76bb1",
gasPrice: "0x1234567"
gasPrice: "0x1234567",
type: "0x0"
}],
result2: '0x1234567'
result3: '0x1234567'
},{
args: [{
from: '0XDBDBDB2CBD23B783741E8D7FCF51E459B497E4A6',
Expand Down Expand Up @@ -249,6 +256,15 @@ describe(method, function () {
});
}

if (test.call3) {
provider.injectResult(clone(test.result3));
provider.injectValidation(function (payload) {
assert.equal(payload.jsonrpc, '2.0');
assert.equal(payload.method, test.call3);
assert.deepEqual(payload.params, test.formattedArgs3 || []);
});
}

provider.injectResult(null);
provider.injectValidation(function (payload) {
assert.equal(payload.method, 'eth_getTransactionReceipt');
Expand Down Expand Up @@ -331,7 +347,15 @@ describe(method, function () {
assert.deepEqual(payload.params, test.formattedArgs2 || []);
});
}


if (test.call3) {
provider.injectResult(clone(test.result3));
provider.injectValidation(function (payload) {
assert.equal(payload.jsonrpc, '2.0');
assert.equal(payload.method, test.call3);
assert.deepEqual(payload.params, test.formattedArgs3 || []);
});
}

provider.injectResult(null);
provider.injectValidation(function (payload) {
Expand Down
16 changes: 16 additions & 0 deletions test/helpers/test.method.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ var runTests = function (obj, method, tests) {
});
}

if (test.call3) {
provider.injectResult(clone(test.result3));
provider.injectValidation(function (payload) {
assert.equal(payload.jsonrpc, '2.0');
assert.equal(payload.method, test.call3);
assert.deepEqual(payload.params, test.formattedArgs3 || []);
});
}

// if notification its sendTransaction, which needs two more results, subscription and receipt
if(test.notification) {
Expand Down Expand Up @@ -198,6 +206,14 @@ var runTests = function (obj, method, tests) {
});
}

if (test.call3) {
provider.injectResult(clone(test.result3));
provider.injectValidation(function (payload) {
assert.equal(payload.jsonrpc, '2.0');
assert.equal(payload.method, test.call3);
assert.deepEqual(payload.params, test.formattedArgs3 || []);
});
}

var args = clone(test.args);

Expand Down
Loading

0 comments on commit c474f8f

Please sign in to comment.