Skip to content

Commit

Permalink
Add PragueGasCalculator tests
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Dudley <[email protected]>
  • Loading branch information
siladu committed Jan 16, 2025
1 parent d9e770d commit 74685b3
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,7 @@ public long transactionIntrinsicGasCost(
if (dynamicIntrinsicGasCost == Long.MIN_VALUE || dynamicIntrinsicGasCost == Long.MAX_VALUE) {
return dynamicIntrinsicGasCost;
}
return clampedAdd(TX_BASE_COST, dynamicIntrinsicGasCost);
}

@Override
public long transactionFloorCost(final Bytes payload) {
return 0L;
return clampedAdd(getMinimumTransactionCost(), dynamicIntrinsicGasCost);
}

/**
Expand Down Expand Up @@ -222,6 +217,11 @@ protected long txCreateExtraGasCost() {
return TX_CREATE_EXTRA_COST;
}

@Override
public long transactionFloorCost(final Bytes payload) {
return 0L;
}

@Override
public long codeDepositGasCost(final int codeSize) {
return CODE_DEPOSIT_BYTE_COST * codeSize;
Expand Down Expand Up @@ -637,33 +637,21 @@ public long getMinimumTransactionCost() {
public long calculateGasRefund(
final Transaction transaction,
final MessageFrame initialFrame,
final long codeDelegationRefund) {
final long ignoredCodeDelegationRefund) {

final long refundAllowance =
calculateRefundAllowance(transaction, initialFrame, codeDelegationRefund);
final long refundAllowance = calculateRefundAllowance(transaction, initialFrame);

return initialFrame.getRemainingGas() + refundAllowance;
}

/**
* Calculate the refund allowance for a transaction.
*
* @param transaction the transaction
* @param initialFrame the initial frame
* @param codeDelegationRefund the code delegation refund
* @return the refund allowance
*/
protected long calculateRefundAllowance(
final Transaction transaction,
final MessageFrame initialFrame,
final long codeDelegationRefund) {
private long calculateRefundAllowance(
final Transaction transaction, final MessageFrame initialFrame) {
final long selfDestructRefund =
getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size();
final long baseRefundGas =
initialFrame.getGasRefund() + selfDestructRefund + codeDelegationRefund;
final long executionRefund = initialFrame.getGasRefund() + selfDestructRefund;
// Integer truncation takes care of the floor calculation needed after the divide.
final long maxRefundAllowance =
(transaction.getGasLimit() - initialFrame.getRemainingGas()) / getMaxRefundQuotient();
return Math.min(maxRefundAllowance, baseRefundGas);
return Math.min(executionRefund, maxRefundAllowance);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public long calculateGasRefund(
final Transaction transaction,
final MessageFrame initialFrame,
final long codeDelegationRefund) {

final long refundAllowance =
calculateRefundAllowance(transaction, initialFrame, codeDelegationRefund);

Expand All @@ -91,10 +92,24 @@ public long calculateGasRefund(
return transaction.getGasLimit() - totalGasUsed;
}

private long calculateRefundAllowance(
final Transaction transaction,
final MessageFrame initialFrame,
final long codeDelegationRefund) {
final long selfDestructRefund =
getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size();
final long executionRefund =
initialFrame.getGasRefund() + selfDestructRefund + codeDelegationRefund;
// Integer truncation takes care of the floor calculation needed after the divide.
final long maxRefundAllowance =
(transaction.getGasLimit() - initialFrame.getRemainingGas()) / getMaxRefundQuotient();
return Math.min(executionRefund, maxRefundAllowance);
}

@Override
public long transactionFloorCost(final Bytes transactionPayload) {
return clampedAdd(
TX_BASE_COST,
getMinimumTransactionCost(),
tokensInCallData(transactionPayload.size(), zeroBytes(transactionPayload))
* TOTAL_COST_FLOOR_PER_TOKEN);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ void shouldCalculateRefundWithNoSelfDestructs() {

// Assert
assertThat(refund)
.isEqualTo(6500L); // 5000 (remaining) + min(1500 (total refund), 19000 (max allowance))
.isEqualTo(6000L); // 5000 (remaining) + min(1000 (execution refund), 47500 (max allowance))
}

@Test
void shouldCalculateRefundWithMultipleSelfDestructs() {
void shouldCalculateRefundWithMultipleSelfDestructsAndIgnoreCodeDelegation() {
// Arrange
Set<Address> selfDestructs = new HashSet<>();
selfDestructs.add(Address.wrap(Bytes.random(20)));
Expand All @@ -71,7 +71,8 @@ void shouldCalculateRefundWithMultipleSelfDestructs() {

// Assert
assertThat(refund)
.isEqualTo(52500L); // 5000 (remaining) + min(47500 (total refund), 49500 (max allowance))
.isEqualTo(
52500L); // 5000 (remaining) + min(49500 (execution refund), 47500 (max allowance))
}

@Test
Expand All @@ -87,7 +88,8 @@ void shouldRespectMaxRefundAllowance() {

// Assert
assertThat(refund)
.isEqualTo(60000L); // 20000 (remaining) + min(101000 (total refund), 40000 (max allowance))
.isEqualTo(
60000L); // 20000 (remaining) + min(101000 (execution refund), 40000 (max allowance))
}

@Test
Expand All @@ -99,9 +101,23 @@ void shouldHandleZeroValuesCorrectly() {
when(transaction.getGasLimit()).thenReturn(100000L);

// Act
long refund = gasCalculator.calculateGasRefund(transaction, messageFrame, 0L);
long refund =
gasCalculator.calculateGasRefund(
transaction,
messageFrame,
0L); // 0 (remaining) + min(0 (execution refund), 50000 (max allowance))

// Assert
assertThat(refund).isEqualTo(0L);
}

@Test
void transactionFloorCostShouldAlwaysBeZero() {
assertThat(gasCalculator.transactionFloorCost(Bytes.EMPTY)).isEqualTo(0L);
assertThat(gasCalculator.transactionFloorCost(Bytes.random(256))).isEqualTo(0L);
assertThat(gasCalculator.transactionFloorCost(Bytes.repeat((byte) 0x0, Integer.MAX_VALUE)))
.isEqualTo(0L);
assertThat(gasCalculator.transactionFloorCost(Bytes.repeat((byte) 0x1, Integer.MAX_VALUE)))
.isEqualTo(0L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,36 @@
package org.hyperledger.besu.evm.gascalculator;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.evm.frame.MessageFrame;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.tuweni.bytes.Bytes;
import org.assertj.core.api.AssertionsForClassTypes;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(MockitoExtension.class)
class PragueGasCalculatorTest {

private static final long TARGET_BLOB_GAS_PER_BLOCK_PRAGUE = 0xC0000;
private final PragueGasCalculator pragueGasCalculator = new PragueGasCalculator();
@Mock private Transaction transaction;
@Mock private MessageFrame messageFrame;

@Test
void testPrecompileSize() {
Expand Down Expand Up @@ -66,4 +80,125 @@ Iterable<Arguments> blobGasses() {
pragueGasCalculator.getBlobGasPerBlob()),
Arguments.of(sixBlobTargetGas, newTargetCount, sixBlobTargetGas));
}

@Test
void shouldCalculateRefundWithCodeDelegationAndNoSelfDestructs() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(1000L);
when(messageFrame.getRemainingGas()).thenReturn(5000L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);

// Act
long refund = pragueGasCalculator.calculateGasRefund(transaction, messageFrame, 500L);

// Assert
// execution refund = 1000 + 0 (self destructs) + 500 (code delegation) = 1500
AssertionsForClassTypes.assertThat(refund)
.isEqualTo(6500L); // 5000 (remaining) + min(1500 (execution refund), 19000 (max allowance))
}

@Test
void shouldCalculateRefundWithMultipleSelfDestructs() {
// Arrange
Set<Address> selfDestructs = new HashSet<>();
selfDestructs.add(Address.wrap(Bytes.random(20)));
selfDestructs.add(Address.wrap(Bytes.random(20)));

when(messageFrame.getSelfDestructs()).thenReturn(selfDestructs);
when(messageFrame.getGasRefund()).thenReturn(1000L);
when(messageFrame.getRemainingGas()).thenReturn(5000L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);

// Act
long refund = pragueGasCalculator.calculateGasRefund(transaction, messageFrame, 1000L);

// Assert
// execution refund = 1000 + 0 (self destructs EIP-3529) + 1000 (code delegation) = 2000
AssertionsForClassTypes.assertThat(refund)
.isEqualTo(7000L); // 5000 (remaining) + min(2000 (execution refund), 1900 (max allowance))
}

@Test
void shouldRespectMaxRefundAllowance() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(100000L);
when(messageFrame.getRemainingGas()).thenReturn(20000L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);

// Act
long refund = pragueGasCalculator.calculateGasRefund(transaction, messageFrame, 1000L);

// Assert
// execution refund = 100000 + 1000 (code delegation) = 101000
AssertionsForClassTypes.assertThat(refund)
.isEqualTo(
36000L); // 20000 (remaining) + min(101000 (execution refund), 16000 (max allowance))
}

@Test
void shouldHandleZeroValuesCorrectly() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(0L);
when(messageFrame.getRemainingGas()).thenReturn(0L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);

// Act
long refund =
pragueGasCalculator.calculateGasRefund(
transaction,
messageFrame,
0L); // 0 (remaining) + min(0 (execution refund), 20000 (max allowance))

// Assert
AssertionsForClassTypes.assertThat(refund).isEqualTo(0L);
}

@Test
void shouldRespectTransactionFloorCost() {
// Arrange
when(messageFrame.getSelfDestructs()).thenReturn(Collections.emptySet());
when(messageFrame.getGasRefund()).thenReturn(100000L);
when(messageFrame.getRemainingGas()).thenReturn(90000L);
when(transaction.getGasLimit()).thenReturn(100000L);
when(transaction.getPayload()).thenReturn(Bytes.EMPTY);

// Act
long refund = pragueGasCalculator.calculateGasRefund(transaction, messageFrame, 1000L);

// Assert
// refund allowance = 16000
// execution gas used = 100000 (gas limit) - 20000 (remaining gas) - 16000 (refund allowance) =
// 64000
// floor cost = 21000 (base cost) + 0 (tokensInCallData * floor cost per token) = 21000
AssertionsForClassTypes.assertThat(refund)
.isEqualTo(
79000L); // 100000 (gas limit) - max(8000 (execution gas used), 21000 (floor cost))
}

@Test
void transactionFloorCostShouldBeAtLeastTransactionBaseCost() {
// floor cost = 21000 (base cost) + 0
AssertionsForClassTypes.assertThat(pragueGasCalculator.transactionFloorCost(Bytes.EMPTY))
.isEqualTo(21000);
// floor cost = 21000 (base cost) + 256 (tokensInCallData) * 10 (cost per token)
AssertionsForClassTypes.assertThat(
pragueGasCalculator.transactionFloorCost(Bytes.repeat((byte) 0x0, 256)))
.isEqualTo(23560L);
// floor cost = 21000 (base cost) + 256 * 4 (tokensInCallData) * 10 (cost per token)
AssertionsForClassTypes.assertThat(
pragueGasCalculator.transactionFloorCost(Bytes.repeat((byte) 0x1, 256)))
.isEqualTo(31240L);
// floor cost = 21000 (base cost) + 5 + (6 * 4) (tokensInCallData) * 10 (cost per token)
AssertionsForClassTypes.assertThat(
pragueGasCalculator.transactionFloorCost(
Bytes.fromHexString("0x0001000100010001000101")))
.isEqualTo(21290L);
}
}

0 comments on commit 74685b3

Please sign in to comment.