Skip to content

Commit

Permalink
Merge pull request #1383 from nomiclabs/local-accounts-provider-eip2930
Browse files Browse the repository at this point in the history
Add support for EIP-2930 txs to LocalAccountsProvider
  • Loading branch information
alcuadrado authored Apr 8, 2021
2 parents e63f330 + 4753312 commit 8795510
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 8 deletions.
33 changes: 26 additions & 7 deletions packages/hardhat-core/src/internal/core/providers/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Transaction as TransactionT } from "@ethereumjs/tx";
import { BN } from "ethereumjs-util";
import * as t from "io-ts";

Expand Down Expand Up @@ -188,9 +187,11 @@ export class LocalAccountsProvider extends ProviderWrapperWithChainId {
chainId: number,
privateKey: Buffer
): Promise<Buffer> {
const chains = await import("@ethereumjs/common/dist/chains");
const { chains } = await import("@ethereumjs/common/dist/chains");

const { Transaction } = await import("@ethereumjs/tx");
const { AccessListEIP2930Transaction, Transaction } = await import(
"@ethereumjs/tx"
);

const { default: Common } = await import("@ethereumjs/common");

Expand All @@ -200,18 +201,36 @@ export class LocalAccountsProvider extends ProviderWrapperWithChainId {
};

const common =
chains.chains.names[chainId] !== undefined
? new Common({ chain: chainId })
chains.names[chainId] !== undefined
? new Common({ chain: chainId, hardfork: "berlin" })
: Common.forCustomChain(
"mainnet",
{
chainId,
networkId: chainId,
},
"istanbul"
"berlin"
);

const transaction = Transaction.fromTxData(txData, { common });
let transaction;
if (txData.accessList !== undefined) {
// we convert the access list to the type
// that AccessListEIP2930Transaction expects
const accessList = txData.accessList.map(
({ address, storageKeys }) =>
[address, storageKeys] as [Buffer, Buffer[]]
);

transaction = AccessListEIP2930Transaction.fromTxData(
{
...txData,
accessList,
},
{ common }
);
} else {
transaction = Transaction.fromTxData(txData, { common });
}

const signedTransaction = transaction.sign(privateKey);

Expand Down
121 changes: 120 additions & 1 deletion packages/hardhat-core/test/internal/core/providers/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Common from "@ethereumjs/common";
import { AccessListEIP2930Transaction } from "@ethereumjs/tx";
import { assert } from "chai";
import { bufferToHex, privateToAddress, toBuffer } from "ethereumjs-util";

Expand All @@ -23,6 +25,8 @@ function privateKeyToAddress(privateKey: string): string {
return bufferToHex(privateToAddress(toBuffer(privateKey))).toLowerCase();
}

const MOCK_PROVIDER_CHAIN_ID = 123;

describe("Local accounts provider", () => {
let mock: MockedProvider;
let wrapper: EIP1193Provider;
Expand All @@ -35,7 +39,10 @@ describe("Local accounts provider", () => {

beforeEach(() => {
mock = new MockedProvider();
mock.setReturnValue("net_version", numberToRpcQuantity(123));
mock.setReturnValue(
"net_version",
numberToRpcQuantity(MOCK_PROVIDER_CHAIN_ID)
);
mock.setReturnValue("eth_getTransactionCount", numberToRpcQuantity(0x8));
mock.setReturnValue("eth_accounts", []);

Expand Down Expand Up @@ -166,6 +173,85 @@ describe("Local accounts provider", () => {
assert.equal(mock.getNumberOfCalls("eth_getTransactionCount"), 1);
});

it("should send access list transactions", async () => {
const tx = {
from: "0xb5bc06d4548a3ac17d72b372ae1e416bf65b8ead",
to: "0xb5bc06d4548a3ac17d72b372ae1e416bf65b8ead",
gas: numberToRpcQuantity(30000),
gasPrice: numberToRpcQuantity(1),
nonce: numberToRpcQuantity(0),
value: numberToRpcQuantity(1),
chainId: numberToRpcQuantity(MOCK_PROVIDER_CHAIN_ID),
accessList: [
{
address: "0x57d7ad4d3f0c74e3766874cf06fa1dc23c21f7e8",
storageKeys: [
"0xa50e92910457911e0e22d6dd1672f440a37b590b231d8309101255290f5394ec",
],
},
],
};
await wrapper.request({
method: "eth_sendTransaction",
params: [tx],
});

const rawTransaction = mock.getLatestParams("eth_sendRawTransaction")[0];

// this is a valid raw EIP_2930 tx
// checked in a local hardhat node, where the sender account
// had funds and the chain id was 123
const expectedRaw =
"0x01f89a7b800182753094b5bc06d4548a3ac17d72b372ae1e416bf65b8e" +
"ad0180f838f79457d7ad4d3f0c74e3766874cf06fa1dc23c21f7e8e1a0a5" +
"0e92910457911e0e22d6dd1672f440a37b590b231d8309101255290f5394" +
"ec80a02b2fca5e2cf3569d29693e965f045529efa6a54bf0ab11104dd4ea" +
"8b2ca3daf7a06025c30f36a179a09b9952e025632a65f220ec385eccd23a" +
"1fb952976eace481";

assert.equal(rawTransaction, expectedRaw);

validateRawEIP2930Transaction(expectedRaw, tx);
});

it("should add the chainId value if it's missing", async () => {
const tx = {
from: "0xb5bc06d4548a3ac17d72b372ae1e416bf65b8ead",
to: "0xb5bc06d4548a3ac17d72b372ae1e416bf65b8ead",
gas: numberToRpcQuantity(30000),
gasPrice: numberToRpcQuantity(1),
nonce: numberToRpcQuantity(0),
value: numberToRpcQuantity(1),
accessList: [
{
address: "0x57d7ad4d3f0c74e3766874cf06fa1dc23c21f7e8",
storageKeys: [
"0xa50e92910457911e0e22d6dd1672f440a37b590b231d8309101255290f5394ec",
],
},
],
};
await wrapper.request({
method: "eth_sendTransaction",
params: [tx],
});

const rawTransaction = mock.getLatestParams("eth_sendRawTransaction")[0];

// see previous test
const expectedRaw =
"0x01f89a7b800182753094b5bc06d4548a3ac17d72b372ae1e416bf65b8e" +
"ad0180f838f79457d7ad4d3f0c74e3766874cf06fa1dc23c21f7e8e1a0a5" +
"0e92910457911e0e22d6dd1672f440a37b590b231d8309101255290f5394" +
"ec80a02b2fca5e2cf3569d29693e965f045529efa6a54bf0ab11104dd4ea" +
"8b2ca3daf7a06025c30f36a179a09b9952e025632a65f220ec385eccd23a" +
"1fb952976eace481";

assert.equal(rawTransaction, expectedRaw);

validateRawEIP2930Transaction(expectedRaw, tx);
});

describe("eth_sign", () => {
it("Should be compatible with parity's implementation", async () => {
// This test was created by using Parity Ethereum
Expand Down Expand Up @@ -436,3 +522,36 @@ describe("Sender providers", () => {
});
});
});

/**
* Validate that `rawTx` is an EIP-2930 transaction that has
* the same values as `tx`
*/
function validateRawEIP2930Transaction(rawTx: string, tx: any) {
const common = Common.forCustomChain(
"mainnet",
{ chainId: MOCK_PROVIDER_CHAIN_ID },
"berlin"
);

const sentTx = AccessListEIP2930Transaction.fromSerializedTx(
toBuffer(rawTx),
{ common }
);

const accessList = sentTx.accessList.map(([address, storageKeys]) => {
return {
address: bufferToHex(address),
storageKeys: storageKeys.map(bufferToHex),
};
});

assert.equal(sentTx.getSenderAddress().toString(), tx.from);
assert.equal(sentTx.to?.toString(), tx.to);

assert.equal(numberToRpcQuantity(sentTx.gasLimit), tx.gas);
assert.equal(numberToRpcQuantity(sentTx.gasPrice), tx.gasPrice);
assert.equal(numberToRpcQuantity(sentTx.nonce), tx.nonce);
assert.equal(numberToRpcQuantity(sentTx.value), tx.value);
assert.deepEqual(accessList, tx.accessList);
}

0 comments on commit 8795510

Please sign in to comment.