diff --git a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java index ff7143e4348..155f4c0a608 100644 --- a/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java +++ b/rskj-core/src/main/java/org/ethereum/config/blockchain/upgrades/ConsensusRule.java @@ -88,6 +88,7 @@ public enum ConsensusRule { RSKIP385("rskip385"), RSKIP398("rskip398"), RSKIP400("rskip400"), // From EIP-2028 calldata gas cost reduction + RSKIP412("rskip412"), // From EIP-3198 BASEFEE opcode ; private String configKey; diff --git a/rskj-core/src/main/java/org/ethereum/vm/OpCode.java b/rskj-core/src/main/java/org/ethereum/vm/OpCode.java index bf51f550c2f..f9de1515e87 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/OpCode.java +++ b/rskj-core/src/main/java/org/ethereum/vm/OpCode.java @@ -272,6 +272,11 @@ public enum OpCode { */ CHAINID(0x46, 0, 1, BASE_TIER), + /** + * (0x48) Get the block base fee + */ + BASEFEE(0x48, 0, 1, BASE_TIER), + /* Memory, Storage and Flow Operations */ /** diff --git a/rskj-core/src/main/java/org/ethereum/vm/OpCodes.java b/rskj-core/src/main/java/org/ethereum/vm/OpCodes.java index 4823d52e081..7b483642fb7 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/OpCodes.java +++ b/rskj-core/src/main/java/org/ethereum/vm/OpCodes.java @@ -269,6 +269,11 @@ private OpCodes() { */ public static final byte OP_SELFBALANCE = 0x47 ; + /** + * (0x48) Get the block base fee + */ + public static final byte OP_BASEFEE = 0x48 ; + /* Memory Storage and F Operations */ /** diff --git a/rskj-core/src/main/java/org/ethereum/vm/VM.java b/rskj-core/src/main/java/org/ethereum/vm/VM.java index babe9160899..2365d387028 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/VM.java +++ b/rskj-core/src/main/java/org/ethereum/vm/VM.java @@ -953,7 +953,7 @@ protected void doRETURNDATACOPY() { protected void doGASPRICE(){ spendOpCodeGas(); // EXECUTION PHASE - DataWord gasPrice = program.getGasPrice(); + DataWord gasPrice = program.getTxGasPrice(); if (isLogEnabled) { hint = "price: " + gasPrice.toString(); @@ -1058,6 +1058,19 @@ protected void doGASLIMIT() { program.step(); } + protected void doBASEFEE() { + spendOpCodeGas(); + // EXECUTION PHASE + DataWord minimumGasPrice = program.getMinimumGasPrice(); + + if (isLogEnabled) { + hint = "baseFee: " + minimumGasPrice; + } + + program.stackPush(minimumGasPrice); + program.step(); + } + protected void doCHAINID() { spendOpCodeGas(); // EXECUTION PHASE @@ -1834,6 +1847,12 @@ protected void executeOpcode() { break; case OpCodes.OP_GASLIMIT: doGASLIMIT(); break; + case OpCodes.OP_BASEFEE: + if (!activations.isActive(RSKIP412)) { + throw Program.ExceptionHelper.invalidOpCode(program); + } + doBASEFEE(); + break; case OpCodes.OP_CHAINID: if (!activations.isActive(RSKIP152)) { throw Program.ExceptionHelper.invalidOpCode(program); diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java index 9a2ec121b15..849be161beb 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -195,7 +195,7 @@ private InternalTransaction addInternalTx(byte[] nonce, DataWord gasLimit, RskAd transaction, getCallDeep(), senderNonce, - getGasPrice(), + getTxGasPrice(), gasLimit, senderAddress.getBytes(), receiveAddress.getBytes(), @@ -1022,8 +1022,12 @@ public DataWord getCallerAddress() { return invoke.getCallerAddress(); } - public DataWord getGasPrice() { - return invoke.getMinGasPrice(); + public DataWord getTxGasPrice() { + return invoke.getTxGasPrice(); + } + + public DataWord getMinimumGasPrice() { + return invoke.getMinimumGasPrice(); } public long getRemainingGas() { diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvoke.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvoke.java index d2538b4f5c5..1bdf64b0d89 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvoke.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvoke.java @@ -33,7 +33,7 @@ public interface ProgramInvoke extends InvokeData { DataWord getOriginAddress(); - DataWord getMinGasPrice(); + DataWord getTxGasPrice(); DataWord getPrevHash(); @@ -49,6 +49,8 @@ public interface ProgramInvoke extends InvokeData { DataWord getGaslimit(); + DataWord getMinimumGasPrice(); + boolean byTransaction(); boolean byTestingSuite(); diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java index cd07c765f07..286c2662b19 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java @@ -64,7 +64,7 @@ public ProgramInvoke createProgramInvoke(Transaction tx, int txindex, Block bloc Coin balance = repository.getBalance(addr); /*** GASPRICE op ***/ - Coin gasPrice = tx.getGasPrice(); + Coin txGasPrice = tx.getGasPrice(); /*** GAS op ***/ byte[] gas = tx.getGasLimit(); @@ -95,13 +95,16 @@ public ProgramInvoke createProgramInvoke(Transaction tx, int txindex, Block bloc /*** GASLIMIT op ***/ byte[] gaslimit = block.getGasLimit(); + /*** BASEFEE op ***/ + Coin minimumGasPrice = block.getMinimumGasPrice(); + if (logger.isInfoEnabled()) { logger.info("Top level call: \n" + "address={}\n" + "origin={}\n" + "caller={}\n" + "balance={}\n" + - "gasPrice={}\n" + + "txGasPrice={}\n" + "gas={}\n" + "callValue={}\n" + "data={}\n" + @@ -111,13 +114,14 @@ public ProgramInvoke createProgramInvoke(Transaction tx, int txindex, Block bloc "blockNumber={}\n" + "transactionIndex={}\n" + "difficulty={}\n" + - "gaslimit={}\n", + "gaslimit={}\n" + + "minimumGasPrice={}\n", addr, ByteUtil.toHexString(origin), ByteUtil.toHexString(caller), balance, - gasPrice, + txGasPrice, new BigInteger(1, gas).longValue(), callValue, ByteUtil.toHexString(data), @@ -127,11 +131,14 @@ public ProgramInvoke createProgramInvoke(Transaction tx, int txindex, Block bloc number, txindex, ByteUtil.toHexString(difficulty), - gaslimit); + gaslimit, + minimumGasPrice); } - return new ProgramInvokeImpl(addr.getBytes(), origin, caller, balance.getBytes(), gasPrice.getBytes(), gas, callValue.getBytes(), data, - lastHash, coinbase, timestamp, number, txindex,difficulty, gaslimit, + byte[] minGasPrice = minimumGasPrice != null ? minimumGasPrice.getBytes() : ByteUtil.EMPTY_BYTE_ARRAY; + + return new ProgramInvokeImpl(addr.getBytes(), origin, caller, balance.getBytes(), txGasPrice.getBytes(), gas, callValue.getBytes(), data, + lastHash, coinbase, timestamp, number, txindex,difficulty, gaslimit, minGasPrice, repository, blockStore); } @@ -151,7 +158,7 @@ public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, Da DataWord caller = callerAddress; DataWord balance = DataWord.valueOf(balanceInt.getBytes()); - DataWord gasPrice = program.getGasPrice(); + DataWord txGasPrice = program.getTxGasPrice(); long agas = inGas; DataWord callValue = inValue; @@ -163,6 +170,7 @@ public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, Da DataWord transactionIndex = program.getTransactionIndex(); DataWord difficulty = program.getDifficulty(); DataWord gasLimit = program.getGasLimit(); + DataWord minimumGasPrice = program.getMinimumGasPrice(); if (logger.isInfoEnabled()) { logger.info("Internal call: \n" + @@ -170,7 +178,7 @@ public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, Da "origin={}\n" + "caller={}\n" + "balance={}\n" + - "gasPrice={}\n" + + "txGasPrice={}\n" + "gas={}\n" + "callValue={}\n" + "data={}\n" + @@ -180,12 +188,13 @@ public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, Da "blockNumber={}\n" + "transactionIndex={}\n" + "difficulty={}\n" + - "gaslimit={}\n", + "gaslimit={}\n" + + "minimumGasPrice={}\n", ByteUtil.toHexString(address.getLast20Bytes()), ByteUtil.toHexString(origin.getLast20Bytes()), ByteUtil.toHexString(caller.getLast20Bytes()), balance.toString(), - gasPrice.longValue(), + txGasPrice.longValue(), agas, ByteUtil.toHexString(callValue.getNoLeadZeroesData()), data == null ? "" : ByteUtil.toHexString(data), @@ -195,12 +204,13 @@ public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, Da number.longValue(), transactionIndex.intValue(), ByteUtil.toHexString(difficulty.getNoLeadZeroesData()), - gasLimit.bigIntValue()); + gasLimit.bigIntValue(), + minimumGasPrice.longValue()); } - return new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, agas, callValue, + return new ProgramInvokeImpl(address, origin, caller, balance, txGasPrice, agas, callValue, data, lastHash, coinbase, timestamp, number, transactionIndex, difficulty, gasLimit, - repository, program.getCallDeep() + 1, blockStore, + minimumGasPrice, repository, program.getCallDeep() + 1, blockStore, isStaticCall, byTestingSuite); } } diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java index b661f43b33a..4127dfa833b 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java @@ -43,7 +43,7 @@ public class ProgramInvokeImpl implements ProgramInvoke { private final DataWord origin; private final DataWord caller; private final DataWord balance; - private final DataWord gasPrice; + private final DataWord txGasPrice; private final DataWord callValue; private long gas; @@ -58,6 +58,7 @@ public class ProgramInvokeImpl implements ProgramInvoke { private final DataWord number; private final DataWord difficulty; private final DataWord gaslimit; + private final DataWord minimumGasPrice; private final DataWord transactionIndex; @@ -70,12 +71,12 @@ public class ProgramInvokeImpl implements ProgramInvoke { private boolean isStaticCall = false; public ProgramInvokeImpl(DataWord address, DataWord origin, DataWord caller, DataWord balance, - DataWord gasPrice, + DataWord txGasPrice, long gas, DataWord callValue, byte[] msgData, DataWord lastHash, DataWord coinbase, DataWord timestamp, DataWord number, DataWord transactionIndex, DataWord difficulty, - DataWord gaslimit, Repository repository, int callDeep, BlockStore blockStore, + DataWord gaslimit, DataWord minimumGasPrice, Repository repository, int callDeep, BlockStore blockStore, boolean isStaticCall, boolean byTestingSuite) { @@ -84,7 +85,7 @@ public ProgramInvokeImpl(DataWord address, DataWord origin, DataWord caller, Dat this.origin = origin; this.caller = caller; this.balance = balance; - this.gasPrice = gasPrice; + this.txGasPrice = txGasPrice; this.gas = gas; this.callValue = callValue; this.msgData = msgData; @@ -97,6 +98,7 @@ public ProgramInvokeImpl(DataWord address, DataWord origin, DataWord caller, Dat this.transactionIndex = transactionIndex; this.difficulty = difficulty; this.gaslimit = gaslimit; + this.minimumGasPrice = minimumGasPrice; this.repository = repository; this.byTransaction = false; @@ -107,22 +109,22 @@ public ProgramInvokeImpl(DataWord address, DataWord origin, DataWord caller, Dat } public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] balance, - byte[] gasPrice, byte[] gas, byte[] callValue, byte[] msgData, + byte[] txGasPrice, byte[] gas, byte[] callValue, byte[] msgData, byte[] lastHash, byte[] coinbase, long timestamp, long number, int transactionIndex, byte[] difficulty, - byte[] gaslimit, + byte[] gaslimit, byte[] minimumGasPrice, Repository repository, BlockStore blockStore, boolean byTestingSuite) { - this(address, origin, caller, balance, gasPrice, gas, callValue, msgData, lastHash, coinbase, - timestamp, number, transactionIndex, difficulty, gaslimit, repository, blockStore); + this(address, origin, caller, balance, txGasPrice, gas, callValue, msgData, lastHash, coinbase, + timestamp, number, transactionIndex, difficulty, gaslimit, minimumGasPrice, repository, blockStore); this.byTestingSuite = byTestingSuite; } public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] balance, - byte[] gasPrice, byte[] gas, byte[] callValue, byte[] msgData, + byte[] txGasPrice, byte[] gas, byte[] callValue, byte[] msgData, byte[] lastHash, byte[] coinbase, long timestamp, long number, int transactionIndex, byte[] difficulty, - byte[] gaslimit, + byte[] gaslimit, byte[] minimumGasPrice, Repository repository, BlockStore blockStore) { // Transaction env @@ -130,7 +132,7 @@ public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] ba this.origin = DataWord.valueOf(origin); this.caller = DataWord.valueOf(caller); this.balance = DataWord.valueOf(balance); - this.gasPrice = DataWord.valueOf(gasPrice); + this.txGasPrice = DataWord.valueOf(txGasPrice); this.gas = Program.limitToMaxLong(DataWord.valueOf(gas)); this.callValue = DataWord.valueOf(callValue); this.msgData = msgData; @@ -143,42 +145,50 @@ public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] ba this.transactionIndex = DataWord.valueOf(transactionIndex); this.difficulty = DataWord.valueOf(difficulty); this.gaslimit = DataWord.valueOf(gaslimit); + this.minimumGasPrice = DataWord.valueOf(minimumGasPrice); this.repository = repository; this.blockStore = blockStore; } /* ADDRESS op */ + @Override public DataWord getOwnerAddress() { return address; } /* BALANCE op */ + @Override public DataWord getBalance() { return balance; } /* ORIGIN op */ + @Override public DataWord getOriginAddress() { return origin; } /* CALLER op */ + @Override public DataWord getCallerAddress() { return caller; } /* GASPRICE op */ - public DataWord getMinGasPrice() { - return gasPrice; + @Override + public DataWord getTxGasPrice() { + return txGasPrice; } /* GAS op */ + @Override public long getGas() { return gas; } /* CALLVALUE op */ + @Override public DataWord getCallValue() { return callValue; } @@ -192,6 +202,7 @@ public DataWord getCallValue() { private static BigInteger maxMsgData = BigInteger.valueOf(Integer.MAX_VALUE); /* CALLDATALOAD op */ + @Override public DataWord getDataValue(DataWord indexData) { BigInteger tempIndex = indexData.value(); @@ -212,6 +223,7 @@ public DataWord getDataValue(DataWord indexData) { } /* CALLDATASIZE */ + @Override public DataWord getDataSize() { if (msgData == null || msgData.length == 0) { @@ -222,6 +234,7 @@ public DataWord getDataSize() { } /* CALLDATACOPY */ + @Override public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) { int offset = offsetData.intValueSafe(); @@ -246,45 +259,59 @@ public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) { /* PREVHASH op */ + @Override public DataWord getPrevHash() { return prevHash; } /* COINBASE op */ + @Override public DataWord getCoinbase() { return coinbase; } /* TIMESTAMP op */ + @Override public DataWord getTimestamp() { return timestamp; } /* NUMBER op */ + @Override public DataWord getNumber() { return number; } /* TXINDEX op */ + @Override public DataWord getTransactionIndex() { return transactionIndex; } /* DIFFICULTY op */ + @Override public DataWord getDifficulty() { return difficulty; } /* GASLIMIT op */ + @Override public DataWord getGaslimit() { return gaslimit; } + /* BASEFEE op */ + @Override + public DataWord getMinimumGasPrice() { + return minimumGasPrice; + } + /* Storage */ public Map getStorage() { return storage; } + @Override public Repository getRepository() { return repository; } @@ -352,12 +379,15 @@ public boolean equals(Object o) { if (gas!=that.gas) { return false; } - if (gasPrice != null ? !gasPrice.equals(that.gasPrice) : that.gasPrice != null) { + if (txGasPrice != null ? !txGasPrice.equals(that.txGasPrice) : that.txGasPrice != null) { return false; } if (gaslimit != null ? !gaslimit.equals(that.gaslimit) : that.gaslimit != null) { return false; } + if (minimumGasPrice != null ? !minimumGasPrice.equals(that.minimumGasPrice) : that.minimumGasPrice != null) { + return false; + } if (!Arrays.equals(msgData, that.msgData)) { return false; } @@ -385,7 +415,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - int result = Objects.hash(address, origin, caller, balance, gasPrice, callValue, gas, prevHash, coinbase, timestamp, number, difficulty, gaslimit, storage, repository, byTransaction, byTestingSuite); + int result = Objects.hash(address, origin, caller, balance, txGasPrice, callValue, gas, prevHash, coinbase, timestamp, number, difficulty, gaslimit, minimumGasPrice, storage, repository, byTransaction, byTestingSuite); result = 31 * result + Arrays.hashCode(msgData); return result; } @@ -398,7 +428,7 @@ public String toString() { ", caller=" + caller + ", balance=" + balance + ", gas=" + gas + - ", gasPrice=" + gasPrice + + ", txGasPrice=" + txGasPrice + ", callValue=" + callValue + ", msgData=" + Arrays.toString(msgData) + ", prevHash=" + prevHash + @@ -407,6 +437,7 @@ public String toString() { ", number=" + number + ", difficulty=" + difficulty + ", gaslimit=" + gaslimit + + ", minimumGasPrice=" + minimumGasPrice + ", storage=" + storage + ", repository=" + repository + ", byTransaction=" + byTransaction + diff --git a/rskj-core/src/main/resources/expected.conf b/rskj-core/src/main/resources/expected.conf index 57e52beb51f..d850dfec6bb 100644 --- a/rskj-core/src/main/resources/expected.conf +++ b/rskj-core/src/main/resources/expected.conf @@ -87,6 +87,7 @@ blockchain = { rskip385 = rskip398 = rskip400 = + rskip412 = } } gc = { diff --git a/rskj-core/src/main/resources/reference.conf b/rskj-core/src/main/resources/reference.conf index 8c8ee860e6c..2328a5d9f43 100644 --- a/rskj-core/src/main/resources/reference.conf +++ b/rskj-core/src/main/resources/reference.conf @@ -74,6 +74,7 @@ blockchain = { rskip385 = fingerroot500 rskip398 = arrowhead600 rskip400 = arrowhead600 + rskip412 = arrowhead600 } } gc = { diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/trace/TraceTransformerTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/trace/TraceTransformerTest.java index c17c96533bb..da578fc7499 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/modules/trace/TraceTransformerTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/trace/TraceTransformerTest.java @@ -43,7 +43,7 @@ void getActionFromInvokeData() { gas, callValue, data, - null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, 0, null, false, false); TraceAction action = TraceTransformer.toAction(TraceType.CALL, invoke, CallType.CALL, null, null, null); @@ -76,7 +76,7 @@ void getActionFromInvokeDataWithCreationData() { gas, callValue, null, - null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, 0, null, false, false); TraceAction action = TraceTransformer.toAction(TraceType.CREATE, invoke, CallType.NONE, data, null, null); @@ -110,7 +110,7 @@ void getActionFromInvokeDataWithCreationDataUsingCreationMethod() { gas, callValue, null, - null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, 0, null, false, false); TraceAction action = TraceTransformer.toAction(TraceType.CREATE, invoke, CallType.NONE, data, "create2", null); diff --git a/rskj-core/src/test/java/co/rsk/vm/VMExecutionTest.java b/rskj-core/src/test/java/co/rsk/vm/VMExecutionTest.java index 11717e15cf5..6f2d5b56558 100644 --- a/rskj-core/src/test/java/co/rsk/vm/VMExecutionTest.java +++ b/rskj-core/src/test/java/co/rsk/vm/VMExecutionTest.java @@ -901,6 +901,7 @@ private void executePush0(ActivationConfig.ForBlock activations){ Assertions.assertEquals(1, stack.size()); Assertions.assertEquals(DataWord.valueFromHex("0000000000000000000000000000000000000000000000000000000000000000"), stack.peek()); } + @Test void testPUSH0Activation() { ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class); @@ -917,6 +918,33 @@ void testPUSH0NoActivation() { Assertions.assertThrows(Program.IllegalOperationException.class, () -> { executePush0(activations); }); + } + + @Test + void testBASEFFEActivation() { + ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class); + when(activations.isActive(RSKIP412)).thenReturn(true); + + executeBASEFEE(activations); + } + + @Test + void testBASEFEENoActivation() { + ActivationConfig.ForBlock activations = mock(ActivationConfig.ForBlock.class); + when(activations.isActive(RSKIP412)).thenReturn(false); + + Assertions.assertThrows(Program.IllegalOperationException.class, () -> { + executeBASEFEE(activations); + }); + } + + private void executeBASEFEE(ActivationConfig.ForBlock activations) { + Program program = executeCodeWithActivationConfig("BASEFEE", 1, activations); + Stack stack = program.getStack(); + + Assertions.assertEquals(1, stack.size()); + // See ProgramInvokeMockImpl.getMinimumGasPrice() + Assertions.assertEquals(DataWord.valueFromHex("000000000000000000000000000000000000000000000000000003104e60a000"), stack.peek()); } } diff --git a/rskj-core/src/test/java/co/rsk/vm/opcode/BasefeeDslTest.java b/rskj-core/src/test/java/co/rsk/vm/opcode/BasefeeDslTest.java new file mode 100644 index 00000000000..bb52b96c9ef --- /dev/null +++ b/rskj-core/src/test/java/co/rsk/vm/opcode/BasefeeDslTest.java @@ -0,0 +1,179 @@ +/* + * This file is part of RskJ + * Copyright (C) 2023 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package co.rsk.vm.opcode; + +import co.rsk.config.TestSystemProperties; +import co.rsk.test.World; +import co.rsk.test.dsl.DslParser; +import co.rsk.test.dsl.DslProcessorException; +import co.rsk.test.dsl.WorldDslProcessor; +import org.ethereum.config.blockchain.upgrades.ActivationConfig; +import org.ethereum.config.blockchain.upgrades.ConsensusRule; +import org.ethereum.core.Block; +import org.ethereum.core.Transaction; +import org.ethereum.core.TransactionReceipt; +import org.ethereum.core.util.TransactionReceiptUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.FileNotFoundException; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +public class BasefeeDslTest { + + @Test + void testBASEFEE_whenActivated_behavesAsExpected() throws FileNotFoundException, DslProcessorException { + DslParser parser = DslParser.fromResource("dsl/opcode/basefee/baseFeeActivatedTest.txt"); + World world = new World(); + WorldDslProcessor processor = new WorldDslProcessor(world); + processor.processCommands(parser); + + // Assertions + + // There's one block (b01) containing only 1 transaction + Block block1 = world.getBlockByName("b01"); + Assertions.assertNotNull(block1); + Assertions.assertEquals(1, block1.getTransactionsList().size()); + + // There's a transaction called txTestBasefee + Transaction txTestBasefee = world.getTransactionByName("txTestBasefee"); + Assertions.assertNotNull(txTestBasefee); + + // Transaction txTestBasefee has a transaction receipt + TransactionReceipt txTestBasefeeReceipt = world.getTransactionReceiptByName("txTestBasefee"); + Assertions.assertNotNull(txTestBasefeeReceipt); + + // Transaction txTestBasefee has been processed correctly + byte[] creationStatus = txTestBasefeeReceipt.getStatus(); + Assertions.assertNotNull(creationStatus); + Assertions.assertEquals(1, creationStatus.length); + Assertions.assertEquals(1, creationStatus[0]); + + // There's one block (b02) containing only 1 transaction + Block block2 = world.getBlockByName("b02"); + Assertions.assertNotNull(block2); + Assertions.assertEquals(1, block2.getTransactionsList().size()); + + // There's a transaction called txTestBasefeeOKCall + Transaction txTestBasefeeOKCall = world.getTransactionByName("txTestBasefeeOKCall"); + Assertions.assertNotNull(txTestBasefeeOKCall); + + // Transaction txTestBasefeeOKCall has a transaction receipt + TransactionReceipt txTestBasefeeOKCallReceipt = world.getTransactionReceiptByName("txTestBasefeeOKCall"); + Assertions.assertNotNull(txTestBasefeeOKCallReceipt); + + // Transaction txTestBasefeeOKCall has been processed correctly + byte[] txTestBasefeeOKCallCreationStatus = txTestBasefeeOKCallReceipt.getStatus(); + Assertions.assertNotNull(txTestBasefeeOKCallCreationStatus); + Assertions.assertEquals(1, txTestBasefeeOKCallCreationStatus.length); + Assertions.assertEquals(1, txTestBasefeeOKCallCreationStatus[0]); + + // Check events + Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txTestBasefeeOKCallReceipt, "OK", null)); + Assertions.assertEquals(0, TransactionReceiptUtil.getEventCount(txTestBasefeeOKCallReceipt, "ERROR", null)); + + // There's one block (b03) containing only 1 transaction + Block block3 = world.getBlockByName("b03"); + Assertions.assertNotNull(block3); + Assertions.assertEquals(1, block3.getTransactionsList().size()); + + // There's a transaction called txTestBasefeeErrorCall + Transaction txTestBasefeeErrorCall = world.getTransactionByName("txTestBasefeeErrorCall"); + Assertions.assertNotNull(txTestBasefeeErrorCall); + + // Transaction txTestBasefeeErrorCall has a transaction receipt + TransactionReceipt txTestBasefeeErrorCallReceipt = world.getTransactionReceiptByName("txTestBasefeeErrorCall"); + Assertions.assertNotNull(txTestBasefeeErrorCallReceipt); + + // Transaction txTestBasefeeErrorCall has been processed correctly + byte[] txTestBasefeeErrorCallCreationStatus = txTestBasefeeErrorCallReceipt.getStatus(); + Assertions.assertNotNull(txTestBasefeeErrorCallCreationStatus); + Assertions.assertEquals(1, txTestBasefeeErrorCallCreationStatus.length); + Assertions.assertEquals(1, txTestBasefeeErrorCallCreationStatus[0]); + + // Check events + Assertions.assertEquals(1, TransactionReceiptUtil.getEventCount(txTestBasefeeErrorCallReceipt, "ERROR", null)); + Assertions.assertEquals(0, TransactionReceiptUtil.getEventCount(txTestBasefeeErrorCallReceipt, "OK", null)); + } + + @Test + void testBASEFEE_whenNotActivated_BehavesAsExpected() throws FileNotFoundException, DslProcessorException { + + // Config Spies Setup + + TestSystemProperties config = new TestSystemProperties(); + ActivationConfig activationConfig = config.getActivationConfig(); + + TestSystemProperties configSpy = spy(config); + ActivationConfig activationConfigSpy = spy(activationConfig); + + doReturn(activationConfigSpy).when(configSpy).getActivationConfig(); + doReturn(false).when(activationConfigSpy).isActive(eq(ConsensusRule.RSKIP412), anyLong()); + + // Test Setup + + DslParser parser = DslParser.fromResource("dsl/opcode/basefee/baseFeeNotActivatedTest.txt"); + World world = new World(configSpy); + WorldDslProcessor processor = new WorldDslProcessor(world); + processor.processCommands(parser); + + // Assertions + + // There's one block (b01) containing only 1 transaction + Block block1 = world.getBlockByName("b01"); + Assertions.assertNotNull(block1); + Assertions.assertEquals(1, block1.getTransactionsList().size()); + + // There's a transaction called txTestBasefee + Transaction txTestBasefee = world.getTransactionByName("txTestBasefee"); + Assertions.assertNotNull(txTestBasefee); + + // Transaction txTestBasefee has a transaction receipt + TransactionReceipt txTestBasefeeReceipt = world.getTransactionReceiptByName("txTestBasefee"); + Assertions.assertNotNull(txTestBasefeeReceipt); + + // Transaction txTestBasefee has been processed correctly + byte[] creationStatus = txTestBasefeeReceipt.getStatus(); + Assertions.assertNotNull(creationStatus); + Assertions.assertEquals(1, creationStatus.length); + Assertions.assertEquals(1, creationStatus[0]); + + verify(activationConfigSpy, atLeast(1)).isActive(eq(ConsensusRule.RSKIP412), eq(2L)); + + // There's one block (b02) containing only 1 transaction + Block block2 = world.getBlockByName("b02"); + Assertions.assertNotNull(block2); + Assertions.assertEquals(1, block2.getTransactionsList().size()); + + // There's a transaction called txTestBasefeeNotActivated + Transaction txTestBasefeeNotActivated = world.getTransactionByName("txTestBasefeeNotActivated"); + Assertions.assertNotNull(txTestBasefeeNotActivated); + + // Transaction txTestBasefeeNotActivated has a transaction receipt + TransactionReceipt txTestBasefeeNotActivatedReceipt = world.getTransactionReceiptByName("txTestBasefeeNotActivated"); + Assertions.assertNotNull(txTestBasefeeNotActivatedReceipt); + + // Transaction txTestBasefeeNotActivated has failed + byte[] txTestBasefeeNotActivatedCreationStatus = txTestBasefeeNotActivatedReceipt.getStatus(); + Assertions.assertNotNull(txTestBasefeeNotActivatedCreationStatus); + Assertions.assertEquals(0, txTestBasefeeNotActivatedCreationStatus.length); + } + +} diff --git a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java index 1e4d4cfeab0..92e44f80e2a 100644 --- a/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java +++ b/rskj-core/src/test/java/org/ethereum/config/blockchain/upgrades/ActivationConfigTest.java @@ -114,6 +114,7 @@ class ActivationConfigTest { " rskip385: fingerroot500", " rskip398: arrowhead600", " rskip400: arrowhead600", + " rskip412: arrowhead600", "}" )); diff --git a/rskj-core/src/test/java/org/ethereum/jsontestsuite/Env.java b/rskj-core/src/test/java/org/ethereum/jsontestsuite/Env.java index 4aef0fd6eaf..02c10efe8bd 100644 --- a/rskj-core/src/test/java/org/ethereum/jsontestsuite/Env.java +++ b/rskj-core/src/test/java/org/ethereum/jsontestsuite/Env.java @@ -16,12 +16,8 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - package org.ethereum.jsontestsuite; -import com.fasterxml.jackson.databind.JsonNode; -import org.bouncycastle.util.BigIntegers; -import org.bouncycastle.util.encoders.Hex; import org.ethereum.util.ByteUtil; /** @@ -33,50 +29,24 @@ public class Env { private final byte[] currentCoinbase; private final byte[] currentDifficulty; private final byte[] currentGasLimit; + private final byte[] currentMinimumGasPrice; private final byte[] currentNumber; private final byte[] currentTimestamp; private final byte[] previousHash; - public Env(byte[] currentCoinbase, byte[] currentDifficulty, byte[] - currentGasLimit, byte[] currentNumber, byte[] - currentTimestamp, byte[] previousHash) { + currentGasLimit, byte[] currentMinimumGasPrice, + byte[] currentNumber, byte[] currentTimestamp, + byte[] previousHash) { this.currentCoinbase = currentCoinbase; this.currentDifficulty = currentDifficulty; this.currentGasLimit = currentGasLimit; + this.currentMinimumGasPrice = currentMinimumGasPrice; this.currentNumber = currentNumber; this.currentTimestamp = currentTimestamp; this.previousHash = previousHash; } - /* - e.g: - "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentDifficulty" : "256", - "currentGasLimit" : "1000000", - "currentNumber" : "0", - "currentTimestamp" : 1, - "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" - */ - public Env(JsonNode env) { - - String coinbase = env.get("currentCoinbase").asText(); - String difficulty = env.get("currentDifficulty").asText(); - String timestamp = env.get("currentTimestamp").asText(); - String number = env.get("currentNumber").asText(); - String gasLimit = org.ethereum.json.Utils.parseUnidentifiedBase(env.get("currentGasLimit").asText()); - JsonNode previousHash = env.get("previousHash"); - String prevHash = previousHash == null ? "" : previousHash.asText(); - - this.currentCoinbase = Hex.decode(coinbase); - this.currentDifficulty = BigIntegers.asUnsignedByteArray(TestingCase.toBigInt(difficulty) ); - this.currentGasLimit = BigIntegers.asUnsignedByteArray(TestingCase.toBigInt(gasLimit)); - this.currentNumber = TestingCase.toBigInt(number).toByteArray(); - this.currentTimestamp = TestingCase.toBigInt(timestamp).toByteArray(); - this.previousHash = Hex.decode(prevHash); - - } - public byte[] getCurrentCoinbase() { return currentCoinbase; } @@ -89,6 +59,10 @@ public byte[] getCurrentGasLimit() { return currentGasLimit; } + public byte[] getCurrentMinimumGasPrice() { + return currentMinimumGasPrice; + } + public byte[] getCurrentNumber() { return currentNumber; } @@ -107,6 +81,7 @@ public String toString() { "currentCoinbase=" + ByteUtil.toHexString(currentCoinbase) + ", currentDifficulty=" + ByteUtil.toHexString(currentDifficulty) + ", currentGasLimit=" + ByteUtil.toHexString(currentGasLimit) + + ", currentMinimumGasPrice=" + ByteUtil.toHexString(currentMinimumGasPrice) + ", currentNumber=" + ByteUtil.toHexString(currentNumber) + ", currentTimestamp=" + ByteUtil.toHexString(currentTimestamp) + ", previousHash=" + ByteUtil.toHexString(previousHash) + diff --git a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java index e19db998bad..3af60cfbe7c 100644 --- a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java +++ b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java @@ -47,7 +47,7 @@ public TestProgramInvokeFactory(Env env) { @Override public ProgramInvoke createProgramInvoke(Transaction tx, int txindex, Block block, Repository repository, BlockStore blockStore, SignatureCache signatureCache) { - return generalInvoke(tx, txindex, repository, blockStore, signatureCache); + return generalInvoke(tx, txindex, block, repository, blockStore, signatureCache); } @Override @@ -59,7 +59,7 @@ public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, Da return null; } - private ProgramInvoke generalInvoke(Transaction tx, int txindex, Repository repository, BlockStore blockStore, SignatureCache signatureCache) { + private ProgramInvoke generalInvoke(Transaction tx, int txindex, Block block, Repository repository, BlockStore blockStore, SignatureCache signatureCache) { /*** ADDRESS op ***/ // YP: Get address of currently executing account. @@ -77,7 +77,7 @@ private ProgramInvoke generalInvoke(Transaction tx, int txindex, Repository repo Coin balance = repository.getBalance(addr); /*** GASPRICE op ***/ - Coin gasPrice = tx.getGasPrice(); + Coin txGasPrice = tx.getGasPrice(); /*** GAS op ***/ byte[] gas = tx.getGasLimit(); @@ -109,9 +109,12 @@ private ProgramInvoke generalInvoke(Transaction tx, int txindex, Repository repo /*** GASLIMIT op ***/ byte[] gaslimit = env.getCurrentGasLimit(); + /*** BASEFEE op ***/ + Coin minimumGasPrice = block.getMinimumGasPrice(); + return new ProgramInvokeImpl(addr.getBytes(), origin.getBytes(), caller.getBytes(), balance.getBytes(), - gasPrice.getBytes(), gas, callValue.getBytes(), data, lastHash, coinbase, - timestamp, number, txindex, difficulty, gaslimit, repository, blockStore); + txGasPrice.getBytes(), gas, callValue.getBytes(), data, lastHash, coinbase, + timestamp, number, txindex, difficulty, gaslimit, minimumGasPrice.getBytes(), repository, blockStore); } } diff --git a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestRunner.java b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestRunner.java index 5d4a7ea3e26..2f01f686168 100644 --- a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestRunner.java +++ b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestRunner.java @@ -252,7 +252,7 @@ public List runTestCase(TestingCase testCase) { byte[] origin = exec.getOrigin(); byte[] caller = exec.getCaller(); byte[] balance = ByteUtil.bigIntegerToBytes(repository.getBalance(new RskAddress(exec.getAddress())).asBigInteger()); - byte[] gasPrice = exec.getGasPrice(); + byte[] txGasPrice = exec.getGasPrice(); byte[] gas = exec.getGas(); byte[] callValue = exec.getValue(); byte[] msgData = exec.getData(); @@ -262,6 +262,7 @@ public List runTestCase(TestingCase testCase) { long number = ByteUtil.byteArrayToLong(env.getCurrentNumber()); byte[] difficulty = env.getCurrentDifficulty(); byte[] gaslimit = env.getCurrentGasLimit(); + byte[] minimumGasPrice = env.getCurrentMinimumGasPrice(); // Origin and caller need to exist in order to be able to execute RskAddress originAddress = new RskAddress(origin); @@ -274,8 +275,8 @@ public List runTestCase(TestingCase testCase) { } ProgramInvoke programInvoke = new ProgramInvokeImpl(address, origin, caller, balance, - gasPrice, gas, callValue, msgData, lastHash, coinbase, - timestamp, number, 0, difficulty, gaslimit, repository, new BlockStoreDummy(), true); + txGasPrice, gas, callValue, msgData, lastHash, coinbase, + timestamp, number, 0, difficulty, gaslimit, minimumGasPrice, repository, new BlockStoreDummy(), true); /* 3. Create Program - exec.code */ /* 4. run VM */ diff --git a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestingCase.java b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestingCase.java index d9a2c7f00dd..9276d2b113b 100644 --- a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestingCase.java +++ b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestingCase.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import org.bouncycastle.util.BigIntegers; import org.bouncycastle.util.encoders.Hex; +import org.ethereum.jsontestsuite.builder.EnvBuilder; import org.ethereum.util.ByteUtil; import java.io.IOException; @@ -128,7 +129,7 @@ public TestingCase(JsonNode testCaseJSONObj) throws IOException { } if (testCaseJSONObj.has("env")) - this.env = new Env(envJSON); + this.env = EnvBuilder.build(envJSON); if (testCaseJSONObj.has("exec")) this.exec = new Exec(execJSON); @@ -139,7 +140,7 @@ public TestingCase(JsonNode testCaseJSONObj) throws IOException { } } - static BigInteger toBigInt(String s) { + public static BigInteger toBigInt(String s) { if (s.startsWith("0x")) { if (s.equals("0x")) return new BigInteger("0"); return new BigInteger(s.substring(2), 16); diff --git a/rskj-core/src/test/java/org/ethereum/jsontestsuite/builder/EnvBuilder.java b/rskj-core/src/test/java/org/ethereum/jsontestsuite/builder/EnvBuilder.java index 2914d33af47..78e65ab2e85 100644 --- a/rskj-core/src/test/java/org/ethereum/jsontestsuite/builder/EnvBuilder.java +++ b/rskj-core/src/test/java/org/ethereum/jsontestsuite/builder/EnvBuilder.java @@ -16,25 +16,57 @@ * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - package org.ethereum.jsontestsuite.builder; +import com.fasterxml.jackson.databind.JsonNode; +import org.bouncycastle.util.encoders.Hex; import org.ethereum.jsontestsuite.Env; import org.ethereum.jsontestsuite.model.EnvTck; +import static org.bouncycastle.util.BigIntegers.asUnsignedByteArray; import static org.ethereum.json.Utils.*; +import static org.ethereum.jsontestsuite.TestingCase.toBigInt; public class EnvBuilder { - public static Env build(EnvTck envTck){ + public static final String defaultMinimumGasPrice = "0x0050"; + + public static Env build(EnvTck envTck) { byte[] coinbase = parseData(envTck.getCurrentCoinbase()); byte[] difficulty = parseVarData(envTck.getCurrentDifficulty()); byte[] gasLimit = parseVarData(envTck.getCurrentGasLimit()); + byte[] minimumGasPrice = parseVarData(defaultMinimumGasPrice); byte[] number = parseNumericData(envTck.getCurrentNumber()); byte[] timestamp = parseNumericData(envTck.getCurrentTimestamp()); byte[] hash = parseData(envTck.getPreviousHash()); - return new Env(coinbase, difficulty, gasLimit, number, timestamp, hash); + return new Env(coinbase, difficulty, gasLimit, minimumGasPrice, number, timestamp, hash); + } + + /* + e.g: + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "256", + "currentGasLimit" : "1000000", + "currentMinimumGasPrice" : "777", + "currentNumber" : "0", + "currentTimestamp" : 1, + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" + */ + public static Env build(JsonNode jsonEnv) { + byte[] coinbase = Hex.decode(jsonEnv.get("currentCoinbase").asText()); + byte[] difficulty = asUnsignedByteArray(toBigInt(jsonEnv.get("currentDifficulty").asText())); + byte[] gasLimit = asUnsignedByteArray(toBigInt(parseUnidentifiedBase(jsonEnv.get("currentGasLimit").asText()))); + byte[] minimumGasPrice = asUnsignedByteArray(toBigInt(parseUnidentifiedBase(defaultMinimumGasPrice))); + byte[] number = toBigInt(jsonEnv.get("currentNumber").asText()).toByteArray(); + byte[] timestamp = toBigInt(jsonEnv.get("currentTimestamp").asText()).toByteArray(); + + JsonNode previousHash = jsonEnv.get("previousHash"); + String prevHash = previousHash == null ? "" : previousHash.asText(); + + byte[] hash = Hex.decode(prevHash); + + return new Env(coinbase, difficulty, gasLimit, minimumGasPrice, number, timestamp, hash); } } diff --git a/rskj-core/src/test/java/org/ethereum/vm/VMTest.java b/rskj-core/src/test/java/org/ethereum/vm/VMTest.java index 3c404d6d736..0f9e95cd2fb 100644 --- a/rskj-core/src/test/java/org/ethereum/vm/VMTest.java +++ b/rskj-core/src/test/java/org/ethereum/vm/VMTest.java @@ -3366,6 +3366,20 @@ void testScriptVersion3() { } } + @Test + void testBASEFEE() { + // Given + program = getProgram(compile("BASEFEE")); + when(program.getActivations().isActive(ConsensusRule.RSKIP412)).thenReturn(true); + + // When + program.fullTrace(); + vm.step(program); + + // Then (See ProgramInvokeMockImpl.getMinimumGasPrice()) + assertEquals("000000000000000000000000000000000000000000000000000003104e60a000", ByteUtil.toHexString(program.getStack().peek().getData())); + } + private VM getSubject() { return new VM(vmConfig, precompiledContracts); } diff --git a/rskj-core/src/test/java/org/ethereum/vm/program/ProgramInvokeImplTest.java b/rskj-core/src/test/java/org/ethereum/vm/program/ProgramInvokeImplTest.java index 43e68746e57..27127f5cede 100644 --- a/rskj-core/src/test/java/org/ethereum/vm/program/ProgramInvokeImplTest.java +++ b/rskj-core/src/test/java/org/ethereum/vm/program/ProgramInvokeImplTest.java @@ -32,6 +32,7 @@ void testEquals_OK() { int transactionIndex1 = 3; byte[] difficulty1 = new byte[]{11}; byte[] gasLimit1 = new byte[]{12}; + byte[] minimumGasPrice1 = new byte[]{13}; Repository repository1 = RepositoryBuilder.build(Collections.emptyMap()); BlockStore blockStore1 = new BlockStoreDummy(); boolean byTestingSuite1 = true; @@ -51,13 +52,14 @@ void testEquals_OK() { int transactionIndex2 = 2; byte[] difficulty2 = new byte[]{2}; byte[] gasLimit2 = new byte[]{1}; + byte[] minimumGasPrice2 = new byte[]{14}; Repository repository2 = null; BlockStore blockStore2 = null; boolean byTestingSuite2 = false; // An object must be equal to itself - ProgramInvokeImpl programInvokeA = new ProgramInvokeImpl(address1, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, repository1, blockStore1, byTestingSuite1); + ProgramInvokeImpl programInvokeA = new ProgramInvokeImpl(address1, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, minimumGasPrice1, repository1, blockStore1, byTestingSuite1); assertEquals(programInvokeA, programInvokeA); @@ -67,15 +69,15 @@ void testEquals_OK() { // Same property values make objects to be equal - ProgramInvokeImpl programInvokeB = new ProgramInvokeImpl(address1, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, repository1, blockStore1, byTestingSuite1); + ProgramInvokeImpl programInvokeB = new ProgramInvokeImpl(address1, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, minimumGasPrice1, repository1, blockStore1, byTestingSuite1); assertEquals(programInvokeA, programInvokeB); // Different combinations of property values make objects to be different - ProgramInvokeImpl programInvokeC = new ProgramInvokeImpl(address2, origin2, caller2, balance2, gasPrice2, gas2, callValue2, msgData2, lastHash2, coinbase2, timestamp2, number2, transactionIndex2, difficulty2, gasLimit2, repository2, blockStore2, byTestingSuite2); - ProgramInvokeImpl programInvokeD = new ProgramInvokeImpl(address2, origin2, caller2, balance1, gasPrice2, gas2, callValue2, msgData2, lastHash2, coinbase2, timestamp2, number1, transactionIndex2, difficulty2, gasLimit2, repository2, blockStore2, byTestingSuite2); - ProgramInvokeImpl programInvokeE = new ProgramInvokeImpl(address2, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, repository1, blockStore1, byTestingSuite1); + ProgramInvokeImpl programInvokeC = new ProgramInvokeImpl(address2, origin2, caller2, balance2, gasPrice2, gas2, callValue2, msgData2, lastHash2, coinbase2, timestamp2, number2, transactionIndex2, difficulty2, gasLimit2, minimumGasPrice2, repository2, blockStore2, byTestingSuite2); + ProgramInvokeImpl programInvokeD = new ProgramInvokeImpl(address2, origin2, caller2, balance1, gasPrice2, gas2, callValue2, msgData2, lastHash2, coinbase2, timestamp2, number1, transactionIndex2, difficulty2, gasLimit2, minimumGasPrice2, repository2, blockStore2, byTestingSuite2); + ProgramInvokeImpl programInvokeE = new ProgramInvokeImpl(address2, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, minimumGasPrice1, repository1, blockStore1, byTestingSuite1); assertNotEquals(programInvokeA, programInvokeC); assertNotEquals(programInvokeA, programInvokeD); @@ -103,6 +105,7 @@ void testHashcode_OK() { int transactionIndex1 = 3; byte[] difficulty1 = new byte[]{11}; byte[] gasLimit1 = new byte[]{12}; + byte[] minimumGasPrice1 = new byte[]{13}; Repository repository1 = RepositoryBuilder.build(Collections.emptyMap()); BlockStore blockStore1 = new BlockStoreDummy(); boolean byTestingSuite1 = true; @@ -122,20 +125,21 @@ void testHashcode_OK() { int transactionIndex2 = 2; byte[] difficulty2 = new byte[]{2}; byte[] gasLimit2 = new byte[]{1}; + byte[] minimumGasPrice2 = new byte[]{14}; Repository repository2 = null; BlockStore blockStore2 = null; boolean byTestingSuite2 = false; // Same properties included in the hashcode makes hashcode to be equal - ProgramInvokeImpl programInvokeA = new ProgramInvokeImpl(address1, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, repository1, blockStore1, byTestingSuite1); - ProgramInvokeImpl programInvokeB = new ProgramInvokeImpl(address1, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, repository1, blockStore1, byTestingSuite1); + ProgramInvokeImpl programInvokeA = new ProgramInvokeImpl(address1, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, minimumGasPrice1, repository1, blockStore1, byTestingSuite1); + ProgramInvokeImpl programInvokeB = new ProgramInvokeImpl(address1, origin1, caller1, balance1, gasPrice1, gas1, callValue1, msgData1, lastHash1, coinbase1, timestamp1, number1, transactionIndex1, difficulty1, gasLimit1, minimumGasPrice1, repository1, blockStore1, byTestingSuite1); assertEquals(programInvokeA.hashCode(), programInvokeB.hashCode()); // Different combinations of property values makes hashcode to be different - ProgramInvokeImpl programInvokeC = new ProgramInvokeImpl(address2, origin2, caller2, balance2, gasPrice2, gas2, callValue2, msgData2, lastHash2, coinbase2, timestamp2, number2, transactionIndex2, difficulty2, gasLimit2, repository2, blockStore2, byTestingSuite2); + ProgramInvokeImpl programInvokeC = new ProgramInvokeImpl(address2, origin2, caller2, balance2, gasPrice2, gas2, callValue2, msgData2, lastHash2, coinbase2, timestamp2, number2, transactionIndex2, difficulty2, gasLimit2, minimumGasPrice2, repository2, blockStore2, byTestingSuite2); assertNotEquals(programInvokeA.hashCode(), programInvokeC.hashCode()); diff --git a/rskj-core/src/test/java/org/ethereum/vm/program/invoke/ProgramInvokeMockImpl.java b/rskj-core/src/test/java/org/ethereum/vm/program/invoke/ProgramInvokeMockImpl.java index 89123900667..f9377f33a65 100644 --- a/rskj-core/src/test/java/org/ethereum/vm/program/invoke/ProgramInvokeMockImpl.java +++ b/rskj-core/src/test/java/org/ethereum/vm/program/invoke/ProgramInvokeMockImpl.java @@ -96,17 +96,20 @@ public ProgramInvokeMockImpl(boolean defaults) { } /* ADDRESS op */ + @Override public DataWord getOwnerAddress() { return DataWord.valueOf(ownerAddress.getBytes()); } /* BALANCE op */ + @Override public DataWord getBalance() { byte[] balance = Hex.decode("0DE0B6B3A7640000"); return DataWord.valueOf(balance); } /* ORIGIN op */ + @Override public DataWord getOriginAddress() { byte[] cowPrivKey = HashUtil.keccak256("horse".getBytes(StandardCharsets.UTF_8)); @@ -116,6 +119,7 @@ public DataWord getOriginAddress() { } /* CALLER op */ + @Override public DataWord getCallerAddress() { byte[] cowPrivKey = HashUtil.keccak256("monkey".getBytes(StandardCharsets.UTF_8)); @@ -125,23 +129,33 @@ public DataWord getCallerAddress() { } /* GASPRICE op */ - public DataWord getMinGasPrice() { + @Override + public DataWord getTxGasPrice() { - byte[] minGasPrice = Hex.decode("09184e72a000"); - return DataWord.valueOf(minGasPrice); + byte[] txGasPrice = Hex.decode("09184e72a000"); + return DataWord.valueOf(txGasPrice); } /* GAS op */ + @Override public long getGas() { return gasLimit; } + /* BASEFEE op */ + @Override + public DataWord getMinimumGasPrice() { + byte[] minimumGasPrice = Hex.decode("03104e60a000"); + return DataWord.valueOf(minimumGasPrice); + } + public void setGas(long gasLimit) { this.gasLimit = gasLimit; } /* CALLVALUE op */ + @Override public DataWord getCallValue() { byte[] balance = Hex.decode("0DE0B6B3A7640000"); return DataWord.valueOf(balance); @@ -154,6 +168,7 @@ public DataWord getCallValue() { */ /* CALLDATALOAD op */ + @Override public DataWord getDataValue(DataWord indexData) { byte[] data = new byte[32]; @@ -171,6 +186,7 @@ public DataWord getDataValue(DataWord indexData) { } /* CALLDATASIZE */ + @Override public DataWord getDataSize() { if (msgData == null || msgData.length == 0) return DataWord.valueOf(new byte[32]); @@ -179,6 +195,7 @@ public DataWord getDataSize() { } /* CALLDATACOPY */ + @Override public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) { int offset = offsetData.value().intValue(); diff --git a/rskj-core/src/test/resources/dsl/opcode/basefee/baseFeeActivatedTest.txt b/rskj-core/src/test/resources/dsl/opcode/basefee/baseFeeActivatedTest.txt new file mode 100644 index 00000000000..5cc27c797c6 --- /dev/null +++ b/rskj-core/src/test/resources/dsl/opcode/basefee/baseFeeActivatedTest.txt @@ -0,0 +1,122 @@ +comment + +// CONTRACT CODE + +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +contract TestBasefee { + constructor() {} + + event OK(); + event ERROR(); + + function checkBasefee(uint256 expected) external { + if (block.basefee == expected) { + emit OK(); + } else { + emit ERROR(); + } + } +} + +// DESCRIPTION + +This contract compares an expected value against the block basefee: + - If block basefee matches the expected value, then the OK event is emmited + - ERROR event is emmited otherwise. + +// CONTRACT BYTECODE + +608060405234801561000f575f80fd5b506101498061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80636b11a75f1461002d575b5f80fd5b610047600480360381019061004291906100e8565b610049565b005b804803610081577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100ae565b7f1c9c433b57013295d61f5c5738f5e2cb1de70bb5ba5b2896edfa8efae345965e60405160405180910390a15b50565b5f80fd5b5f819050919050565b6100c7816100b5565b81146100d1575f80fd5b50565b5f813590506100e2816100be565b92915050565b5f602082840312156100fd576100fc6100b1565b5b5f61010a848285016100d4565b9150509291505056fea2646970667358221220af033c2d8dcac1c830549d1c3144ac62d2ce6d74e5363841f42152caab9ec22a64736f6c63430008170033 + +// CONTRACT CALLS + +- checkBasefee(0) // Should emit "OK" as this is the minimum gas price on test + + 6b11a75f0000000000000000000000000000000000000000000000000000000000000000 + +- checkBasefee(1) // Should emit "ERROR" as it is not the minimum gas price + + 6b11a75f0000000000000000000000000000000000000000000000000000000000000001 + +end + +# Create and fund new account +account_new acc1 10000000 + +# Create transaction to deploy TestBasefee contract +transaction_build txTestBasefee + sender acc1 + receiverAddress 00 + value 0 + data 608060405234801561000f575f80fd5b506101498061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80636b11a75f1461002d575b5f80fd5b610047600480360381019061004291906100e8565b610049565b005b804803610081577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100ae565b7f1c9c433b57013295d61f5c5738f5e2cb1de70bb5ba5b2896edfa8efae345965e60405160405180910390a15b50565b5f80fd5b5f819050919050565b6100c7816100b5565b81146100d1575f80fd5b50565b5f813590506100e2816100be565b92915050565b5f602082840312156100fd576100fc6100b1565b5b5f61010a848285016100d4565b9150509291505056fea2646970667358221220af033c2d8dcac1c830549d1c3144ac62d2ce6d74e5363841f42152caab9ec22a64736f6c63430008170033 + gas 1200000 + build + +# Create block to hold txTestBasefee transaction +block_build b01 + parent g00 + transactions txTestBasefee + build + +# Connect block +block_connect b01 + +# Check b01 is best block +assert_best b01 + +# Check txTestBasefee succeded +assert_tx_success txTestBasefee + +# Create transaction to execute testBasefee(0) method +transaction_build txTestBasefeeOKCall + sender acc1 + nonce 1 + contract txTestBasefee + value 0 + data 6b11a75f0000000000000000000000000000000000000000000000000000000000000000 + gas 30000 + build + +# Create block to hold txTestBasefeeOKCall transaction +block_build b02 + parent b01 + transactions txTestBasefeeOKCall + gasLimit 6500000 + build + +# Connect block +block_connect b02 + +# Check b02 is best block +assert_best b02 + +# Check txTestBasefeeOKCall succeded +assert_tx_success txTestBasefeeOKCall + +# Create transaction to execute testBasefee(1) method +transaction_build txTestBasefeeErrorCall + sender acc1 + nonce 2 + contract txTestBasefee + value 0 + data 6b11a75f0000000000000000000000000000000000000000000000000000000000000001 + gas 30000 + build + +# Create block to hold txTestBasefeeErrorCall transaction +block_build b03 + parent b02 + transactions txTestBasefeeErrorCall + gasLimit 6500000 + build + +# Connect block +block_connect b03 + +# Check b03 is best block +assert_best b03 + +# Check txTestBasefeeErrorCall succeded +assert_tx_success txTestBasefeeErrorCall \ No newline at end of file diff --git a/rskj-core/src/test/resources/dsl/opcode/basefee/baseFeeNotActivatedTest.txt b/rskj-core/src/test/resources/dsl/opcode/basefee/baseFeeNotActivatedTest.txt new file mode 100644 index 00000000000..6ce240f312e --- /dev/null +++ b/rskj-core/src/test/resources/dsl/opcode/basefee/baseFeeNotActivatedTest.txt @@ -0,0 +1,89 @@ +comment + +// CONTRACT CODE + +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +contract TestBasefee { + constructor() {} + + event OK(); + event ERROR(); + + function checkBasefee(uint256 expected) external { + if (block.basefee == expected) { + emit OK(); + } else { + emit ERROR(); + } + } +} + +// DESCRIPTION + +This contract compares an expected value against the block basefee: + - If block basefee matches the expected value, then the OK event is emmited + - ERROR event is emmited otherwise. + +// CONTRACT BYTECODE + +608060405234801561000f575f80fd5b506101498061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80636b11a75f1461002d575b5f80fd5b610047600480360381019061004291906100e8565b610049565b005b804803610081577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100ae565b7f1c9c433b57013295d61f5c5738f5e2cb1de70bb5ba5b2896edfa8efae345965e60405160405180910390a15b50565b5f80fd5b5f819050919050565b6100c7816100b5565b81146100d1575f80fd5b50565b5f813590506100e2816100be565b92915050565b5f602082840312156100fd576100fc6100b1565b5b5f61010a848285016100d4565b9150509291505056fea2646970667358221220af033c2d8dcac1c830549d1c3144ac62d2ce6d74e5363841f42152caab9ec22a64736f6c63430008170033 + +// CONTRACT CALL + +- checkBasefee(0) // Param doesn't matter in this case, so we picked 0 (lazy? XD) + + 6b11a75f0000000000000000000000000000000000000000000000000000000000000000 + +end + +# Create and fund new account +account_new acc1 10000000 + +# Create transaction to deploy TestBasefee contract +transaction_build txTestBasefee + sender acc1 + receiverAddress 00 + value 0 + data 608060405234801561000f575f80fd5b506101498061001d5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80636b11a75f1461002d575b5f80fd5b610047600480360381019061004291906100e8565b610049565b005b804803610081577fd48fe2800bace8f5ca2450feacbd6efc681b1cd0115019bb49fa529b6171bf6760405160405180910390a16100ae565b7f1c9c433b57013295d61f5c5738f5e2cb1de70bb5ba5b2896edfa8efae345965e60405160405180910390a15b50565b5f80fd5b5f819050919050565b6100c7816100b5565b81146100d1575f80fd5b50565b5f813590506100e2816100be565b92915050565b5f602082840312156100fd576100fc6100b1565b5b5f61010a848285016100d4565b9150509291505056fea2646970667358221220af033c2d8dcac1c830549d1c3144ac62d2ce6d74e5363841f42152caab9ec22a64736f6c63430008170033 + gas 1200000 + build + +# Create block to hold txTestBasefee transaction +block_build b01 + parent g00 + transactions txTestBasefee + build + +# Connect block +block_connect b01 + +# Check b01 is best block +assert_best b01 + +# Check txTestBasefee succeded +assert_tx_success txTestBasefee + +# Create transaction to execute testBasefee(0) method +transaction_build txTestBasefeeNotActivated + sender acc1 + nonce 1 + contract txTestBasefee + value 0 + data 6b11a75f0000000000000000000000000000000000000000000000000000000000000000 + gas 30000 + build + +# Create block to hold txTestBasefeeNotActivated transaction +block_build b02 + parent b01 + transactions txTestBasefeeNotActivated + gasLimit 6500000 + build + +# Connect block +block_connect b02 + +# Check b02 is best block +assert_best b02 \ No newline at end of file