-
Notifications
You must be signed in to change notification settings - Fork 547
Expand file tree
/
Copy pathtest-gas.ts
More file actions
287 lines (261 loc) · 10.2 KB
/
test-gas.ts
File metadata and controls
287 lines (261 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
import { expect } from "chai";
import { step } from "mocha-steps";
import { AbiItem } from "web3-utils";
import InvalidOpcode from "../build/contracts/InvalidOpcode.json";
import Test from "../build/contracts/Test.json";
import StorageLoop from "../build/contracts/StorageLoop.json";
import { GENESIS_ACCOUNT, GENESIS_ACCOUNT_PRIVATE_KEY, FIRST_CONTRACT_ADDRESS, ETH_BLOCK_GAS_LIMIT } from "./config";
import { describeWithFrontier, createAndFinalizeBlock, customRequest } from "./util";
const TEST_ACCOUNT = "0x1111111111111111111111111111111111111111";
// (!) The implementation must match the one in the rpc handler.
// If the variation in the estimate is less than 10%,
// then the estimate is considered sufficiently accurate.
const ESTIMATION_VARIANCE = 10;
function binarySearch(oneOffEstimation) {
let highest = 4_294_967_295; // max(u32)
let lowest = 21000;
let mid = Math.min(oneOffEstimation * 3, (highest + lowest) / 2);
let previousHighest = highest;
while (true) {
if (mid >= oneOffEstimation) {
highest = mid;
if (((previousHighest - highest) * ESTIMATION_VARIANCE) / previousHighest < 1) {
break;
}
previousHighest = highest;
} else {
lowest = mid;
}
mid = Math.floor((highest + lowest) / 2);
}
return highest;
}
function estimationVariance(binarySearchEstimation, oneOffEstimation) {
return ((binarySearchEstimation - oneOffEstimation) * ESTIMATION_VARIANCE) / binarySearchEstimation;
}
describeWithFrontier("Frontier RPC (Gas)", (context) => {
const TEST_CONTRACT_ABI = Test.abi as AbiItem[];
// Those test are ordered. In general this should be avoided, but due to the time it takes
// to spin up a frontier node, it saves a lot of time.
it("eth_estimateGas for contract creation", async function () {
// The value returned as an estimation by the evm with estimate mode ON.
let oneOffEstimation = 196701;
let binarySearchEstimation = binarySearch(oneOffEstimation);
// Sanity check expect a variance of 10%.
expect(estimationVariance(binarySearchEstimation, oneOffEstimation)).to.be.lessThan(1);
expect(
await context.web3.eth.estimateGas({
from: GENESIS_ACCOUNT,
data: Test.bytecode,
})
).to.equal(binarySearchEstimation);
});
it("eth_estimateGas for contract call", async function () {
// The value returned as an estimation by the evm with estimate mode ON.
let oneOffEstimation = 21204;
let binarySearchEstimation = binarySearch(oneOffEstimation);
// Sanity check expect a variance of 10%.
expect(estimationVariance(binarySearchEstimation, oneOffEstimation)).to.be.lessThan(1);
const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, {
from: GENESIS_ACCOUNT,
gasPrice: "0x3B9ACA00",
});
expect(await contract.methods.multiply(3).estimateGas()).to.equal(binarySearchEstimation);
});
it("eth_estimateGas without gas_limit should pass", async function () {
// The value returned as an estimation by the evm with estimate mode ON.
let oneOffEstimation = 21204;
let binarySearchEstimation = binarySearch(oneOffEstimation);
// Sanity check expect a variance of 10%.
expect(estimationVariance(binarySearchEstimation, oneOffEstimation)).to.be.lessThan(1);
const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, {
from: GENESIS_ACCOUNT,
});
expect(await contract.methods.multiply(3).estimateGas()).to.equal(binarySearchEstimation);
});
it("eth_estimateGas should handle AccessList alias", async function () {
// The value returned as an estimation by the evm with estimate mode ON.
// 4300 == 1900 for one key and 2400 for one storage.
let oneOffEstimation = 196701 + 4300;
let binarySearchEstimation = binarySearch(oneOffEstimation);
// Sanity check expect a variance of 10%.
expect(estimationVariance(binarySearchEstimation, oneOffEstimation)).to.be.lessThan(1);
let result = (
await customRequest(context.web3, "eth_estimateGas", [
{
from: GENESIS_ACCOUNT,
data: Test.bytecode,
accessList: [
{
address: "0x0000000000000000000000000000000000000000",
storageKeys: ["0x0000000000000000000000000000000000000000000000000000000000000000"],
},
],
},
])
).result;
expect(result).to.equal(context.web3.utils.numberToHex(binarySearchEstimation));
});
it("eth_estimateGas 0x0 gasPrice is equivalent to not setting one", async function () {
let result = await context.web3.eth.estimateGas({
from: GENESIS_ACCOUNT,
data: Test.bytecode,
gasPrice: "0x0",
});
expect(result).to.equal(197732);
result = await context.web3.eth.estimateGas({
from: GENESIS_ACCOUNT,
data: Test.bytecode,
});
expect(result).to.equal(197732);
});
it("tx gas limit below ETH_BLOCK_GAS_LIMIT", async function () {
const tx = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
data: Test.bytecode,
gas: ETH_BLOCK_GAS_LIMIT - 1,
gasPrice: "0x3B9ACA00",
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
const createReceipt = await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]);
await createAndFinalizeBlock(context.web3);
expect((createReceipt as any).transactionHash).to.be.not.null;
expect((createReceipt as any).blockHash).to.be.not.null;
});
it("tx gas limit equal ETH_BLOCK_GAS_LIMIT", async function () {
const tx = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
data: Test.bytecode,
gas: ETH_BLOCK_GAS_LIMIT,
gasPrice: "0x3B9ACA00",
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
const createReceipt = await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]);
await createAndFinalizeBlock(context.web3);
expect((createReceipt as any).transactionHash).to.be.not.null;
expect((createReceipt as any).blockHash).to.be.not.null;
});
it("tx gas limit larger ETH_BLOCK_GAS_LIMIT", async function () {
const tx = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
data: Test.bytecode,
gas: ETH_BLOCK_GAS_LIMIT + 1,
gasPrice: "0x3B9ACA00",
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
const createReceipt = await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]);
await createAndFinalizeBlock(context.web3);
expect((createReceipt as any).error.message).to.equal("exceeds block gas limit");
});
});
describeWithFrontier("Frontier RPC (Gas limit Weightv2 ref time)", (context) => {
const STORAGE_LOOP_CONTRACT_BYTECODE = StorageLoop.bytecode;
const STORAGE_LOOP_CONTRACT_ABI = StorageLoop.abi as AbiItem[];
// First call to contract storageLoop method
const FIRST_CALL = 752_450;
// Rest of calls
const CALL_COST = 735_350;
// Block gas limit
const BLOCK_GAS_LIMIT = ETH_BLOCK_GAS_LIMIT - FIRST_CALL;
// Number of calls per block
const CALLS_PER_BLOCK = Math.floor(BLOCK_GAS_LIMIT / CALL_COST) + 1;
// Available space left after all calls
const REMNANT = Math.floor(ETH_BLOCK_GAS_LIMIT - (CALL_COST * (CALLS_PER_BLOCK - 1) + FIRST_CALL));
// Number of transfers per available space left
const TRANSFERS_PER_BLOCK = Math.floor(REMNANT / 21_000);
before("create the contract", async function () {
const tx = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
data: STORAGE_LOOP_CONTRACT_BYTECODE,
value: "0x00",
gasPrice: "0x3B9ACA00",
gas: "0x100000",
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]);
await createAndFinalizeBlock(context.web3);
});
step("gas limit bound works with ref time heavy txns", async function () {
this.timeout(10000);
const contract = new context.web3.eth.Contract(STORAGE_LOOP_CONTRACT_ABI, FIRST_CONTRACT_ADDRESS, {
from: GENESIS_ACCOUNT,
gasPrice: "0x3B9ACA00",
});
let nonce = await context.web3.eth.getTransactionCount(GENESIS_ACCOUNT);
for (var i = 0; i < CALLS_PER_BLOCK; i++) {
let data = contract.methods.storageLoop(1000, TEST_ACCOUNT, i);
let tx = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
to: contract.options.address,
data: data.encodeABI(),
gasPrice: "0x3B9ACA00",
gas: "0x100000",
nonce,
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]);
nonce++;
}
// because we are using Math.floor for everything, at the end there is room for an additional
// transfer.
for (var i = 0; i < TRANSFERS_PER_BLOCK + 1; i++) {
const tx = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
to: "0x2111111111111111111111111111111111111111",
value: "0x1",
gasPrice: "0x3B9ACA00",
gas: "0x5208",
nonce,
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
let r = await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction]);
nonce++;
}
await createAndFinalizeBlock(context.web3);
let latest = await context.web3.eth.getBlock("latest");
expect(latest.transactions.length).to.be.eq(CALLS_PER_BLOCK + TRANSFERS_PER_BLOCK + 1);
expect(latest.gasUsed).to.be.lessThanOrEqual(ETH_BLOCK_GAS_LIMIT);
expect(ETH_BLOCK_GAS_LIMIT - latest.gasUsed).to.be.lessThan(21_000);
});
});
describeWithFrontier("Frontier RPC (Invalid opcode estimate gas)", (context) => {
const INVALID_OPCODE_BYTECODE = InvalidOpcode.bytecode;
let contractAddess;
before(async () => {
const tx = await context.web3.eth.accounts.signTransaction(
{
from: GENESIS_ACCOUNT,
data: INVALID_OPCODE_BYTECODE,
value: "0x00",
gasPrice: "0x3B9ACA00",
gas: "0x100000",
},
GENESIS_ACCOUNT_PRIVATE_KEY
);
const txHash = (await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction])).result;
await createAndFinalizeBlock(context.web3);
contractAddess = (await context.web3.eth.getTransactionReceipt(txHash)).contractAddress;
});
it("should estimate gas with invalid opcode", async function () {
let estimate = await context.web3.eth.estimateGas({
from: GENESIS_ACCOUNT,
to: contractAddess,
data: "0x28b5e32b", // selector for the contract's `call` method
});
// The actual estimated value is irrelevant for this test purposes, we just want to verify that
// the binary search is not interrupted when an InvalidCode is returned by the evm.
expect(estimate).to.equal(85703);
});
});