public override void Execute() { // Obtain all of our values for the call (the value variable is only used in some calls, and is zero otherwise) BigInteger gas = Stack.Pop(); // The base amount of gas we allocate to the call. Address to = Stack.Pop(); // The address we're making the call to. BigInteger value = 0; if (Opcode == InstructionOpcode.CALL || Opcode == InstructionOpcode.CALLCODE) { value = Stack.Pop(); } // Obtain the values for where our input memory comes from, and where our output memory will go to. BigInteger inputMemoryStart = Stack.Pop(); BigInteger inputMemorySize = Stack.Pop(); BigInteger outputMemoryStart = Stack.Pop(); BigInteger outputMemorySize = Stack.Pop(); // CALL opcode can only make static calls with a zero value. if (Opcode == InstructionOpcode.CALL && Message.IsStatic && value != 0) { throw new EVMException($"Cannot use opcode {Opcode.ToString()} in a static context call with any value but zero."); } // Gas: Pre-Expand Memory (since it should be charged for expansion, then the call should be made, then written to) Memory.ExpandStream(inputMemoryStart, inputMemorySize); Memory.ExpandStream(outputMemoryStart, outputMemorySize); // Gas: Calculate extra gas costs based off of forks and what kind of call this is. BigInteger extraGasCost = 0; // Gas: If this is a call and the account doesn't exist if (Opcode == InstructionOpcode.CALL && !EVM.State.ContainsAccount(to)) { // If the value is above zero (or if we're pre-spurious dragon) we charge for calling a new account. if (value > 0 || Version < EthereumRelease.SpuriousDragon) { extraGasCost = GasDefinitions.GAS_CALL_NEW_ACCOUNT; } } // If we are transferring a value, we charge gas if (value > 0) { extraGasCost += GasDefinitions.GAS_CALL_VALUE; } // Tangerine whistle introduces new inner call gas limits // Source: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md if (Version < EthereumRelease.TangerineWhistle) { // Prior to tangerine whistle, we need the provided gas + extra gas available GasState.Check(gas + extraGasCost); } else { // After tangerine whistle, we check that the desired gas amount for the call doesn't exceed our calculated max call gas (or else it's capped). GasState.Check(extraGasCost); gas = BigInteger.Min(gas, GasDefinitions.GetMaxCallGas(GasState.Gas - extraGasCost)); } // Define how much gas our inner message can take. BigInteger innerCallGas = gas; if (value > 0) { innerCallGas += GasDefinitions.GAS_CALL_VALUE_STIPEND; } // Verify we have enough balance and call depth hasn't exceeded the maximum. if (EVM.State.GetBalance(Message.To) >= value && Message.Depth < EVMDefinitions.MAX_CALL_DEPTH) { // We're going to make an inner call, so we charge our gas and extra gas. GasState.Deduct(gas + extraGasCost); // Obtain our call data. byte[] callData = Memory.ReadBytes((long)inputMemoryStart, (int)inputMemorySize); // Create our message EVMMessage innerMessage = null; switch (Opcode) { case InstructionOpcode.CALL: innerMessage = new EVMMessage(Message.To, to, value, innerCallGas, callData, Message.Depth + 1, to, true, Message.IsStatic); break; case InstructionOpcode.DELEGATECALL: innerMessage = new EVMMessage(Message.Sender, Message.To, Message.Value, innerCallGas, callData, Message.Depth + 1, to, false, Message.IsStatic); break; case InstructionOpcode.STATICCALL: innerMessage = new EVMMessage(Message.To, to, value, innerCallGas, callData, Message.Depth + 1, to, true, true); break; case InstructionOpcode.CALLCODE: innerMessage = new EVMMessage(Message.To, Message.To, value, innerCallGas, callData, Message.Depth + 1, to, true, Message.IsStatic); break; } // Execute our message in an inner VM. EVMExecutionResult innerVMResult = MeadowEVM.Execute(EVM.State, innerMessage); // Refund our remaining gas that the inner VM didn't use. GasState.Refund(innerVMResult.RemainingGas); // Set our last call results ExecutionState.LastCallResult = innerVMResult; // Push a status indicating whether execution had succeeded without reverting changes. if (!innerVMResult.Succeeded) { Stack.Push(0); } else { Stack.Push(1); } // Determine how much we want to copy out. int returnCopyLength = Math.Min(ExecutionState.LastCallResult?.ReturnData.Length ?? 0, (int)outputMemorySize); if (returnCopyLength == 0 || ExecutionState.LastCallResult?.ReturnData == null) { return; } // Copy our data out Memory.Write((long)outputMemoryStart, ExecutionState.LastCallResult.ReturnData.ToArray()); } else { // We didn't have a sufficient balance or call depth so we push nothing to the stack. We push 0 (fail) Stack.Push(0); // Set our last call result as null. ExecutionState.LastCallResult = null; // Since we couldn't make an inner message call, we charge all the other extra charges, but not the inner message call cost. (Note: inner call gas comes from gas, so we put it here to offset potential extra cost from stipend) GasState.Deduct(gas + extraGasCost - innerCallGas); } }
/// <summary> /// Sets the results of our execution to mark execution as concluded. /// </summary> /// <param name="executionResult">The result of the execution.</param> public void Return(EVMExecutionResult executionResult) { // Set the result of our execution. ExecutionState.Result = executionResult; }
public static EVMExecutionResult CreateContract(State state, EVMMessage message) { // If this message to create didn't come from the transaction origin, we increment nonce if (state.CurrentTransaction.GetSenderAddress() != message.Sender) { state.IncrementNonce(message.Sender); } BigInteger newNonce = state.GetNonce(message.Sender) - 1; message.To = Address.MakeContractAddress(message.Sender, newNonce); // If we're past the byzantium fork, we want to make sure the nonce is 0 and there is no code (making sure the address we're creating doesn't already exist). Otherwise we fail out. if (state.Configuration.Version >= EthereumRelease.Byzantium) { byte[] existingCode = state.GetCodeSegment(message.To); if (state.GetNonce(message.To) > 0 || existingCode.Length > 0) { return(new EVMExecutionResult(null, null, 0, false)); } } // If this is an existing account, remove existing values attached to it. BigInteger balance = state.GetBalance(message.To); if (balance > 0) { state.SetBalance(message.To, balance); state.SetNonce(message.To, 0); state.SetCodeSegment(message.To, Array.Empty <byte>()); } // Obtain our code from our message data byte[] code = message.Data; // Set our message data to a blank array message.Data = Array.Empty <byte>(); // Back up the state. StateSnapshot snapshot = state.Snapshot(); // If spurious dragon version if (state.Configuration.Version >= EthereumRelease.SpuriousDragon) { state.SetNonce(message.To, 1); } else { state.SetNonce(message.To, 0); } // Execute our message EVMExecutionResult result = Execute(state, message, code); // If we should revert if (!result.Succeeded) { // Revert our changes state.Revert(snapshot); // Return our execution result return(result); } else { // If we have no return data, our return data is the To address. if (result.ReturnData.Length == 0) { // Record an exception here (although this is technically not an exception, it is acceptable behavior, but in any real world case, this is undesirable, so we warn of it). state.Configuration.DebugConfiguration?.RecordException(new Exception("Contract deployment ended up deploying a contract which is zero bytes in size."), false); // Return early to avoid processing more code. return(new EVMExecutionResult(result.EVM, message.To.ToByteArray(), result.RemainingGas, true)); } // Obtain our code code = result.ReturnData.ToArray(); // Calculate cost based off every byte in our contract BigInteger remainingGas = result.RemainingGas; BigInteger extraGasCost = result.ReturnData.Length * GasDefinitions.GAS_CONTRACT_BYTE; // Verify we have enough gas to create the contract, and we pass the the size constraint for contracts introduced in spurious dragon. bool passSizeContraint = (state.Configuration.Version < EthereumRelease.SpuriousDragon || code.Length <= EVMDefinitions.MAX_CONTRACT_SIZE); // Allow contract to be over the size limit if the debug/testing option for it has been set if (!passSizeContraint && state.Configuration.DebugConfiguration.IsContractSizeCheckDisabled) { passSizeContraint = true; } if (result.RemainingGas < extraGasCost || !passSizeContraint) { // Store our code length var codeSize = code.Length; // Set our code array as blank code = Array.Empty <byte>(); // If we are past homestead, we revert here. if (state.Configuration.Version >= EthereumRelease.Homestead) { // Report an exception here. string exceptionMessage = null; if (!passSizeContraint) { exceptionMessage = $"Out of gas: Contract size of {codeSize} bytes exceeds the maximum contract size of {EVMDefinitions.MAX_CONTRACT_SIZE} bytes."; } else { exceptionMessage = $"Out of gas: Not enough gas to pay for the cost-per-byte of deployed contract. Gas: {result.RemainingGas} / Cost: {extraGasCost}"; } // Record an exception here. state.Configuration.DebugConfiguration?.RecordException(new Exception(exceptionMessage), false); // Revert our changes state.Revert(snapshot); // Return our execution result return(new EVMExecutionResult(result.EVM, null, 0, false)); } } else { // We could pay for the creation, remove the gas cost from our remaining gas. remainingGas -= extraGasCost; } // Set our code segment. state.SetCodeSegment(message.To, code); // Return our result return(new EVMExecutionResult(result.EVM, message.To.ToByteArray(), remainingGas, true)); } }