mirror of https://github.com/hyperledger/besu
[NC-1579] EIP-1283: Net gas metering for SSTORE (#10)
As per EIP-1283: https://eips.ethereum.org/EIPS/eip-1283
parent
2549f5ee6a
commit
be98c0dcd4
@ -0,0 +1,90 @@ |
|||||||
|
package net.consensys.pantheon.ethereum.core; |
||||||
|
|
||||||
|
import net.consensys.pantheon.crypto.SECP256K1.Signature; |
||||||
|
import net.consensys.pantheon.ethereum.db.WorldStateArchive; |
||||||
|
import net.consensys.pantheon.ethereum.mainnet.MainnetMessageCallProcessor; |
||||||
|
import net.consensys.pantheon.ethereum.mainnet.PrecompileContractRegistry; |
||||||
|
import net.consensys.pantheon.ethereum.mainnet.ProtocolSchedule; |
||||||
|
import net.consensys.pantheon.ethereum.mainnet.ProtocolSpec; |
||||||
|
import net.consensys.pantheon.ethereum.vm.Code; |
||||||
|
import net.consensys.pantheon.ethereum.vm.MessageFrame; |
||||||
|
import net.consensys.pantheon.ethereum.vm.MessageFrame.Type; |
||||||
|
import net.consensys.pantheon.ethereum.vm.OperationTracer; |
||||||
|
import net.consensys.pantheon.util.bytes.BytesValue; |
||||||
|
|
||||||
|
import java.math.BigInteger; |
||||||
|
import java.util.ArrayDeque; |
||||||
|
import java.util.Deque; |
||||||
|
import java.util.function.Consumer; |
||||||
|
|
||||||
|
public class TestCodeExecutor { |
||||||
|
|
||||||
|
private final ExecutionContextTestFixture fixture; |
||||||
|
private final BlockHeader blockHeader = new BlockHeaderTestFixture().number(13).buildHeader(); |
||||||
|
private static final Address SENDER_ADDRESS = AddressHelpers.ofValue(244259721); |
||||||
|
|
||||||
|
public TestCodeExecutor(final ProtocolSchedule<Void> protocolSchedule) { |
||||||
|
fixture = new ExecutionContextTestFixture(protocolSchedule); |
||||||
|
} |
||||||
|
|
||||||
|
public MessageFrame executeCode( |
||||||
|
final String code, final long gasLimit, final Consumer<MutableAccount> accountSetup) { |
||||||
|
final ProtocolSpec<Void> protocolSpec = fixture.getProtocolSchedule().getByBlockNumber(0); |
||||||
|
final WorldUpdater worldState = |
||||||
|
createInitialWorldState(accountSetup, fixture.getStateArchive()); |
||||||
|
final Deque<MessageFrame> messageFrameStack = new ArrayDeque<>(); |
||||||
|
|
||||||
|
final MainnetMessageCallProcessor messageCallProcessor = |
||||||
|
new MainnetMessageCallProcessor(protocolSpec.getEvm(), new PrecompileContractRegistry()); |
||||||
|
|
||||||
|
final Transaction transaction = |
||||||
|
Transaction.builder() |
||||||
|
.value(Wei.ZERO) |
||||||
|
.sender(SENDER_ADDRESS) |
||||||
|
.signature(Signature.create(BigInteger.ONE, BigInteger.TEN, (byte) 1)) |
||||||
|
.gasLimit(gasLimit) |
||||||
|
.to(SENDER_ADDRESS) |
||||||
|
.payload(BytesValue.EMPTY) |
||||||
|
.gasPrice(Wei.ZERO) |
||||||
|
.nonce(0) |
||||||
|
.build(); |
||||||
|
final MessageFrame initialFrame = |
||||||
|
MessageFrame.builder() |
||||||
|
.type(Type.MESSAGE_CALL) |
||||||
|
.messageFrameStack(messageFrameStack) |
||||||
|
.blockchain(fixture.getBlockchain()) |
||||||
|
.worldState(worldState) |
||||||
|
.initialGas(Gas.of(gasLimit)) |
||||||
|
.address(SENDER_ADDRESS) |
||||||
|
.originator(SENDER_ADDRESS) |
||||||
|
.contract(SENDER_ADDRESS) |
||||||
|
.gasPrice(transaction.getGasPrice()) |
||||||
|
.inputData(transaction.getPayload()) |
||||||
|
.sender(SENDER_ADDRESS) |
||||||
|
.value(transaction.getValue()) |
||||||
|
.apparentValue(transaction.getValue()) |
||||||
|
.code(new Code(BytesValue.fromHexString(code))) |
||||||
|
.blockHeader(blockHeader) |
||||||
|
.depth(0) |
||||||
|
.completer(c -> {}) |
||||||
|
.build(); |
||||||
|
messageFrameStack.addFirst(initialFrame); |
||||||
|
|
||||||
|
while (!messageFrameStack.isEmpty()) { |
||||||
|
messageCallProcessor.process(messageFrameStack.peekFirst(), OperationTracer.NO_TRACING); |
||||||
|
} |
||||||
|
return initialFrame; |
||||||
|
} |
||||||
|
|
||||||
|
private WorldUpdater createInitialWorldState( |
||||||
|
final Consumer<MutableAccount> accountSetup, final WorldStateArchive stateArchive) { |
||||||
|
final MutableWorldState initialWorldState = stateArchive.getMutable(); |
||||||
|
|
||||||
|
final WorldUpdater worldState = initialWorldState.updater(); |
||||||
|
final MutableAccount senderAccount = worldState.getOrCreate(TestCodeExecutor.SENDER_ADDRESS); |
||||||
|
accountSetup.accept(senderAccount); |
||||||
|
worldState.commit(); |
||||||
|
initialWorldState.persist(); |
||||||
|
return stateArchive.getMutable(initialWorldState.rootHash()).updater(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
package net.consensys.pantheon.ethereum.mainnet; |
||||||
|
|
||||||
|
import static net.consensys.pantheon.util.uint.UInt256.ONE; |
||||||
|
import static net.consensys.pantheon.util.uint.UInt256.ZERO; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
import net.consensys.pantheon.ethereum.core.Gas; |
||||||
|
import net.consensys.pantheon.util.uint.UInt256; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.junit.runners.Parameterized; |
||||||
|
import org.junit.runners.Parameterized.Parameter; |
||||||
|
import org.junit.runners.Parameterized.Parameters; |
||||||
|
|
||||||
|
@RunWith(Parameterized.class) |
||||||
|
public class ConstantinopleSstoreGasTest { |
||||||
|
|
||||||
|
private static final UInt256 TWO = UInt256.of(2); |
||||||
|
|
||||||
|
private final ConstantinopleGasCalculator gasCalculator = new ConstantinopleGasCalculator(); |
||||||
|
|
||||||
|
@Parameters(name = "original: {0}, current: {1}, new: {2}") |
||||||
|
public static Object[][] scenarios() { |
||||||
|
return new Object[][] { |
||||||
|
// Zero no-op
|
||||||
|
{ZERO, ZERO, ZERO, Gas.of(200), Gas.ZERO}, |
||||||
|
|
||||||
|
// Zero fresh change
|
||||||
|
{ZERO, ZERO, ONE, Gas.of(20_000), Gas.ZERO}, |
||||||
|
|
||||||
|
// Dirty, reset to zero
|
||||||
|
{ZERO, ONE, ZERO, Gas.of(200), Gas.of(19800)}, |
||||||
|
|
||||||
|
// Dirty, changed but not reset
|
||||||
|
{ZERO, ONE, TWO, Gas.of(200), Gas.ZERO}, |
||||||
|
|
||||||
|
// Dirty no-op
|
||||||
|
{ZERO, ONE, ONE, Gas.of(200), Gas.ZERO}, |
||||||
|
|
||||||
|
// Dirty, zero no-op
|
||||||
|
{ONE, ZERO, ZERO, Gas.of(200), Gas.ZERO}, |
||||||
|
|
||||||
|
// Dirty, reset to non-zero
|
||||||
|
{ONE, ZERO, ONE, Gas.of(200), Gas.of(-15000).plus(Gas.of(4800))}, |
||||||
|
|
||||||
|
// Fresh change to zero
|
||||||
|
{ONE, ONE, ZERO, Gas.of(5000), Gas.of(15000)}, |
||||||
|
|
||||||
|
// Fresh change with all non-zero
|
||||||
|
{ONE, ONE, TWO, Gas.of(5000), Gas.ZERO}, |
||||||
|
|
||||||
|
// Dirty, clear originally set value
|
||||||
|
{ONE, TWO, ZERO, Gas.of(200), Gas.of(15000)}, |
||||||
|
|
||||||
|
// Non-zero no-op
|
||||||
|
{ONE, ONE, ONE, Gas.of(200), Gas.ZERO}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
@Parameter public UInt256 originalValue; |
||||||
|
|
||||||
|
@Parameter(value = 1) |
||||||
|
public UInt256 currentValue; |
||||||
|
|
||||||
|
@Parameter(value = 2) |
||||||
|
public UInt256 newValue; |
||||||
|
|
||||||
|
@Parameter(value = 3) |
||||||
|
public Gas expectedGasCost; |
||||||
|
|
||||||
|
@Parameter(value = 4) |
||||||
|
public Gas expectedGasRefund; |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldChargeCorrectGas() { |
||||||
|
assertThat(gasCalculator.calculateStorageCost(() -> originalValue, currentValue, newValue)) |
||||||
|
.isEqualTo(expectedGasCost); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldRefundCorrectGas() { |
||||||
|
assertThat( |
||||||
|
gasCalculator.calculateStorageRefundAmount(() -> originalValue, currentValue, newValue)) |
||||||
|
.isEqualTo(expectedGasRefund); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,79 @@ |
|||||||
|
package net.consensys.pantheon.ethereum.vm.operations; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
import net.consensys.pantheon.ethereum.core.Gas; |
||||||
|
import net.consensys.pantheon.ethereum.core.TestCodeExecutor; |
||||||
|
import net.consensys.pantheon.ethereum.mainnet.MainnetProtocolSchedule; |
||||||
|
import net.consensys.pantheon.ethereum.mainnet.ProtocolSchedule; |
||||||
|
import net.consensys.pantheon.ethereum.vm.MessageFrame; |
||||||
|
import net.consensys.pantheon.ethereum.vm.MessageFrame.State; |
||||||
|
import net.consensys.pantheon.util.uint.UInt256; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.junit.runners.Parameterized; |
||||||
|
import org.junit.runners.Parameterized.Parameter; |
||||||
|
import org.junit.runners.Parameterized.Parameters; |
||||||
|
|
||||||
|
@RunWith(Parameterized.class) |
||||||
|
public class ConstantinopleSStoreOperationGasCostTest { |
||||||
|
private static final ProtocolSchedule<Void> protocolSchedule = |
||||||
|
MainnetProtocolSchedule.create(0, 0, 0, 0, 0, 0, 1); |
||||||
|
|
||||||
|
@Parameters(name = "Code: {0}, Original: {1}") |
||||||
|
public static Object[][] scenarios() { |
||||||
|
// Tests specified in EIP-1283.
|
||||||
|
return new Object[][] { |
||||||
|
{"0x60006000556000600055", 0, 412, 0}, |
||||||
|
{"0x60006000556001600055", 0, 20212, 0}, |
||||||
|
{"0x60016000556000600055", 0, 20212, 19800}, |
||||||
|
{"0x60016000556002600055", 0, 20212, 0}, |
||||||
|
{"0x60016000556001600055", 0, 20212, 0}, |
||||||
|
{"0x60006000556000600055", 1, 5212, 15000}, |
||||||
|
{"0x60006000556001600055", 1, 5212, 4800}, |
||||||
|
{"0x60006000556002600055", 1, 5212, 0}, |
||||||
|
{"0x60026000556003600055", 1, 5212, 0}, |
||||||
|
{"0x60026000556001600055", 1, 5212, 4800}, |
||||||
|
{"0x60026000556002600055", 1, 5212, 0}, |
||||||
|
{"0x60016000556000600055", 1, 5212, 15000}, |
||||||
|
{"0x60016000556002600055", 1, 5212, 0}, |
||||||
|
{"0x60016000556001600055", 1, 412, 0}, |
||||||
|
{"0x600160005560006000556001600055", 0, 40218, 19800}, |
||||||
|
{"0x600060005560016000556000600055", 1, 10218, 19800}, |
||||||
|
{"0x60026000556000600055", 1, 5212, 15000}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private TestCodeExecutor codeExecutor; |
||||||
|
|
||||||
|
@Parameter public String code; |
||||||
|
|
||||||
|
@Parameter(value = 1) |
||||||
|
public int originalValue; |
||||||
|
|
||||||
|
@Parameter(value = 2) |
||||||
|
public int expectedGasUsed; |
||||||
|
|
||||||
|
@Parameter(value = 3) |
||||||
|
public int expectedGasRefund; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void setUp() { |
||||||
|
codeExecutor = new TestCodeExecutor(protocolSchedule); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void shouldCalculateGasAccordingToEip1283() { |
||||||
|
final long gasLimit = 1_000_000; |
||||||
|
final MessageFrame frame = |
||||||
|
codeExecutor.executeCode( |
||||||
|
code, |
||||||
|
gasLimit, |
||||||
|
account -> account.setStorageValue(UInt256.ZERO, UInt256.of(originalValue))); |
||||||
|
assertThat(frame.getState()).isEqualTo(State.COMPLETED_SUCCESS); |
||||||
|
assertThat(frame.getRemainingGas()).isEqualTo(Gas.of(gasLimit - expectedGasUsed)); |
||||||
|
assertThat(frame.getGasRefund()).isEqualTo(Gas.of(expectedGasRefund)); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue