Commit 9343f33e authored by Mark Tyneway's avatar Mark Tyneway

integration-tests: add berlin hardfork tests

Add integration tests to test the hardfork logic
parent c201aeba
---
'@eth-optimism/integration-tests': patch
---
Add in berlin hardfork tests
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract Precompiles {
function expmod(uint256 base, uint256 e, uint256 m) public returns (uint256 o) {
assembly {
// define pointer
let p := mload(0x40)
// store data assembly-favouring ways
mstore(p, 0x20) // Length of Base
mstore(add(p, 0x20), 0x20) // Length of Exponent
mstore(add(p, 0x40), 0x20) // Length of Modulus
mstore(add(p, 0x60), base) // Base
mstore(add(p, 0x80), e) // Exponent
mstore(add(p, 0xa0), m) // Modulus
if iszero(staticcall(sub(gas(), 2000), 0x05, p, 0xc0, p, 0x20)) {
revert(0, 0)
}
// data
o := mload(p)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract SelfDestruction {
bytes32 public data = 0x0000000000000000000000000000000000000000000000000000000061626364;
function setData(bytes32 _data) public {
data = _data;
}
function destruct() public {
address payable self = payable(address(this));
selfdestruct(self);
}
}
import { expect } from './shared/setup'
import { Contract, BigNumber } from 'ethers'
import { ethers } from 'hardhat'
import { OptimismEnv } from './shared/env'
const traceToGasByOpcode = (structLogs, opcode) => {
let gas = 0
const opcodes = []
for (const log of structLogs) {
if (log.op === opcode) {
opcodes.push(opcode)
gas += log.gasCost
}
}
return gas
}
describe('Hard forks', () => {
let env: OptimismEnv
let SimpleStorage: Contract
let SelfDestruction: Contract
let Precompiles: Contract
before(async () => {
env = await OptimismEnv.new()
const Factory__SimpleStorage = await ethers.getContractFactory(
'SimpleStorage',
env.l2Wallet
)
SimpleStorage = await Factory__SimpleStorage.deploy()
const Factory__SelfDestruction = await ethers.getContractFactory(
'SelfDestruction',
env.l2Wallet
)
SelfDestruction = await Factory__SelfDestruction.deploy()
const Factory__Precompiles = await ethers.getContractFactory(
'Precompiles',
env.l2Wallet
)
Precompiles = await Factory__Precompiles.deploy()
})
describe('Berlin', () => {
// https://eips.ethereum.org/EIPS/eip-2929
describe('EIP-2929', () => {
it('should update the gas schedule', async () => {
// Get the tip height
const tip = await env.l2Provider.getBlock('latest')
// send a transaction to be able to trace
const tx = await SimpleStorage.setValueNotXDomain(
`0x${'77'.repeat(32)}`
)
await tx.wait()
// Collect the traces
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number + 10 } }]
)
expect(berlinTrace.gas).to.not.eq(preBerlinTrace.gas)
const berlinSstoreCosts = traceToGasByOpcode(
berlinTrace.structLogs,
'SSTORE'
)
const preBerlinSstoreCosts = traceToGasByOpcode(
preBerlinTrace.structLogs,
'SSTORE'
)
expect(berlinSstoreCosts).to.not.eq(preBerlinSstoreCosts)
})
})
// https://eips.ethereum.org/EIPS/eip-2565
describe('EIP-2565', async () => {
it('should become cheaper', async () => {
const tip = await env.l2Provider.getBlock('latest')
const tx = await Precompiles.expmod(64, 1, 64, { gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number + 10 } }]
)
expect(berlinTrace.gas).to.be.lt(preBerlinTrace.gas)
})
})
})
// Optimism includes EIP-3529 as part of its Berlin hardfork. It is part
// of the London hardfork on L1. Since it is coupled to the Berlin
// hardfork, some of its functionality cannot be directly tests via
// integration tests since we can currently only turn on all of the Berlin
// EIPs or none of the Berlin EIPs
describe('Berlin Additional (L1 London)', () => {
// https://eips.ethereum.org/EIPS/eip-3529
describe('EIP-3529', async () => {
const bytes32Zero = '0x' + '00'.repeat(32)
const bytes32NonZero = '0x' + 'ff'.repeat(32)
it('should lower the refund for storage clear', async () => {
const tip = await env.l2Provider.getBlock('latest')
const value = await SelfDestruction.callStatic.data()
// It should be non zero
expect(BigNumber.from(value).toNumber()).to.not.eq(0)
{
// Set the value to another non zero value
// Going from non zero to non zero
const tx = await SelfDestruction.setData(bytes32NonZero, {
gasLimit: 5_000_000,
})
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number + 10 } }]
)
// Updating a non zero value to another non zero value should not change
expect(berlinTrace.gas).to.deep.eq(preBerlinTrace.gas)
}
{
// Set the value to the zero value
// Going from non zero to zero
const tx = await SelfDestruction.setData(bytes32Zero, {
gasLimit: 5_000_000,
})
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number + 10 } }]
)
// Updating to a zero value from a non zero value should becomes
// more expensive due to this change being coupled with EIP-2929
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
}
{
// Set the value to a non zero value
// Going from zero to non zero
const tx = await SelfDestruction.setData(bytes32NonZero, {
gasLimit: 5_000_000,
})
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number + 10 } }]
)
// Updating to a zero value from a non zero value should becomes
// more expensive due to this change being coupled with EIP-2929
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
}
})
it('should remove the refund for selfdestruct', async () => {
const tip = await env.l2Provider.getBlock('latest')
// Send transaction with a large gas limit
const tx = await SelfDestruction.destruct({ gasLimit: 5_000_000 })
await tx.wait()
const berlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash]
)
const preBerlinTrace = await env.l2Provider.send(
'debug_traceTransaction',
[tx.hash, { overrides: { berlinBlock: tip.number + 10 } }]
)
// The berlin execution should use more gas than the pre Berlin
// execution because there is no longer a selfdestruct gas
// refund
expect(berlinTrace.gas).to.be.gt(preBerlinTrace.gas)
})
})
})
})
...@@ -27,6 +27,7 @@ describe('Basic RPC tests', () => { ...@@ -27,6 +27,7 @@ describe('Basic RPC tests', () => {
const provider = injectL2Context(l2Provider) const provider = injectL2Context(l2Provider)
let Reverter: Contract let Reverter: Contract
let ValueContext: Contract
let revertMessage: string let revertMessage: string
let revertingTx: TransactionRequest let revertingTx: TransactionRequest
let revertingDeployTx: TransactionRequest let revertingDeployTx: TransactionRequest
...@@ -52,6 +53,12 @@ describe('Basic RPC tests', () => { ...@@ -52,6 +53,12 @@ describe('Basic RPC tests', () => {
revertingDeployTx = { revertingDeployTx = {
data: Factory__ConstructorReverter.bytecode, data: Factory__ConstructorReverter.bytecode,
} }
// Deploy a contract to check msg.value of the call
const Factory__ValueContext: ContractFactory =
await ethers.getContractFactory('ValueContext', wallet)
ValueContext = await Factory__ValueContext.deploy()
await ValueContext.deployTransaction.wait()
}) })
describe('eth_sendRawTransaction', () => { describe('eth_sendRawTransaction', () => {
...@@ -211,12 +218,6 @@ describe('Basic RPC tests', () => { ...@@ -211,12 +218,6 @@ describe('Basic RPC tests', () => {
}) })
it('should allow eth_calls with nonzero value', async () => { it('should allow eth_calls with nonzero value', async () => {
// Deploy a contract to check msg.value of the call
const Factory__ValueContext: ContractFactory =
await ethers.getContractFactory('ValueContext', wallet)
const ValueContext: Contract = await Factory__ValueContext.deploy()
await ValueContext.deployTransaction.wait()
// Fund account to call from // Fund account to call from
const from = wallet.address const from = wallet.address
const value = 15 const value = 15
...@@ -236,12 +237,6 @@ describe('Basic RPC tests', () => { ...@@ -236,12 +237,6 @@ describe('Basic RPC tests', () => {
// https://github.com/ethereum-optimism/optimism/issues/1998 // https://github.com/ethereum-optimism/optimism/issues/1998
it('should use address(0) as the default "from" value', async () => { it('should use address(0) as the default "from" value', async () => {
// Deploy a contract to check msg.caller
const Factory__ValueContext: ContractFactory =
await ethers.getContractFactory('ValueContext', wallet)
const ValueContext: Contract = await Factory__ValueContext.deploy()
await ValueContext.deployTransaction.wait()
// Do the call and check msg.sender // Do the call and check msg.sender
const data = ValueContext.interface.encodeFunctionData('getCaller') const data = ValueContext.interface.encodeFunctionData('getCaller')
const res = await provider.call({ const res = await provider.call({
...@@ -258,12 +253,6 @@ describe('Basic RPC tests', () => { ...@@ -258,12 +253,6 @@ describe('Basic RPC tests', () => {
}) })
it('should correctly use the "from" value', async () => { it('should correctly use the "from" value', async () => {
// Deploy a contract to check msg.caller
const Factory__ValueContext: ContractFactory =
await ethers.getContractFactory('ValueContext', wallet)
const ValueContext: Contract = await Factory__ValueContext.deploy()
await ValueContext.deployTransaction.wait()
const from = wallet.address const from = wallet.address
// Do the call and check msg.sender // Do the call and check msg.sender
...@@ -280,6 +269,15 @@ describe('Basic RPC tests', () => { ...@@ -280,6 +269,15 @@ describe('Basic RPC tests', () => {
) )
expect(paddedRes).to.eq(from) expect(paddedRes).to.eq(from)
}) })
it('should be deterministic', async () => {
let res = await ValueContext.callStatic.getSelfBalance()
for (let i = 0; i < 10; i++) {
const next = await ValueContext.callStatic.getSelfBalance()
expect(res.toNumber()).to.deep.eq(next.toNumber())
res = next
}
})
}) })
describe('eth_getTransactionReceipt', () => { describe('eth_getTransactionReceipt', () => {
...@@ -453,7 +451,7 @@ describe('Basic RPC tests', () => { ...@@ -453,7 +451,7 @@ describe('Basic RPC tests', () => {
}) })
describe('eth_estimateGas', () => { describe('eth_estimateGas', () => {
it('gas estimation is deterministic', async () => { it('simple send gas estimation is deterministic', async () => {
let lastEstimate: BigNumber let lastEstimate: BigNumber
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const estimate = await l2Provider.estimateGas({ const estimate = await l2Provider.estimateGas({
...@@ -469,6 +467,15 @@ describe('Basic RPC tests', () => { ...@@ -469,6 +467,15 @@ describe('Basic RPC tests', () => {
} }
}) })
it('deterministic gas estimation for evm execution', async () => {
let res = await ValueContext.estimateGas.getSelfBalance()
for (let i = 0; i < 10; i++) {
const next = await ValueContext.estimateGas.getSelfBalance()
expect(res.toNumber()).to.deep.eq(next.toNumber())
res = next
}
})
it('should return a gas estimate for txs with empty data', async () => { it('should return a gas estimate for txs with empty data', async () => {
const estimate = await l2Provider.estimateGas({ const estimate = await l2Provider.estimateGas({
to: defaultTransactionFactory().to, to: defaultTransactionFactory().to,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment