diff --git a/libconsensus b/libconsensus index ef26c4a02..79d6b7fd6 160000 --- a/libconsensus +++ b/libconsensus @@ -1 +1 @@ -Subproject commit ef26c4a020884f280addd9b54037ee77fe396ecf +Subproject commit 79d6b7fd676e5f52aed5ff626066e810d5219f13 diff --git a/libethcore/ChainOperationParams.h b/libethcore/ChainOperationParams.h index ec57fe4ed..db6e52683 100644 --- a/libethcore/ChainOperationParams.h +++ b/libethcore/ChainOperationParams.h @@ -175,6 +175,7 @@ struct SChain { time_t verifyDaSigsPatchTimestamp = 0; time_t storageDestructionPatchTimestamp = 0; time_t powCheckPatchTimestamp = 0; + time_t pushZeroPatchTimestamp = 0; time_t skipInvalidTransactionsPatchTimestamp = 0; SChain() { diff --git a/libethereum/ChainParams.cpp b/libethereum/ChainParams.cpp index fb48f748d..8ed23de33 100644 --- a/libethereum/ChainParams.cpp +++ b/libethereum/ChainParams.cpp @@ -266,6 +266,10 @@ ChainParams ChainParams::loadConfig( sChainObj.at( "powCheckPatchTimestamp" ).get_int64() : 0; + s.pushZeroPatchTimestamp = sChainObj.count( "pushZeroPatchTimestamp" ) ? + sChainObj.at( "pushZeroPatchTimestamp" ).get_int64() : + 0; + s.skipInvalidTransactionsPatchTimestamp = sChainObj.count( "skipInvalidTransactionsPatchTimestamp" ) ? sChainObj.at( "skipInvalidTransactionsPatchTimestamp" ).get_int64() : diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 06cec9516..d20bdc950 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -163,6 +164,7 @@ Client::Client( ChainParams const& _params, int _networkID, RevertableFSPatch::setTimestamp( chainParams().sChain.revertableFSPatchTimestamp ); StorageDestructionPatch::setTimestamp( chainParams().sChain.storageDestructionPatchTimestamp ); POWCheckPatch::setTimestamp( chainParams().sChain.powCheckPatchTimestamp ); + PushZeroPatch::setTimestamp( chainParams().sChain.pushZeroPatchTimestamp ); SkipInvalidTransactionsPatch::setTimestamp( this->chainParams().sChain.skipInvalidTransactionsPatchTimestamp ); } @@ -657,6 +659,7 @@ size_t Client::syncTransactions( RevertableFSPatch::lastBlockTimestamp = blockChain().info().timestamp(); StorageDestructionPatch::lastBlockTimestamp = blockChain().info().timestamp(); POWCheckPatch::lastBlockTimestamp = blockChain().info().timestamp(); + PushZeroPatch::lastBlockTimestamp = blockChain().info().timestamp(); SkipInvalidTransactionsPatch::lastBlockTimestamp = blockChain().info().timestamp(); DEV_WRITE_GUARDED( x_working ) { diff --git a/libethereum/ValidationSchemes.cpp b/libethereum/ValidationSchemes.cpp index 38231c130..fdf83fdeb 100644 --- a/libethereum/ValidationSchemes.cpp +++ b/libethereum/ValidationSchemes.cpp @@ -268,6 +268,8 @@ void validateConfigJson( js::mObject const& _obj ) { { "storageDestructionPatchTimestamp", { { js::int_type }, JsonFieldPresence::Optional } }, { "powCheckPatchTimestamp", { { js::int_type }, JsonFieldPresence::Optional } }, + { "pushZeroPatchTimestamp", { { js::int_type }, JsonFieldPresence::Optional } }, + { "nodeGroups", { { js::obj_type }, JsonFieldPresence::Optional } }, { "nodeGroups", { { js::obj_type }, JsonFieldPresence::Optional } }, { "skipInvalidTransactionsPatchTimestamp", { { js::int_type }, JsonFieldPresence::Optional } } } ); diff --git a/libevm/Instruction.cpp b/libevm/Instruction.cpp index 78e8eec2d..bb4f25d20 100644 --- a/libevm/Instruction.cpp +++ b/libevm/Instruction.cpp @@ -86,6 +86,10 @@ static const std::map c_instructionInfo = { Instruction::MSIZE, { "MSIZE", 0, 1, Tier::Base } }, { Instruction::GAS, { "GAS", 0, 1, Tier::Base } }, { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, Tier::Special } }, + // As per EIP-3855 PUSH0 instruction tire is base (2 gas units) + // As all other PUSH instructions, it removes zero elements from stack and + // pushes 1 element to stack + { Instruction::PUSH0, { "PUSH0", 0, 1, Tier::Base } }, { Instruction::PUSH1, { "PUSH1", 0, 1, Tier::VeryLow } }, { Instruction::PUSH2, { "PUSH2", 0, 1, Tier::VeryLow } }, { Instruction::PUSH3, { "PUSH3", 0, 1, Tier::VeryLow } }, diff --git a/libevm/Instruction.h b/libevm/Instruction.h index 573b57107..0dc52bdad 100644 --- a/libevm/Instruction.h +++ b/libevm/Instruction.h @@ -94,6 +94,7 @@ enum class Instruction : uint8_t { GAS, ///< get the amount of available gas JUMPDEST, ///< set a potential jump destination + PUSH0 = 0x5f, // EIP-3855 PUSH1 = 0x60, ///< place 1 byte item on stack PUSH2, ///< place 2 byte item on stack PUSH3, ///< place 3 byte item on stack diff --git a/libevm/LegacyVM.cpp b/libevm/LegacyVM.cpp index 8a0775112..a987a3b0c 100644 --- a/libevm/LegacyVM.cpp +++ b/libevm/LegacyVM.cpp @@ -16,6 +16,7 @@ */ #include "LegacyVM.h" +#include "libskale/PushZeroPatch.h" using namespace std; using namespace dev; @@ -1355,6 +1356,21 @@ void LegacyVM::interpretCases() { } CONTINUE + // EIP-3855. Code PUSH0 is similar to PUSH1 but pushes 0 + // we need to increment program counter only by one since + // the value is not read from program code as in PUSH1 + CASE( PUSH0 ) { + if ( !PushZeroPatch::isEnabled() ) { + throwBadInstruction(); + } + ON_OP(); + updateIOGas(); + m_SPP[0] = 0; + ++m_PC; + }; + CONTINUE + + CASE( PUSH1 ) { ON_OP(); updateIOGas(); diff --git a/libevm/LegacyVMConfig.h b/libevm/LegacyVMConfig.h index 4b8bde611..9908d712c 100644 --- a/libevm/LegacyVMConfig.h +++ b/libevm/LegacyVMConfig.h @@ -254,7 +254,7 @@ namespace eth { &&BEGINDATA, \ &&BEGINSUB, \ &&INVALID, \ - &&INVALID, \ + &&PUSH0, /* EIP-3855 */ \ &&PUSH1, /* 60, */ \ &&PUSH2, \ &&PUSH3, \ diff --git a/libskale/CMakeLists.txt b/libskale/CMakeLists.txt index 73acfbbf5..ba774b211 100644 --- a/libskale/CMakeLists.txt +++ b/libskale/CMakeLists.txt @@ -20,6 +20,7 @@ set(sources OverlayFS.cpp StorageDestructionPatch.cpp POWCheckPatch.cpp + PushZeroPatch.cpp SkipInvalidTransactionsPatch.cpp ) diff --git a/libskale/PushZeroPatch.cpp b/libskale/PushZeroPatch.cpp new file mode 100644 index 000000000..e7ef4f6c1 --- /dev/null +++ b/libskale/PushZeroPatch.cpp @@ -0,0 +1,11 @@ +#include "PushZeroPatch.h" + +time_t PushZeroPatch::pushZeroPatchTimestamp; +time_t PushZeroPatch::lastBlockTimestamp; + +bool PushZeroPatch::isEnabled() { + if ( pushZeroPatchTimestamp == 0 ) { + return false; + } + return pushZeroPatchTimestamp <= lastBlockTimestamp; +} diff --git a/libskale/PushZeroPatch.h b/libskale/PushZeroPatch.h new file mode 100644 index 000000000..6ebab4e9f --- /dev/null +++ b/libskale/PushZeroPatch.h @@ -0,0 +1,27 @@ +#include +#include + +namespace dev { +namespace eth { +class Client; +} +} // namespace dev + +/* + * Context: enable effective storage destruction + */ +class PushZeroPatch : public SchainPatch { +public: + static bool isEnabled(); + + static void setTimestamp( time_t _timeStamp ) { + printInfo( __FILE__, _timeStamp ); + pushZeroPatchTimestamp = _timeStamp; + } + + +private: + friend class dev::eth::Client; + static time_t pushZeroPatchTimestamp; + static time_t lastBlockTimestamp; +}; \ No newline at end of file diff --git a/test/historicstate/configs/basic_config.json b/test/historicstate/configs/basic_config.json index aa5915277..ea3bbf1d6 100644 --- a/test/historicstate/configs/basic_config.json +++ b/test/historicstate/configs/basic_config.json @@ -322,10 +322,11 @@ "sChain": { "schainName": "TestChain", - "schainID": 1, + "schainID": 5, "schainOwner": "0x907cd0881E50d359bb9Fd120B1A5A143b1C97De6", "contractStorageLimit": 10000000000, "emptyBlockIntervalMs": 10000, + "pushZeroPatchTimestamp": 1, "nodes": [ { "nodeID": 1112, "ip": "127.0.0.1", "basePort": 1231, "schainIndex" : 1, "publicKey":""} ] diff --git a/test/historicstate/hardhat/contracts/Push0.sol b/test/historicstate/hardhat/contracts/Push0.sol new file mode 100644 index 000000000..e09e457b6 --- /dev/null +++ b/test/historicstate/hardhat/contracts/Push0.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Push0 { + + uint256 public constant ZERO = 0; + + function getZero() public { + // this triggers compiler using push0 to stack since operations use lots of zeros + uint256 one = 0; + one = one + 1 + ZERO; + uint256 two = one * 0; + uint256 three = one * ZERO; + } +} \ No newline at end of file diff --git a/test/historicstate/hardhat/hardhat.config.js b/test/historicstate/hardhat/hardhat.config.js index d369e02ba..3ff48bfba 100644 --- a/test/historicstate/hardhat/hardhat.config.js +++ b/test/historicstate/hardhat/hardhat.config.js @@ -10,7 +10,7 @@ module.exports = { const INSECURE_PRIVATE_KEY = "bd200f4e7f597f3c2c77fb405ee7fabeb249f63f03f43d5927b4fa0c43cfe85e"; module.exports = { - solidity: "0.8.9", + solidity: "0.8.20", networks: { skaled: { url: `http://localhost:1234`, diff --git a/test/historicstate/hardhat/scripts/push0_test.ts b/test/historicstate/hardhat/scripts/push0_test.ts new file mode 100644 index 000000000..d76a0a934 --- /dev/null +++ b/test/historicstate/hardhat/scripts/push0_test.ts @@ -0,0 +1,71 @@ +const OWNER_ADDRESS: string = "0x907cd0881E50d359bb9Fd120B1A5A143b1C97De6"; +const ZERO_ADDRESS: string = "0xO000000000000000000000000000000000000000"; +const INITIAL_MINT: bigint = 10000000000000000000000000000000000000000; + +import {ethers} from "hardhat"; + +async function waitUntilNextBlock() { + + const current = await hre.ethers.provider.getBlockNumber(); + let newBlock = current; + console.log(`BLOCK_NUMBER ${current}`); + + while (newBlock == current) { + newBlock = await hre.ethers.provider.getBlockNumber(); + } + + console.log(`BLOCK_NUMBER ${newBlock}`); + + return current; + +} + +function CHECK(result: any): void { + if (!result) { + const message: string = `Check failed ${result}` + console.log(message); + throw message; + } +} + +async function getAndPrintTrace(hash: string): Promise { + + const trace = await ethers.provider.send('debug_traceTransaction', [hash, {}]); + + console.log(JSON.stringify(trace, null, 4)); + return trace; +} + +async function deployWriteAndDestroy(): Promise { + + console.log(`Deploying ...`); + + const Push0Test = await ethers.getContractFactory("Push0"); + const test = await Push0Test.deploy(); + const testContract = await test.deployed(); + + + const deployBn = await ethers.provider.getBlockNumber(); + + const hash = testContract.deployTransaction.hash; + console.log(`Gas limit ${testContract.deployTransaction.gasLimit}`); + console.log(`Contract deployed to ${testContract.address} at block ${deployBn} tx hash ${hash}`); + + console.log(`Now testing`); + + const transferReceipt = await testContract.getZero() + console.log(`Gas limit ${transferReceipt.gasLimit}`); + + +} + +async function main(): Promise { + await deployWriteAndDestroy(); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error: any) => { + console.error(error); + process.exitCode = 1; +}); \ No newline at end of file