org.ethereum.vm.program.Program Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ethereumj-core Show documentation
Show all versions of ethereumj-core Show documentation
Java implementation of the Ethereum protocol adapted to use for Hedera Smart Contract Service
The newest version!
/*
* Copyright (c) [2016] [ ]
* This file is part of the ethereumJ library.
*
* The ethereumJ library 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.
*
* The ethereumJ library 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 the ethereumJ library. If not, see .
*/
package org.ethereum.vm.program;
import org.apache.commons.lang3.tuple.Pair;
import org.ethereum.config.BlockchainConfig;
import org.ethereum.config.CommonConfig;
import org.ethereum.config.SystemProperties;
import org.ethereum.core.AccountState;
import org.ethereum.core.Repository;
import org.ethereum.core.Transaction;
import org.ethereum.crypto.HashUtil;
import org.ethereum.db.ContractDetails;
import org.ethereum.util.ByteArraySet;
import org.ethereum.util.ByteUtil;
import org.ethereum.util.FastByteComparisons;
import org.ethereum.util.Utils;
import org.ethereum.vm.*;
import org.ethereum.vm.PrecompiledContracts.PrecompiledContract;
import org.ethereum.vm.hook.VMHook;
import org.ethereum.vm.program.invoke.ProgramInvoke;
import org.ethereum.vm.program.invoke.ProgramInvokeFactory;
import org.ethereum.vm.program.invoke.ProgramInvokeFactoryImpl;
import org.ethereum.vm.program.listener.CompositeProgramListener;
import org.ethereum.vm.program.listener.ProgramListenerAware;
import org.ethereum.vm.program.listener.ProgramStorageChangeListener;
import org.ethereum.vm.trace.ProgramTraceListener;
import org.ethereum.vm.trace.ProgramTrace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.util.*;
import static java.lang.StrictMath.min;
import static java.lang.String.format;
import static java.math.BigInteger.ZERO;
import static org.apache.commons.lang3.ArrayUtils.*;
import static org.ethereum.util.BIUtil.*;
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY;
import static org.ethereum.util.ByteUtil.toHexString;
/**
* @author Roman Mandeleil
* @since 01.06.2014
*/
public class Program {
private static final Logger logger = LoggerFactory.getLogger("VM");
private static final int LOG_CONTRACT_ID_SIZE = 24;
private static final int LOG_TOPIC_SIZE = 32;
private static final int LOG_BLOOM_SIZE = 256;
private static final long THIRTY_DAYS_IN_SECONDS = 30 * 24 * 60 * 60;
private static final long TWENTY_FIVE_HOURS_IN_SECONDS = 25 * 60 * 60;
/**
* This attribute defines the number of recursive calls allowed in the EVM
* Note: For the JVM to reach this level without a StackOverflow exception,
* ethereumj may need to be started with a JVM argument to increase
* the stack size. For example: -Xss10m
*/
private static final int MAX_DEPTH = 1024;
//Max size for stack checks
private static final int MAX_STACKSIZE = 1024;
private Transaction transaction;
private ProgramInvoke invoke;
private ProgramInvokeFactory programInvokeFactory = new ProgramInvokeFactoryImpl();
private ProgramOutListener listener;
private ProgramTraceListener traceListener;
private ProgramStorageChangeListener storageDiffListener = new ProgramStorageChangeListener();
private CompositeProgramListener programListener = new CompositeProgramListener();
private Stack stack;
private Memory memory;
private Storage storage;
private Repository originalRepo;
private byte[] returnDataBuffer;
private ProgramResult result = new ProgramResult();
private ProgramTrace trace = new ProgramTrace();
private byte[] codeHash;
private byte[] ops;
private int pc;
private byte lastOp;
private byte previouslyExecutedOp;
private boolean stopped;
private ByteArraySet touchedAccounts = new ByteArraySet();
private long rbh;
private long sbh;
private long defaultContractDuration;
private long logStorageDuration;
private byte[] fundingAddress;
private ByteArraySet endowments = new ByteArraySet();
private Map remainingDurations = new HashMap();
private NewAccountCreateAdapter newAccountCreateAdapter;
public ByteArraySet getEndowments() {
return endowments;
}
private ProgramPrecompile programPrecompile;
CommonConfig commonConfig = CommonConfig.getDefault();
private final SystemProperties config;
private final BlockchainConfig blockchainConfig;
private final VMHook vmHook;
public Program(byte[] ops, ProgramInvoke programInvoke) {
this(ops, programInvoke, (Transaction) null);
}
public Program(byte[] ops, ProgramInvoke programInvoke, SystemProperties config) {
this(ops, programInvoke, null, config, VMHook.EMPTY);
}
public Program(byte[] ops, ProgramInvoke programInvoke, Transaction transaction) {
this(ops, programInvoke, transaction, SystemProperties.getDefault(), VMHook.EMPTY);
}
public Program(byte[] codeHash, byte[] ops, ProgramInvoke programInvoke, Transaction transaction, SystemProperties config, VMHook vmHook) {
this(codeHash, ops, programInvoke, transaction, config, vmHook,
null, ByteUtil.EMPTY_BYTE_ARRAY, 1L, 1L, THIRTY_DAYS_IN_SECONDS, TWENTY_FIVE_HOURS_IN_SECONDS);
}
public Program(byte[] ops, ProgramInvoke programInvoke, Transaction transaction, SystemProperties config, VMHook vmHook) {
this(null, ops, programInvoke, transaction, config, vmHook,
null, ByteUtil.EMPTY_BYTE_ARRAY, 1L, 1L, THIRTY_DAYS_IN_SECONDS, TWENTY_FIVE_HOURS_IN_SECONDS);
}
public Program(
byte[] ops,
ProgramInvoke programInvoke,
Transaction transaction,
SystemProperties config,
VMHook vmHook,
NewAccountCreateAdapter createAdapter,
byte[] fundingAddress,
long rbh,
long sbh,
long defaultContractDuration,
long logStorageDuration
) {
this(null, ops, programInvoke, transaction, config, vmHook,
createAdapter, fundingAddress, rbh, sbh, defaultContractDuration, logStorageDuration);
}
public Program(
byte[] codeHash,
byte[] ops,
ProgramInvoke programInvoke,
Transaction transaction,
SystemProperties config,
VMHook vmHook,
NewAccountCreateAdapter createAdapter,
byte[] fundingAddress,
long rbh,
long sbh,
long defaultContractDuration,
long logStorageDuration
) {
this.config = config;
this.invoke = programInvoke;
this.transaction = transaction;
this.codeHash = codeHash == null || FastByteComparisons.equal(HashUtil.EMPTY_DATA_HASH, codeHash) ? null : codeHash;
this.ops = nullToEmpty(ops);
this.vmHook = vmHook;
this.traceListener = new ProgramTraceListener(config.vmTrace());
this.memory = setupProgramListener(new Memory());
this.stack = setupProgramListener(new Stack());
this.originalRepo = programInvoke.getOrigRepository();
this.storage = setupProgramListener(new Storage(programInvoke));
this.trace = new ProgramTrace(config, programInvoke);
this.blockchainConfig = config.getBlockchainConfig().getConfigForBlock(programInvoke.getNumber().longValue());
this.newAccountCreateAdapter = createAdapter;
this.fundingAddress = fundingAddress;
this.rbh = rbh;
this.sbh = sbh;
this.defaultContractDuration = defaultContractDuration;
this.logStorageDuration = logStorageDuration;
}
public ProgramPrecompile getProgramPrecompile() {
if (programPrecompile == null) {
if (codeHash != null && commonConfig.precompileSource() != null) {
programPrecompile = commonConfig.precompileSource().get(codeHash);
}
if (programPrecompile == null) {
programPrecompile = ProgramPrecompile.compile(ops);
if (codeHash != null && commonConfig.precompileSource() != null) {
commonConfig.precompileSource().put(codeHash, programPrecompile);
}
}
}
return programPrecompile;
}
public Program withCommonConfig(CommonConfig commonConfig) {
this.commonConfig = commonConfig;
return this;
}
public int getCallDeep() {
return invoke.getCallDeep();
}
private InternalTransaction addInternalTx(byte[] nonce, DataWord gasLimit, byte[] senderAddress, byte[] receiveAddress,
BigInteger value, byte[] data, String note) {
InternalTransaction result = null;
if (transaction != null) {
byte[] senderNonce = isEmpty(nonce) ? getStorage().getNonce(senderAddress).toByteArray() : nonce;
data = config.recordInternalTransactionsData() ? data : null;
result = getResult().addInternalTransaction(transaction.getHash(), getCallDeep(), senderNonce,
getGasPrice(), gasLimit, senderAddress, receiveAddress, value.toByteArray(), data, note);
}
return result;
}
private T setupProgramListener(T programListenerAware) {
if (programListener.isEmpty()) {
programListener.addListener(traceListener);
programListener.addListener(storageDiffListener);
}
programListenerAware.setProgramListener(programListener);
return programListenerAware;
}
public Map getStorageDiff() {
return storageDiffListener.getDiff();
}
public byte getOp(int pc) {
return (getLength(ops) <= pc) ? 0 : ops[pc];
}
public byte getCurrentOp() {
return isEmpty(ops) ? 0 : ops[pc];
}
/**
* Last Op can only be set publicly (no getLastOp method), is used for logging.
*/
public void setLastOp(byte op) {
this.lastOp = op;
}
/**
* Should be set only after the OP is fully executed.
*/
public void setPreviouslyExecutedOp(byte op) {
this.previouslyExecutedOp = op;
}
/**
* Returns the last fully executed OP.
*/
public byte getPreviouslyExecutedOp() {
return this.previouslyExecutedOp;
}
public void stackPush(byte[] data) {
stackPush(DataWord.of(data));
}
public void stackPushZero() {
stackPush(DataWord.ZERO);
}
public void stackPushOne() {
DataWord stackWord = DataWord.ONE;
stackPush(stackWord);
}
public void stackPush(DataWord stackWord) {
verifyStackOverflow(0, 1); //Sanity Check
stack.push(stackWord);
}
public Stack getStack() {
return this.stack;
}
public int getPC() {
return pc;
}
public void setPC(DataWord pc) {
this.setPC(pc.intValue());
}
public void setPC(int pc) {
this.pc = pc;
if (this.pc >= ops.length) {
stop();
}
}
public boolean isStopped() {
return stopped;
}
public void stop() {
stopped = true;
}
public void setHReturn(byte[] buff) {
getResult().setHReturn(buff);
}
public void step() {
setPC(pc + 1);
}
public byte[] sweep(int n) {
if (pc + n > ops.length)
stop();
byte[] data = Arrays.copyOfRange(ops, pc, pc + n);
pc += n;
if (pc >= ops.length) stop();
return data;
}
public DataWord stackPop() {
return stack.pop();
}
/**
* Verifies that the stack is at least stackSize
*
* @param stackSize int
* @throws StackTooSmallException If the stack is
* smaller than stackSize
*/
public void verifyStackSize(int stackSize) {
if (stack.size() < stackSize) {
throw Program.Exception.tooSmallStack(stackSize, stack.size());
}
}
public void verifyStackOverflow(int argsReqs, int returnReqs) {
if ((stack.size() - argsReqs + returnReqs) > MAX_STACKSIZE) {
throw new StackTooLargeException("Expected: overflow " + MAX_STACKSIZE + " elements stack limit");
}
}
public int getMemSize() {
return memory.size();
}
public void memorySave(DataWord addrB, DataWord value) {
memory.write(addrB.intValue(), value.getData(), value.getData().length, false);
}
public void memorySaveLimited(int addr, byte[] data, int dataSize) {
memory.write(addr, data, dataSize, true);
}
public void memorySave(int addr, byte[] value) {
memory.write(addr, value, value.length, false);
}
public void memoryExpand(DataWord outDataOffs, DataWord outDataSize) {
if (!outDataSize.isZero()) {
memory.extend(outDataOffs.intValue(), outDataSize.intValue());
}
}
/**
* Allocates a piece of memory and stores value at given offset address
*
* @param addr is the offset address
* @param allocSize size of memory needed to write
* @param value the data to write to memory
*/
public void memorySave(int addr, int allocSize, byte[] value) {
memory.extendAndWrite(addr, allocSize, value);
}
public DataWord memoryLoad(DataWord addr) {
return memory.readWord(addr.intValue());
}
public DataWord memoryLoad(int address) {
return memory.readWord(address);
}
public byte[] memoryChunk(int offset, int size) {
return memory.read(offset, size);
}
/**
* Allocates extra memory in the program for
* a specified size, calculated from a given offset
*
* @param offset the memory address offset
* @param size the number of bytes to allocate
*/
public void allocateMemory(int offset, int size) {
memory.extend(offset, size);
}
public void suicide(DataWord obtainerAddress) {
byte[] owner = getOwnerAddress().getLast20Bytes();
byte[] obtainer = obtainerAddress.getLast20Bytes();
BigInteger balance = getStorage().getBalance(owner);
if (logger.isInfoEnabled())
logger.info("Transfer to: [{}] heritage: [{}]",
toHexString(obtainer),
balance);
addInternalTx(null, null, owner, obtainer, balance, null, "suicide");
if (FastByteComparisons.compareTo(owner, 0, 20, obtainer, 0, 20) == 0) {
throw new SameOwnerObtainerOnSelfSestructException(obtainer);
} else {
if (getStorage().isExist(obtainer) && !getStorage().getAccountState(obtainer).isDeleted()) {
transfer(getStorage(), owner, obtainer, balance);
} else {
throw new InvalidAccountAddressException(obtainer);
}
}
getResult().addDeleteAccount(this.getOwnerAddress());
}
public Repository getStorage() {
return this.storage;
}
/**
* Create contract for {@link OpCode#CREATE}
* @param value Endowment
* @param memStart Code memory offset
* @param memSize Code memory size
*/
public void createContract(DataWord value, DataWord memStart, DataWord memSize) {
returnDataBuffer = null; // reset return buffer right before the call
byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
BigInteger endowment = value.value();
if (!verifyCall(senderAddress, endowment))
return;
byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue());
createContractImpl(value, programCode, ByteUtil.EMPTY_BYTE_ARRAY);
}
/**
* Create contract for {@link OpCode#CREATE2}
* @param value Endowment
* @param memStart Code memory offset
* @param memSize Code memory size
* @param salt Salt, used in contract address calculation
*/
public void createContract2(DataWord value, DataWord memStart, DataWord memSize, DataWord salt) {
returnDataBuffer = null; // reset return buffer right before the call
byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
BigInteger endowment = value.value();
if (!verifyCall(senderAddress, endowment))
return;
byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue());
createContractImpl(value, programCode, ByteUtil.EMPTY_BYTE_ARRAY);
}
/**
* Verifies CREATE attempt
*/
private boolean verifyCall(byte[] senderAddress, BigInteger endowment) {
if (getCallDeep() == MAX_DEPTH) {
stackPushZero();
return false;
}
if (isNotCovers(getStorage().getBalance(senderAddress), endowment)) {
stackPushZero();
return false;
}
return true;
}
/**
* All stages required to create contract on provided address after initial check
* @param value Endowment
* @param programCode Contract code
* @param newAddress Contract address
*/
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
private void createContractImpl(DataWord value, byte[] programCode, byte[] newAddress) {
// [1] LOG, SPEND GAS
byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
if (logger.isInfoEnabled())
logger.info("creating a new contract inside contract run: [{}]", toHexString(senderAddress));
BlockchainConfig blockchainConfig = config.getBlockchainConfig().getConfigForBlock(getNumber().longValue());
// actual gas subtract
DataWord gasLimit = blockchainConfig.getCreateGas(getGas());
spendGas(gasLimit.longValue(), "internal call");
// [2] CREATE THE CONTRACT ADDRESS
if (newAccountCreateAdapter == null) {
throw new UnableToGenerateContractIdException();
}
newAddress = newAccountCreateAdapter.calculateNewAddress(getOwnerAddress().getLast20Bytes(), getStorage());
AccountState existingAddr = getStorage().getAccountState(newAddress);
boolean contractAlreadyExists = existingAddr != null && existingAddr.isContractExist(blockchainConfig);
if (byTestingSuite()) {
// This keeps track of the contracts created for a test
getResult().addCallCreate(programCode, EMPTY_BYTE_ARRAY,
gasLimit.getNoLeadZeroesData(),
value.getNoLeadZeroesData());
}
// [3] UPDATE THE NONCE
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
if (!byTestingSuite()) {
getStorage().increaseNonce(senderAddress);
}
Repository track = getStorage().startTracking();
//In case of hashing collisions, check for any balance before createAccount()
BigInteger oldBalance = track.getBalance(newAddress);
AccountState newAccount = track.createAccount(newAddress);
newAccount.setSmartContract(true);
DataWord creationTimestamp = getTimestamp();
long creationTimestampSecs = ByteUtil.byteArrayToLong(creationTimestamp.getData());
long expirationTime = creationTimestampSecs + defaultContractDuration;
newAccount.setExpirationTime(expirationTime);
newAccountCreateAdapter.addCreatedContract(newAddress, getOwnerAddress().getLast20Bytes(), track);
if (blockchainConfig.eip161()) {
track.increaseNonce(newAddress);
}
track.addBalance(newAddress, oldBalance);
// [4] TRANSFER THE BALANCE
BigInteger endowment = value.value();
BigInteger newBalance = ZERO;
if (!byTestingSuite()) {
track.addBalance(senderAddress, endowment.negate());
newBalance = track.addBalance(newAddress, endowment);
}
// [5] COOK THE INVOKE AND EXECUTE
byte[] nonce = getStorage().getNonce(senderAddress).toByteArray();
InternalTransaction internalTx = addInternalTx(nonce, getGasLimit(), senderAddress, null, endowment, programCode, "create");
Repository originalRepo = this.invoke.getOrigRepository();
// Some TCK tests have storage only addresses (no code, zero nonce etc) - impossible situation in the real network
// So, we should clean up it before reuse, but as tx not always goes successful, state should be correctly
// reverted in that case too
/*
if (!contractAlreadyExists && track.hasContractDetails(newAddress)) {
originalRepo = originalRepo.clone();
originalRepo.delete(newAddress);
}
*/
ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(
this, DataWord.of(newAddress), getOwnerAddress(), value, gasLimit,
newBalance, null, track, originalRepo, this.invoke.getBlockStore(), false, byTestingSuite());
ProgramResult result = ProgramResult.createEmpty();
if (contractAlreadyExists) {
result.setException(new BytecodeExecutionException("Trying to create a contract with existing contract address: 0x" + toHexString(newAddress)));
} else if (isNotEmpty(programCode)) {
VM vm = new VM(config, vmHook);
Program program = new Program(
programCode, programInvoke, internalTx, config, vmHook,
newAccountCreateAdapter, fundingAddress, rbh, sbh, defaultContractDuration, logStorageDuration).withCommonConfig(commonConfig);
// reset storage if the contract with the same address already exists
// TCK test case only - normally this is near-impossible situation in the real network
ContractDetails contractDetails = program.getStorage().getContractDetails(newAddress);
contractDetails.deleteStorage();
vm.play(program);
result = program.getResult();
}
// 4. CREATE THE CONTRACT OUT OF RETURN
if (!result.isRevert() && result.getException() == null) {
byte[] code = result.getHReturn();
long durationSecs = getOwnerRemainingDuration();
int codeBytes = getLength(code);
long gasPrice = ByteUtil.byteArrayToLong(getGasPrice().getData());
long storageCost = calculateStorageGasNeeded(codeBytes, durationSecs, sbh, gasPrice);
long afterSpend = programInvoke.getGas().longValue() - result.getGasUsed() - storageCost;
if (afterSpend < 0) {
if (!blockchainConfig.getConstants().createEmptyContractOnOOG()) {
result.setException(Program.Exception.notEnoughSpendingGas("No gas to return just created contract", storageCost, this));
} else {
track.saveCode(newAddress, EMPTY_BYTE_ARRAY);
}
} else if (getLength(code) > blockchainConfig.getConstants().getMAX_CONTRACT_SZIE()) {
result.setException(
Program.Exception.notEnoughSpendingGas("Contract size too large: " + getLength(result.getHReturn()), storageCost, this));
} else {
result.spendGas(storageCost);
track.saveCode(newAddress, code);
}
}
getResult().merge(result);
if (result.getException() != null || result.isRevert()) {
logger.debug("contract run halted by Exception: contract: [{}], exception: [{}]",
toHexString(newAddress),
result.getException());
internalTx.reject();
result.rejectInternalTransactions();
track.rollback();
stackPushZero();
if (result.getException() != null) {
return;
} else {
returnDataBuffer = result.getHReturn();
}
} else {
if (!byTestingSuite())
track.commit();
// IN SUCCESS PUSH THE ADDRESS INTO THE STACK
stackPush(DataWord.of(newAddress));
}
// 5. REFUND THE REMAIN GAS
long refundGas = gasLimit.longValue() - result.getGasUsed();
if (refundGas > 0) {
refundGas(refundGas, "remain gas from the internal call");
if (logger.isInfoEnabled()) {
logger.info("The remaining gas is refunded, account: [{}], gas: [{}] ",
toHexString(getOwnerAddress().getLast20Bytes()), refundGas);
}
}
touchedAccounts.add(newAddress);
}
/**
* That method is for internal code invocations
*
* - Normal calls invoke a specified contract which updates itself
* - Stateless calls invoke code from another contract, within the context of the caller
*
* @param msg is the message call object
*/
public void callToAddress(MessageCall msg) {
returnDataBuffer = null; // reset return buffer right before the call
if (getCallDeep() == MAX_DEPTH) {
stackPushZero();
refundGas(msg.getGas().longValue(), " call deep limit reach");
return;
}
byte[] data = memoryChunk(msg.getInDataOffs().intValue(), msg.getInDataSize().intValue());
// FETCH THE SAVED STORAGE
byte[] codeAddress = msg.getCodeAddress().getLast20Bytes();
byte[] senderAddress = getOwnerAddress().getLast20Bytes();
byte[] contextAddress = msg.getType().callIsStateless() ? senderAddress : codeAddress;
if (logger.isInfoEnabled())
logger.info(msg.getType().name() + " for existing contract: address: [{}], outDataOffs: [{}], outDataSize: [{}] ",
toHexString(contextAddress), msg.getOutDataOffs().longValue(), msg.getOutDataSize().longValue());
if (!getStorage().isExist(codeAddress) || getStorage().getAccountState(codeAddress).isDeleted()) {
throw new InvalidAccountAddressException(codeAddress);
}
Repository track = getStorage().startTracking();
// 2.1 PERFORM THE VALUE (endowment) PART
BigInteger endowment = msg.getEndowment().value();
BigInteger senderBalance = track.getBalance(senderAddress);
if (isNotCovers(senderBalance, endowment)) {
stackPushZero();
refundGas(msg.getGas().longValue(), "refund gas from message call");
return;
}
// FETCH THE CODE
byte[] programCode = getStorage().isExist(codeAddress) ? getStorage().getCode(codeAddress) : EMPTY_BYTE_ARRAY;
if (!isEmpty(data) && isEmpty(programCode)) {
throw new EmptyByteCodeException(codeAddress);
}
BigInteger contextBalance = ZERO;
if (byTestingSuite()) {
// This keeps track of the calls created for a test
getResult().addCallCreate(data, contextAddress,
msg.getGas().getNoLeadZeroesData(),
msg.getEndowment().getNoLeadZeroesData());
} else {
track.addBalance(senderAddress, endowment.negate());
contextBalance = track.addBalance(contextAddress, endowment);
endowments.add(contextAddress);
}
// CREATE CALL INTERNAL TRANSACTION
InternalTransaction internalTx = addInternalTx(null, getGasLimit(), senderAddress, contextAddress, endowment, data, "call");
ProgramResult result = null;
if (isNotEmpty(programCode)) {
ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(
this, DataWord.of(contextAddress),
msg.getType().callIsDelegate() ? getCallerAddress() : getOwnerAddress(),
msg.getType().callIsDelegate() ? getCallValue() : msg.getEndowment(),
msg.getGas(), contextBalance, data, track, this.invoke.getOrigRepository(), this.invoke.getBlockStore(),
msg.getType().callIsStatic() || isStaticCall(), byTestingSuite());
VM vm = new VM(config, vmHook);
Program program = new Program(getStorage().getCodeHash(codeAddress), programCode, programInvoke, internalTx, config, vmHook,
newAccountCreateAdapter, fundingAddress, rbh, sbh, defaultContractDuration, logStorageDuration).withCommonConfig(commonConfig);
vm.play(program);
result = program.getResult();
getTrace().merge(program.getTrace());
getResult().merge(result);
if (result.getException() != null || result.isRevert()) {
logger.debug("contract run halted by Exception: contract: [{}], exception: [{}]",
toHexString(contextAddress),
result.getException());
internalTx.reject();
result.rejectInternalTransactions();
track.rollback();
stackPushZero();
if (result.getException() != null) {
return;
}
} else {
// 4. THE FLAG OF SUCCESS IS ONE PUSHED INTO THE STACK
track.commit();
stackPushOne();
}
if (byTestingSuite()) {
logger.info("Testing run, skipping storage diff listener");
} else if (Arrays.equals(transaction.getReceiveAddress(), internalTx.getReceiveAddress())) {
storageDiffListener.merge(program.getStorageDiff());
}
} else {
// 4. THE FLAG OF SUCCESS IS ONE PUSHED INTO THE STACK
track.commit();
stackPushOne();
}
// 3. APPLY RESULTS: result.getHReturn() into out_memory allocated
if (result != null) {
byte[] buffer = result.getHReturn();
int offset = msg.getOutDataOffs().intValue();
int size = msg.getOutDataSize().intValue();
memorySaveLimited(offset, buffer, size);
returnDataBuffer = buffer;
}
// 5. REFUND THE REMAIN GAS
if (result != null) {
BigInteger refundGas = msg.getGas().value().subtract(toBI(result.getGasUsed()));
if (isPositive(refundGas)) {
refundGas(refundGas.longValue(), "remaining gas from the internal call");
if (logger.isInfoEnabled())
logger.info("The remaining gas refunded, account: [{}], gas: [{}] ",
toHexString(senderAddress),
refundGas.toString());
}
} else {
refundGas(msg.getGas().longValue(), "remaining gas from the internal call");
}
}
public void spendGas(long gasValue, String cause) {
if (logger.isDebugEnabled()) {
logger.debug("[{}] Spent for cause: [{}], gas: [{}]", invoke.hashCode(), cause, gasValue);
}
if (getGasLong() < gasValue) {
throw Program.Exception.notEnoughSpendingGas(cause, gasValue, this);
}
getResult().spendGas(gasValue);
}
public void spendAllGas() {
spendGas(getGas().longValue(), "Spending all remaining");
}
public void refundGas(long gasValue, String cause) {
logger.info("[{}] Refund for cause: [{}], gas: [{}]", invoke.hashCode(), cause, gasValue);
getResult().refundGas(gasValue);
}
public void futureRefundGas(long gasValue) {
logger.info("Future refund added: [{}]", gasValue);
getResult().addFutureRefund(gasValue);
}
public void resetFutureRefund() {
getResult().resetFutureRefund();
}
public void storageSave(DataWord word1, DataWord word2) {
storageSave(word1.getData(), word2.getData());
}
public void storageSave(byte[] key, byte[] val) {
DataWord keyWord = DataWord.of(key);
DataWord valWord = DataWord.of(val);
getStorage().addStorageRow(getOwnerAddress().getLast20Bytes(), keyWord, valWord);
}
public byte[] getCode() {
return ops;
}
public byte[] getCodeAt(DataWord address) {
byte[] code = invoke.getRepository().getCode(address.getLast20Bytes());
return nullToEmpty(code);
}
public byte[] getCodeHashAt(DataWord address) {
AccountState state = invoke.getRepository().getAccountState(address.getLast20Bytes());
if (state == null || !state.isSmartContract()) {
return HashUtil.EMPTY_DATA_HASH;
} else {
byte[] code = invoke.getRepository().getCodeHash(address.getLast20Bytes());
return nullToEmpty(code);
}
}
public DataWord getOwnerAddress() {
return invoke.getOwnerAddress();
}
public DataWord getBlockHash(int index) {
return DataWord.ZERO;
}
public DataWord getBalance(DataWord address) {
BigInteger balance = getStorage().getBalance(address.getLast20Bytes());
return DataWord.of(balance.toByteArray());
}
public DataWord getOriginAddress() {
return invoke.getOriginAddress();
}
public DataWord getCallerAddress() {
return invoke.getCallerAddress();
}
public DataWord getGasPrice() {
return invoke.getMinGasPrice();
}
public long getGasLong() {
return invoke.getGasLong() - getResult().getGasUsed();
}
public DataWord getGas() {
return DataWord.of(invoke.getGasLong() - getResult().getGasUsed());
}
public DataWord getCallValue() {
return invoke.getCallValue();
}
public DataWord getDataSize() {
return invoke.getDataSize();
}
public DataWord getDataValue(DataWord index) {
return invoke.getDataValue(index);
}
public byte[] getDataCopy(DataWord offset, DataWord length) {
return invoke.getDataCopy(offset, length);
}
public DataWord getReturnDataBufferSize() {
return DataWord.of(getReturnDataBufferSizeI());
}
private int getReturnDataBufferSizeI() {
return returnDataBuffer == null ? 0 : returnDataBuffer.length;
}
public byte[] getReturnDataBufferData(DataWord off, DataWord size) {
if ((long) off.intValueSafe() + size.intValueSafe() > getReturnDataBufferSizeI()) return null;
return returnDataBuffer == null ? new byte[0] :
Arrays.copyOfRange(returnDataBuffer, off.intValueSafe(), off.intValueSafe() + size.intValueSafe());
}
public DataWord storageLoad(DataWord key) {
return getStorage().getStorageValue(getOwnerAddress().getLast20Bytes(), key);
}
/**
* @return current Storage data for key
*/
public DataWord getCurrentValue(DataWord key) {
return getStorage().getStorageValue(getOwnerAddress().getLast20Bytes(), key);
}
/*
* Original storage value at the beginning of current frame execution
* For more info check EIP-1283 https://eips.ethereum.org/EIPS/eip-1283
* @return Storage data at the beginning of Program execution
*/
public DataWord getOriginalValue(DataWord key) {
return originalRepo.getStorageValue(getOwnerAddress().getLast20Bytes(), key);
}
public DataWord getPrevHash() {
return invoke.getPrevHash();
}
public DataWord getCoinbase() {
return invoke.getCoinbase();
}
public DataWord getTimestamp() {
return invoke.getTimestamp();
}
public DataWord getNumber() {
return invoke.getNumber();
}
public BlockchainConfig getBlockchainConfig() {
return blockchainConfig;
}
public DataWord getDifficulty() {
return invoke.getDifficulty();
}
public DataWord getGasLimit() {
return invoke.getGaslimit();
}
public boolean isStaticCall() {
return invoke.isStaticCall();
}
public ProgramResult getResult() {
return result;
}
public void setRuntimeFailure(RuntimeException e) {
getResult().setException(e);
}
public String memoryToString() {
return memory.toString();
}
public void fullTrace() {
if (logger.isTraceEnabled() || listener != null) {
StringBuilder stackData = new StringBuilder();
for (int i = 0; i < stack.size(); ++i) {
stackData.append(" ").append(stack.get(i));
if (i < stack.size() - 1) stackData.append("\n");
}
if (stackData.length() > 0) stackData.insert(0, "\n");
ContractDetails contractDetails = getStorage().
getContractDetails(getOwnerAddress().getLast20Bytes());
StringBuilder storageData = new StringBuilder("");
StringBuilder memoryData = new StringBuilder();
StringBuilder oneLine = new StringBuilder();
if (memory.size() > 320)
memoryData.append("... Memory Folded.... ")
.append("(")
.append(memory.size())
.append(") bytes");
else
for (int i = 0; i < memory.size(); ++i) {
byte value = memory.readByte(i);
oneLine.append(ByteUtil.oneByteToHexString(value)).append(" ");
if ((i + 1) % 16 == 0) {
String tmp = format("[%4s]-[%4s]", Integer.toString(i - 15, 16),
Integer.toString(i, 16)).replace(" ", "0");
memoryData.append("").append(tmp).append(" ");
memoryData.append(oneLine);
if (i < memory.size()) memoryData.append("\n");
oneLine.setLength(0);
}
}
if (memoryData.length() > 0) memoryData.insert(0, "\n");
StringBuilder opsString = new StringBuilder();
for (int i = 0; i < ops.length; ++i) {
String tmpString = Integer.toString(ops[i] & 0xFF, 16);
tmpString = tmpString.length() == 1 ? "0" + tmpString : tmpString;
if (i != pc)
opsString.append(tmpString);
else
opsString.append(" >>").append(tmpString).append("");
}
if (pc >= ops.length) opsString.append(" >>");
if (opsString.length() > 0) opsString.insert(0, "\n ");
logger.trace(" -- OPS -- {}", opsString);
logger.trace(" -- STACK -- {}", stackData);
logger.trace(" -- MEMORY -- {}", memoryData);
logger.trace(" -- STORAGE -- {}\n", storageData);
logger.trace("\n Spent Gas: [{}]/[{}]\n Left Gas: [{}]\n",
getResult().getGasUsed(),
invoke.getGas().longValue(),
getGas().longValue());
StringBuilder globalOutput = new StringBuilder("\n");
if (stackData.length() > 0) stackData.append("\n");
if (pc != 0)
globalOutput.append("[Op: ").append(OpCode.code(lastOp).name()).append("]\n");
globalOutput.append(" -- OPS -- ").append(opsString).append("\n");
globalOutput.append(" -- STACK -- ").append(stackData).append("\n");
globalOutput.append(" -- MEMORY -- ").append(memoryData).append("\n");
globalOutput.append(" -- STORAGE -- ").append(storageData).append("\n");
if (getResult().getHReturn() != null)
globalOutput.append("\n HReturn: ").append(
Hex.toHexString(getResult().getHReturn()));
// sophisticated assumption that msg.data != codedata
// means we are calling the contract not creating it
byte[] txData = invoke.getDataCopy(DataWord.ZERO, getDataSize());
if (!Arrays.equals(txData, ops))
globalOutput.append("\n msg.data: ").append(Hex.toHexString(txData));
globalOutput.append("\n\n Spent Gas: ").append(getResult().getGasUsed());
if (listener != null)
listener.output(globalOutput.toString());
}
}
public void saveOpTrace() {
if (this.pc < ops.length) {
trace.addOp(ops[pc], pc, getCallDeep(), getGas(), traceListener.resetActions());
}
}
public ProgramTrace getTrace() {
return trace;
}
static String formatBinData(byte[] binData, int startPC) {
StringBuilder ret = new StringBuilder();
for (int i = 0; i < binData.length; i += 16) {
ret.append(Utils.align("" + Integer.toHexString(startPC + (i)) + ":", ' ', 8, false));
ret.append(Hex.toHexString(binData, i, min(16, binData.length - i))).append('\n');
}
return ret.toString();
}
public static String stringifyMultiline(byte[] code) {
int index = 0;
StringBuilder sb = new StringBuilder();
BitSet mask = buildReachableBytecodesMask(code);
ByteArrayOutputStream binData = new ByteArrayOutputStream();
int binDataStartPC = -1;
while (index < code.length) {
final byte opCode = code[index];
OpCode op = OpCode.code(opCode);
if (!mask.get(index)) {
if (binDataStartPC == -1) {
binDataStartPC = index;
}
binData.write(code[index]);
index++;
if (index < code.length) continue;
}
if (binDataStartPC != -1) {
sb.append(formatBinData(binData.toByteArray(), binDataStartPC));
binDataStartPC = -1;
binData = new ByteArrayOutputStream();
if (index == code.length) continue;
}
sb.append(Utils.align("" + Integer.toHexString(index) + ":", ' ', 8, false));
if (op == null) {
sb.append(": ").append(0xFF & opCode).append("\n");
index++;
continue;
}
if (op.name().startsWith("PUSH")) {
sb.append(' ').append(op.name()).append(' ');
int nPush = op.val() - OpCode.PUSH1.val() + 1;
byte[] data = Arrays.copyOfRange(code, index + 1, index + nPush + 1);
BigInteger bi = new BigInteger(1, data);
sb.append("0x").append(bi.toString(16));
if (bi.bitLength() <= 32) {
sb.append(" (").append(new BigInteger(1, data).toString()).append(") ");
}
index += nPush + 1;
} else {
sb.append(' ').append(op.name());
index++;
}
sb.append('\n');
}
return sb.toString();
}
static class ByteCodeIterator {
byte[] code;
int pc;
public ByteCodeIterator(byte[] code) {
this.code = code;
}
public void setPC(int pc) {
this.pc = pc;
}
public int getPC() {
return pc;
}
public OpCode getCurOpcode() {
return pc < code.length ? OpCode.code(code[pc]) : null;
}
public boolean isPush() {
return getCurOpcode() != null ? getCurOpcode().name().startsWith("PUSH") : false;
}
public byte[] getCurOpcodeArg() {
if (isPush()) {
int nPush = getCurOpcode().val() - OpCode.PUSH1.val() + 1;
byte[] data = Arrays.copyOfRange(code, pc + 1, pc + nPush + 1);
return data;
} else {
return new byte[0];
}
}
public boolean next() {
pc += 1 + getCurOpcodeArg().length;
return pc < code.length;
}
}
static BitSet buildReachableBytecodesMask(byte[] code) {
NavigableSet gotos = new TreeSet<>();
ByteCodeIterator it = new ByteCodeIterator(code);
BitSet ret = new BitSet(code.length);
int lastPush = 0;
int lastPushPC = 0;
do {
ret.set(it.getPC()); // reachable bytecode
if (it.isPush()) {
lastPush = new BigInteger(1, it.getCurOpcodeArg()).intValue();
lastPushPC = it.getPC();
}
if (it.getCurOpcode() == OpCode.JUMP || it.getCurOpcode() == OpCode.JUMPI) {
if (it.getPC() != lastPushPC + 1) {
// some PC arithmetic we totally can't deal with
// assuming all bytecodes are reachable as a fallback
ret.set(0, code.length);
return ret;
}
int jumpPC = lastPush;
if (!ret.get(jumpPC)) {
// code was not explored yet
gotos.add(jumpPC);
}
}
if (it.getCurOpcode() == OpCode.JUMP || it.getCurOpcode() == OpCode.RETURN ||
it.getCurOpcode() == OpCode.STOP) {
if (gotos.isEmpty()) break;
it.setPC(gotos.pollFirst());
}
} while (it.next());
return ret;
}
public static String stringify(byte[] code) {
int index = 0;
StringBuilder sb = new StringBuilder();
BitSet mask = buildReachableBytecodesMask(code);
String binData = "";
while (index < code.length) {
final byte opCode = code[index];
OpCode op = OpCode.code(opCode);
if (op == null) {
sb.append(" : ").append(0xFF & opCode).append(" ");
index++;
continue;
}
if (op.name().startsWith("PUSH")) {
sb.append(' ').append(op.name()).append(' ');
int nPush = op.val() - OpCode.PUSH1.val() + 1;
byte[] data = Arrays.copyOfRange(code, index + 1, index + nPush + 1);
BigInteger bi = new BigInteger(1, data);
sb.append("0x").append(bi.toString(16)).append(" ");
index += nPush + 1;
} else {
sb.append(' ').append(op.name());
index++;
}
}
return sb.toString();
}
public void addListener(ProgramOutListener listener) {
this.listener = listener;
}
public int verifyJumpDest(DataWord nextPC) {
if (nextPC.bytesOccupied() > 4) {
throw Program.Exception.badJumpDestination(-1);
}
int ret = nextPC.intValue();
if (!getProgramPrecompile().hasJumpDest(ret)) {
throw Program.Exception.badJumpDestination(ret);
}
return ret;
}
public void callToPrecompiledAddress(MessageCall msg, PrecompiledContract contract) {
returnDataBuffer = null; // reset return buffer right before the call
if (getCallDeep() == MAX_DEPTH) {
stackPushZero();
this.refundGas(msg.getGas().longValue(), " call deep limit reach");
return;
}
Repository track = getStorage().startTracking();
byte[] senderAddress = this.getOwnerAddress().getLast20Bytes();
byte[] codeAddress = msg.getCodeAddress().getLast20Bytes();
byte[] contextAddress = msg.getType().callIsStateless() ? senderAddress : codeAddress;
BigInteger endowment = msg.getEndowment().value();
BigInteger senderBalance = track.getBalance(senderAddress);
if (senderBalance.compareTo(endowment) < 0) {
stackPushZero();
this.refundGas(msg.getGas().longValue(), "refund gas from message call");
return;
}
byte[] data = this.memoryChunk(msg.getInDataOffs().intValue(),
msg.getInDataSize().intValue());
// Charge for endowment - is not reversible by rollback
transfer(track, senderAddress, contextAddress, msg.getEndowment().value());
if (byTestingSuite()) {
// This keeps track of the calls created for a test
this.getResult().addCallCreate(data,
msg.getCodeAddress().getLast20Bytes(),
msg.getGas().getNoLeadZeroesData(),
msg.getEndowment().getNoLeadZeroesData());
stackPushOne();
return;
}
long requiredGas = contract.getGasForData(data);
if (requiredGas > msg.getGas().longValue()) {
this.refundGas(0, "call pre-compiled"); //matches cpp logic
this.stackPushZero();
track.rollback();
} else {
if (logger.isDebugEnabled())
logger.debug("Call {}(data = {})", contract.getClass().getSimpleName(), toHexString(data));
Pair out = contract.execute(data);
if (out.getLeft()) { // success
this.refundGas(msg.getGas().longValue() - requiredGas, "call pre-compiled");
this.stackPushOne();
returnDataBuffer = out.getRight();
track.commit();
} else {
// spend all gas on failure, push zero and revert state changes
this.refundGas(0, "call pre-compiled");
this.stackPushZero();
track.rollback();
}
this.memorySave(msg.getOutDataOffs().intValue(), msg.getOutDataSize().intValueSafe(), out.getRight());
}
}
public boolean byTestingSuite() {
return invoke.byTestingSuite();
}
public interface ProgramOutListener {
void output(String out);
}
/**
* Denotes problem when executing Ethereum bytecode.
* From blockchain and peer perspective this is quite normal situation
* and doesn't mean exceptional situation in terms of the program execution
*/
@SuppressWarnings("serial")
public static class BytecodeExecutionException extends RuntimeException {
public BytecodeExecutionException(String message) {
super(message);
}
}
@SuppressWarnings("serial")
public static class OutOfGasException extends BytecodeExecutionException {
public OutOfGasException(String message, Object... args) {
super(format(message, args));
}
}
@SuppressWarnings("serial")
public static class IllegalOperationException extends BytecodeExecutionException {
public IllegalOperationException(String message, Object... args) {
super(format(message, args));
}
}
@SuppressWarnings("serial")
public static class BadJumpDestinationException extends BytecodeExecutionException {
public BadJumpDestinationException(String message, Object... args) {
super(format(message, args));
}
}
@SuppressWarnings("serial")
public static class StackTooSmallException extends BytecodeExecutionException {
public StackTooSmallException(String message, Object... args) {
super(format(message, args));
}
}
@SuppressWarnings("serial")
public static class SameOwnerObtainerOnSelfSestructException extends BytecodeExecutionException {
public SameOwnerObtainerOnSelfSestructException(byte[] address) {
super(String.format("Address (%s) not a valid balance transfer target for selfdestruct", ByteUtil.toHexString(address)));
}
}
@SuppressWarnings("serial")
public static class EmptyByteCodeException extends BytecodeExecutionException {
public EmptyByteCodeException(byte[] accountAddress) {
super(String.format("Bytecode for contract adress (%s) is empty", ByteUtil.toHexString(accountAddress)));
}
}
@SuppressWarnings("serial")
public static class UnableToGenerateContractIdException extends BytecodeExecutionException {
public UnableToGenerateContractIdException() {
super("Failed while generating new contract ID");
}
}
@SuppressWarnings("serial")
public static class InvalidAccountAddressException extends BytecodeExecutionException {
public InvalidAccountAddressException(byte[] address) {
super(String.format("Account (%s) does not exist", ByteUtil.toHexString(address)));
}
}
@SuppressWarnings("serial")
public static class ReturnDataCopyIllegalBoundsException extends BytecodeExecutionException {
public ReturnDataCopyIllegalBoundsException(DataWord off, DataWord size, long returnDataSize) {
super(String.format("Illegal RETURNDATACOPY arguments: offset (%s) + size (%s) > RETURNDATASIZE (%d)", off, size, returnDataSize));
}
}
@SuppressWarnings("serial")
public static class StaticCallModificationException extends BytecodeExecutionException {
public StaticCallModificationException() {
super("Attempt to call a state modifying opcode inside STATICCALL");
}
}
public static class Exception {
public static OutOfGasException notEnoughOpGas(OpCode op, long opGas, long programGas) {
return new OutOfGasException("Not enough gas for '%s' operation executing: opGas[%d], programGas[%d];", op, opGas, programGas);
}
public static OutOfGasException notEnoughOpGas(OpCode op, DataWord opGas, DataWord programGas) {
return notEnoughOpGas(op, opGas.longValue(), programGas.longValue());
}
public static OutOfGasException notEnoughOpGas(OpCode op, BigInteger opGas, BigInteger programGas) {
return notEnoughOpGas(op, opGas.longValue(), programGas.longValue());
}
public static OutOfGasException notEnoughSpendingGas(String cause, long gasValue, Program program) {
return new OutOfGasException("Not enough gas for '%s' cause spending: invokeGas[%d], gas[%d], usedGas[%d];",
cause, program.invoke.getGas().longValue(), gasValue, program.getResult().getGasUsed());
}
public static OutOfGasException gasOverflow(BigInteger actualGas, BigInteger gasLimit) {
return new OutOfGasException("Gas value overflow: actualGas[%d], gasLimit[%d];", actualGas.longValue(), gasLimit.longValue());
}
public static IllegalOperationException invalidOpCode(byte... opCode) {
return new IllegalOperationException("Invalid operation code: opCode[%s];", Hex.toHexString(opCode, 0, 1));
}
public static BadJumpDestinationException badJumpDestination(int pc) {
return new BadJumpDestinationException("Operation with pc isn't 'JUMPDEST': PC[%d];", pc);
}
public static StackTooSmallException tooSmallStack(int expectedSize, int actualSize) {
return new StackTooSmallException("Expected stack size %d but actual %d;", expectedSize, actualSize);
}
}
@SuppressWarnings("serial")
public class StackTooLargeException extends BytecodeExecutionException {
public StackTooLargeException(String message) {
super(message);
}
}
/**
* used mostly for testing reasons
*/
public byte[] getMemory() {
return memory.read(0, memory.size());
}
/**
* used mostly for testing reasons
*/
public void initMem(byte[] data) {
this.memory.write(0, data, data.length, false);
}
public long getOwnerRemainingDuration() {
return getOwnerRemainingDuration(this.storage);
}
public long getOwnerRemainingDuration(Storage storage) {
byte[] ownerAddress = this.getOwnerAddress().getLast20Bytes();
return getRemainingDuration(ownerAddress, storage);
}
public long getRemainingDuration(byte[] address, Storage storage) {
long duration = 0;
if (remainingDurations.containsKey(address)) {
duration = remainingDurations.get(address);
} else {
AccountState accountState = storage.getAccountState(address);
if (accountState != null) {
long contractExpirationSec = accountState.getExpirationTime();
DataWord currTimeStamp = getTimestamp();
long timeStampInSeconds = ByteUtil.byteArrayToLong(currTimeStamp.getData());
duration = contractExpirationSec - timeStampInSeconds;
remainingDurations.put(address, duration);
}
}
return duration;
}
public static long calculateStorageGasNeeded(
long numberOfBytes,
long durationInSeconds,
long byteHourCostIntinybars,
long gasPrice
) {
long gasUnitsToReturn = 0;
long bps = durationInSeconds * numberOfBytes;
long storageCostTinyBars = (durationInSeconds * byteHourCostIntinybars) / 3600;
gasUnitsToReturn = Math.round((double) storageCostTinyBars / (double) gasPrice);
return gasUnitsToReturn;
}
public static long calculateLogSize(int numberOfTopics, long dataSize) {
return LOG_CONTRACT_ID_SIZE + LOG_BLOOM_SIZE + LOG_TOPIC_SIZE * numberOfTopics + dataSize;
}
public long getRbh() {
return rbh;
}
public long getSbh() {
return sbh;
}
public long getLogStorageDuration() {
return logStorageDuration;
}
public void registerStorageGasUsed(long gasValue, String cause) {
if (logger.isDebugEnabled()) {
logger.debug("[{}] Storage gas used for cause: [{}], gas: [{}]", invoke.hashCode(), cause, gasValue);
}
getResult().addStorageGas(gasValue);
}
}