diff --git a/libdevcore/LevelDB.h b/libdevcore/LevelDB.h
index 9674cb9ec..5aa1f7df0 100644
--- a/libdevcore/LevelDB.h
+++ b/libdevcore/LevelDB.h
@@ -71,6 +71,7 @@ class LevelDB : public DatabaseFace {
static std::atomic< uint64_t > g_keyDeletesStats;
// count of the keys that are scheduled to be deleted but are not yet deleted
static std::atomic< uint64_t > g_keysToBeDeletedStats;
+ static uint64_t getCurrentTimeMs();
private:
std::unique_ptr< leveldb::DB > m_db;
@@ -123,7 +124,6 @@ class LevelDB : public DatabaseFace {
}
};
void openDBInstanceUnsafe();
- uint64_t getCurrentTimeMs();
void reopenDataBaseIfNeeded();
};
diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp
index 1cd97c9f3..faf04ec7d 100644
--- a/libethereum/Client.cpp
+++ b/libethereum/Client.cpp
@@ -674,10 +674,13 @@ size_t Client::syncTransactions(
// Tell network about the new transactions.
m_skaleHost->noteNewTransactions();
- ctrace << cc::debug( "Processed " ) << cc::size10( newPendingReceipts.size() )
- << cc::debug( " transactions in " ) << cc::size10( timer.elapsed() * 1000 )
- << cc::debug( "(" ) << ( bool ) m_syncTransactionQueue << cc::debug( ")" );
+ ctrace << "Processed " << newPendingReceipts.size() << " transactions in "
+ << timer.elapsed() * 1000 << "(" << ( bool ) m_syncTransactionQueue << ")";
+#ifdef HISTORIC_STATE
+ LOG( m_logger ) << "HSCT: "
+ << m_working.mutableState().mutableHistoricState().getAndResetBlockCommitTime();
+#endif
return goodReceipts;
}
diff --git a/libethereum/SkaleHost.cpp b/libethereum/SkaleHost.cpp
index a2f9fcd9e..47714ebfe 100644
--- a/libethereum/SkaleHost.cpp
+++ b/libethereum/SkaleHost.cpp
@@ -731,6 +731,7 @@ void SkaleHost::createBlock( const ConsensusExtFace::transactions_vector& _appro
skaledTimeFinish - skaledTimeStart )
.count();
}
+
latestBlockTime = skaledTimeFinish;
LOG( m_debugLogger ) << "Successfully imported " << n_succeeded << " of " << out_txns.size()
<< " transactions";
diff --git a/libevm/LegacyVM.cpp b/libevm/LegacyVM.cpp
index f0128a9f2..a6d64254d 100644
--- a/libevm/LegacyVM.cpp
+++ b/libevm/LegacyVM.cpp
@@ -231,25 +231,29 @@ void LegacyVM::interpretCases() {
//
CASE( CREATE2 ) {
- // for CREATE and CREATE2 we call ON_OP in caseCreate, since it calculates
- // correct gas cost
- // ON_OP();
- if ( !m_schedule->haveCreate2 )
+ if ( !m_schedule->haveCreate2 ) {
+ ON_OP();
throwBadInstruction();
- if ( m_ext->staticCall )
+ }
+ if ( m_ext->staticCall ) {
+ ON_OP();
throwDisallowedStateChange();
+ }
+ // for CREATE and CREATE2 we call ON_OP in caseCreate, since it calculates
+ // correct gas cost
m_bounce = &LegacyVM::caseCreate;
}
BREAK
CASE( CREATE ) {
- // for CREATE and CREATE2 we call ON_OP in caseCreate, since it calculates
- // correct gas cost
- // ON_OP();
- if ( m_ext->staticCall )
+ if ( m_ext->staticCall ) {
+ ON_OP();
throwDisallowedStateChange();
+ }
+ // for CREATE and CREATE2 we call ON_OP in caseCreate, since it calculates
+ // correct gas cost
m_bounce = &LegacyVM::caseCreate;
}
BREAK
diff --git a/libhistoric/AlethExecutive.cpp b/libhistoric/AlethExecutive.cpp
index fbe3de73e..b5c6cc372 100644
--- a/libhistoric/AlethExecutive.cpp
+++ b/libhistoric/AlethExecutive.cpp
@@ -362,8 +362,7 @@ bool AlethExecutive::go( OnOpFunc const& _onOp ) {
throw;
#ifdef HISTORIC_STATE
} catch ( VMTracingError const& _e ) {
- cwarn << "Tracing error: " << *boost::get_error_info< errinfo_evmcStatusCode >( _e )
- << ")";
+ cerr << "VM tracing error:" << _e.message;
revert();
throw;
#endif
diff --git a/libhistoric/AlethStandardTrace.cpp b/libhistoric/AlethStandardTrace.cpp
index affcdf988..bc840eab8 100644
--- a/libhistoric/AlethStandardTrace.cpp
+++ b/libhistoric/AlethStandardTrace.cpp
@@ -207,7 +207,8 @@ void AlethStandardTrace::setTopFunctionCall(
void AlethStandardTrace::recordFunctionReturned(
evmc_status_code _status, const vector< uint8_t >& _returnData, uint64_t _gasUsed ) {
- STATE_CHECK( getLastOpRecord()->m_gasRemaining >= getLastOpRecord()->m_opGas )
+ // note that m_gas remaining can be less than m_opGas. This happens in case
+ // of out of gas revert
STATE_CHECK( m_currentlyExecutingFunctionCall )
STATE_CHECK( !m_isFinalized )
@@ -348,7 +349,6 @@ shared_ptr< OpExecutionRecord > AlethStandardTrace::createOpExecutionRecord( uin
opName = "KECCAK256";
}
-
auto executionRecord = std::make_shared< OpExecutionRecord >( ext.depth, _inst,
( uint64_t ) _gasRemaining, ( uint64_t ) _gasOpGas, _pc, ext.sub.refunds, opName );
diff --git a/libhistoric/CallTracePrinter.cpp b/libhistoric/CallTracePrinter.cpp
index ea7a46ab9..ed2136a03 100644
--- a/libhistoric/CallTracePrinter.cpp
+++ b/libhistoric/CallTracePrinter.cpp
@@ -52,6 +52,13 @@ void CallTracePrinter::printContractTransactionTrace(
// call trace on the top Solidity function call in the stack
// this will also recursively call printTrace on nested calls if exist
topFunctionCall->printTrace( _jsonTrace, _statePost, 0, m_trace.getOptions() );
+
+ // for the top function the gas limit that geth prints is the transaction gas limit
+ // and not the gas limit given to the top level function, which is smaller because of 21000
+ // transaction cost and potentially eth transfer cost
+ _jsonTrace["gas"] =
+ AlethStandardTrace::toGethCompatibleCompactHexPrefixed( m_trace.getGasLimit() );
+
// handle the case of a transaction that deploys a contract
// in this case geth prints transaction input data as input
// end prints to as newly created contract address
diff --git a/libhistoric/HistoricState.cpp b/libhistoric/HistoricState.cpp
index 6d5ef615c..9f3938a71 100644
--- a/libhistoric/HistoricState.cpp
+++ b/libhistoric/HistoricState.cpp
@@ -44,7 +44,8 @@ HistoricState::HistoricState( HistoricState const& _s )
m_unchangedCacheEntries( _s.m_unchangedCacheEntries ),
m_nonExistingAccountsCache( _s.m_nonExistingAccountsCache ),
m_unrevertablyTouched( _s.m_unrevertablyTouched ),
- m_accountStartNonce( _s.m_accountStartNonce ) {}
+ m_accountStartNonce( _s.m_accountStartNonce ),
+ m_totalTimeSpentInStateCommitsPerBlock( _s.m_totalTimeSpentInStateCommitsPerBlock ) {}
OverlayDB HistoricState::openDB(
fs::path const& _basePath, h256 const& _genesisHash, WithExisting _we ) {
@@ -137,6 +138,7 @@ HistoricState& HistoricState::operator=( HistoricState const& _s ) {
m_nonExistingAccountsCache = _s.m_nonExistingAccountsCache;
m_unrevertablyTouched = _s.m_unrevertablyTouched;
m_accountStartNonce = _s.m_accountStartNonce;
+ m_totalTimeSpentInStateCommitsPerBlock = _s.m_totalTimeSpentInStateCommitsPerBlock;
return *this;
}
@@ -194,11 +196,20 @@ void HistoricState::clearCacheIfTooLarge() const {
}
void HistoricState::commitExternalChanges( AccountMap const& _accountMap ) {
+ auto historicStateStart = dev::db::LevelDB::getCurrentTimeMs();
commitExternalChangesIntoTrieDB( _accountMap, m_state );
m_state.db()->commit();
m_changeLog.clear();
m_cache.clear();
m_unchangedCacheEntries.clear();
+ auto historicStateFinish = dev::db::LevelDB::getCurrentTimeMs();
+ m_totalTimeSpentInStateCommitsPerBlock += historicStateFinish - historicStateStart;
+}
+
+uint64_t HistoricState::getAndResetBlockCommitTime() {
+ uint64_t retVal = m_totalTimeSpentInStateCommitsPerBlock;
+ m_totalTimeSpentInStateCommitsPerBlock = 0;
+ return retVal;
}
diff --git a/libhistoric/HistoricState.h b/libhistoric/HistoricState.h
index 201e214d3..8c3a8cb4a 100644
--- a/libhistoric/HistoricState.h
+++ b/libhistoric/HistoricState.h
@@ -296,6 +296,8 @@ class HistoricState {
void setRootFromDB();
+ uint64_t getAndResetBlockCommitTime();
+
private:
/// Turns all "touched" empty accounts into non-alive accounts.
void removeEmptyAccounts();
@@ -345,6 +347,8 @@ class HistoricState {
AddressHash commitExternalChangesIntoTrieDB(
AccountMap const& _cache, SecureTrieDB< Address, OverlayDB >& _state );
+
+ uint64_t m_totalTimeSpentInStateCommitsPerBlock = 0;
};
std::ostream& operator<<( std::ostream& _out, HistoricState const& _s );
diff --git a/test/historicstate/hardhat/contracts/Erc20Custom.sol b/test/historicstate/hardhat/contracts/Erc20Custom.sol
new file mode 100644
index 000000000..fe4a6a36f
--- /dev/null
+++ b/test/historicstate/hardhat/contracts/Erc20Custom.sol
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+/**
+ * ERC20Custom.sol - SKALE Test tokens
+ * Copyright (C) 2022-Present SKALE Labs
+ * @author Artem Payvin
+ *
+ * SKALE IMA is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * SKALE IMA 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 Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with SKALE IMA. If not, see .
+ */
+
+pragma solidity ^0.8.20;
+
+contract ERC20Custom {
+
+ mapping (address => uint256) private _balances;
+
+ mapping (address => mapping (address => uint256)) private _allowances;
+
+ uint256 private _totalSupply;
+
+ string private _name;
+ string private _symbol;
+ uint8 private _decimals;
+
+ event Approval(address owner, address spender, uint256 amount);
+
+ event Transfer(address from, address to, uint256 amount);
+
+ constructor(string memory tokenName, string memory tokenSymbol) {
+ _name = tokenName;
+ _symbol = tokenSymbol;
+ _decimals = 18;
+
+ _mint(tx.origin, 10);
+ }
+
+ function mint(address account, uint256 amount) external returns (bool) {
+ _mint(account, amount);
+ return true;
+ }
+
+ /**
+ * @dev burn - destroys token on msg sender
+ *
+ * NEED TO HAVE THIS FUNCTION ON SKALE-CHAIN
+ *
+ * @param amount - amount of tokens
+ */
+ function burn(uint256 amount) external {
+ _burn(msg.sender, amount);
+ }
+
+ function name() public view returns (string memory) {
+ return _name;
+ }
+
+ function symbol() public view returns (string memory) {
+ return _symbol;
+ }
+
+ function decimals() public view returns (uint8) {
+ return _decimals;
+ }
+
+ function totalSupply() public view returns (uint256) {
+ return _totalSupply;
+ }
+
+ function balanceOf(address account) public view returns (uint256) {
+ return _balances[account];
+ }
+
+ function transfer(address recipient, uint256 amount) public virtual returns (bool) {
+ this._transfer1();
+ return true;
+ }
+
+ function allowance(address owner, address spender) public view virtual returns (uint256) {
+ return _allowances[owner][spender];
+ }
+
+ function approve(address spender, uint256 amount) public virtual returns (bool) {
+ _approve(msg.sender, spender, amount);
+ return true;
+ }
+
+ function transferFrom(address sender, address recipient, uint256 amount) public virtual returns (bool) {
+ _transfer(sender, recipient, amount);
+ _approve(
+ sender,
+ msg.sender,
+ _allowances[sender][msg.sender] - amount
+ );
+ return true;
+ }
+
+ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
+ _approve(msg.sender, spender, _allowances[msg.sender][spender] + addedValue);
+ return true;
+ }
+
+ function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
+ _approve(
+ msg.sender,
+ spender,
+ _allowances[msg.sender][spender] - subtractedValue
+ );
+ return true;
+ }
+
+ function _transfer1() public {
+ }
+
+ function _transfer(address sender, address recipient, uint256 amount) internal virtual {
+ require(sender != address(0), "ERC20: transfer from the zero address");
+ require(recipient != address(0), "ERC20: transfer to the zero address");
+
+ _balances[sender] = _balances[sender] - amount;
+ _balances[recipient] = _balances[recipient] + amount;
+ emit Transfer(sender, recipient, amount);
+ }
+
+ function _mint(address account, uint256 amount) internal virtual {
+ require(account != address(0), "ERC20: mint to the zero address");
+
+ _totalSupply = _totalSupply + amount;
+ _balances[account] = _balances[account] + amount;
+ emit Transfer(address(0), account, amount);
+ }
+
+ function _burn(address account, uint256 amount) internal virtual {
+ require(account != address(0), "ERC20: burn from the zero address");
+
+ _balances[account] = _balances[account] - amount;
+ _totalSupply = _totalSupply - amount;
+ emit Transfer(account, address(0), amount);
+ }
+
+ function _approve(address owner, address spender, uint256 amount) internal virtual {
+ require(owner != address(0), "ERC20: approve from the zero address");
+ require(spender != address(0), "ERC20: approve to the zero address");
+
+ _allowances[owner][spender] = amount;
+ emit Approval(owner, spender, amount);
+ }
+}
\ No newline at end of file
diff --git a/test/historicstate/hardhat/run_geth.py b/test/historicstate/hardhat/run_geth.py
index 22d576487..f1dd69709 100755
--- a/test/historicstate/hardhat/run_geth.py
+++ b/test/historicstate/hardhat/run_geth.py
@@ -50,7 +50,7 @@ def add_ether_to_account(address, amount):
return
# Unlock the default account (coinbase)
- coinbase = w3.eth.coinbase
+ coinbase = w3.eth.accounts[0]
w3.geth.personal.unlock_account(coinbase, '', 0)
# Convert Ether to Wei
diff --git a/test/historicstate/hardhat/scripts/geth_traces/ERC20Custom.transfer.callTracer.json b/test/historicstate/hardhat/scripts/geth_traces/ERC20Custom.transfer.callTracer.json
new file mode 100644
index 000000000..9cc83b2b1
--- /dev/null
+++ b/test/historicstate/hardhat/scripts/geth_traces/ERC20Custom.transfer.callTracer.json
@@ -0,0 +1,21 @@
+{
+ "calls": [
+ {
+ "error": "out of gas",
+ "from": "ERC20Custom.address",
+ "gas": "0x51",
+ "gasUsed": "0x30d",
+ "input": "0x53bb9b8a",
+ "to": "ERC20Custom.address",
+ "type": "CALL"
+ }
+ ],
+ "error": "out of gas",
+ "from": "OWNER.address",
+ "gas": "0x6239",
+ "gasUsed": "0x6239",
+ "input": "0xa9059cbb000000000000000000000000ce5c7ca85f8cb94fa284a303348ef42add23f5e70000000000000000000000000000000000000000000000000000000000000001",
+ "to": "ERC20Custom.address",
+ "type": "CALL",
+ "value": "0x0"
+}
\ No newline at end of file
diff --git a/test/historicstate/hardhat/scripts/token_revert.ts b/test/historicstate/hardhat/scripts/token_revert.ts
new file mode 100644
index 000000000..dcbf3a5bf
--- /dev/null
+++ b/test/historicstate/hardhat/scripts/token_revert.ts
@@ -0,0 +1,402 @@
+import {mkdir, readdir, writeFileSync, readFile, unlink} from "fs";
+
+const fs = require('fs');
+import {existsSync} from "fs";
+import deepDiff, {diff} from 'deep-diff';
+import {expect} from "chai";
+import * as path from 'path';
+import {int, string} from "hardhat/internal/core/params/argumentTypes";
+import internal from "node:stream";
+import exp from "node:constants";
+
+const OWNER_ADDRESS: string = "0x907cd0881E50d359bb9Fd120B1A5A143b1C97De6";
+const CALL_ADDRESS: string = "0xCe5c7ca85F8cB94FA284a303348ef42ADD23f5e7";
+
+const ZERO_ADDRESS: string = '0x0000000000000000000000000000000000000000';
+
+const SKALE_TRACES_DIR = "/tmp/skale_traces/"
+const GETH_TRACES_DIR = "scripts/geth_traces/"
+
+const DEFAULT_TRACER = "defaultTracer";
+const CALL_TRACER = "callTracer";
+const PRESTATE_TRACER = "prestateTracer";
+const PRESTATEDIFF_TRACER = "prestateDiffTracer";
+const FOURBYTE_TRACER = "4byteTracer";
+const REPLAY_TRACER = "replayTracer";
+
+var DEPLOYED_CONTRACT_ADDRESS_LOWER_CASE: string = "";
+var globalCallCount = 0;
+
+
+async function replaceAddressesWithSymbolicNames(_traceFileName: string) {
+
+ let callAddressLowerCase = CALL_ADDRESS.toLowerCase();
+
+ await replaceStringInFile(SKALE_TRACES_DIR + _traceFileName,
+ callAddressLowerCase, "CALL.address");
+
+ let ownerAddressLowerCase = OWNER_ADDRESS.toLowerCase();
+
+ await replaceStringInFile(SKALE_TRACES_DIR + _traceFileName,
+ ownerAddressLowerCase, "OWNER.address");
+
+ // if the contract has been deployed, also replace contract address
+
+ if (DEPLOYED_CONTRACT_ADDRESS_LOWER_CASE.length > 0) {
+ await replaceStringInFile(SKALE_TRACES_DIR + _traceFileName,
+ DEPLOYED_CONTRACT_ADDRESS_LOWER_CASE, TEST_CONTRACT_NAME + ".address");
+
+ }
+
+
+}
+
+async function getTraceJsonOptions(_tracer: string): Promise