Esempio n. 1
0
        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);
            }
        }
Esempio n. 2
0
 /// <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;
 }
Esempio n. 3
0
        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));
            }
        }