public override void Execute() { // Obtain our offsets and sizes for the copy. BigInteger memoryOffset = Stack.Pop(); BigInteger dataOffset = Stack.Pop(); BigInteger dataSize = Stack.Pop(); // This is considered a copy operation, so we charge for the size of the data. GasState.Deduct(GasDefinitions.GetMemoryCopyCost(Version, dataSize)); // Read our data from the code segment (if our offset is wrong or we hit the end of the array, the rest should be zeroes). byte[] data = new byte[(int)dataSize]; int length = data.Length; if (dataOffset > EVM.Code.Length) { dataOffset = 0; length = 0; } else if (dataOffset + length > EVM.Code.Length) { length = EVM.Code.Length - (int)dataOffset; } // Copy to the memory location. EVM.Code.Slice((int)dataOffset, length).CopyTo(data); // Write the data to our given memory offset. Memory.Write((long)memoryOffset, data); }
public override void Execute() { // Obtain our offsets and sizes for the copy. BigInteger memoryOffset = Stack.Pop(); BigInteger dataOffset = Stack.Pop(); BigInteger dataSize = Stack.Pop(); // This is considered a copy operation, so we charge for the size of the data. GasState.Deduct(GasDefinitions.GetMemoryCopyCost(Version, dataSize)); // If we aren't copying anything, we can stop. if (dataOffset + dataSize == 0) { return; } // Check we have a return result, and check it's bounds. if (ExecutionState.LastCallResult?.ReturnData == null) { throw new EVMException($"{Opcode.ToString()} tried to copy return data from last call, but no last return data exists."); } else if (dataOffset + dataSize > (ExecutionState.LastCallResult?.ReturnData.Length ?? 0)) { throw new EVMException($"{Opcode.ToString()} tried to copy return data past the end."); } else { // Otherwise we write our data we wish to copy to memory. Memory.Write((long)memoryOffset, ExecutionState.LastCallResult.ReturnData.Slice((int)dataOffset, (int)(dataSize)).ToArray()); } }
public override void Execute() { // Obtain the values for our call value, and call data memory. BigInteger value = Stack.Pop(); BigInteger inputMemoryStart = Stack.Pop(); BigInteger inputMemorySize = Stack.Pop(); // We'll want to charge for memory expansion first Memory.ExpandStream(inputMemoryStart, inputMemorySize); // If we're in a static context, we can't self destruct if (Message.IsStatic) { throw new EVMException($"{Opcode.ToString()} instruction cannot execute in a static context!"); } // 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) { // Obtain our call information. byte[] callData = Memory.ReadBytes((long)inputMemoryStart, (int)inputMemorySize); BigInteger innerCallGas = GasState.Gas; if (Version >= EthereumRelease.TangerineWhistle) { innerCallGas = GasDefinitions.GetMaxCallGas(innerCallGas); } // Create our message EVMMessage message = new EVMMessage(Message.To, Address.ZERO_ADDRESS, value, innerCallGas, callData, Message.Depth + 1, Address.ZERO_ADDRESS, true, Message.IsStatic); EVMExecutionResult innerVMResult = MeadowEVM.CreateContract(EVM.State, message); if (innerVMResult.Succeeded) { // Push our resulting address onto the stack. Stack.Push(BigIntegerConverter.GetBigInteger(innerVMResult.ReturnData.ToArray())); EVM.ExecutionState.LastCallResult = null; } else { // We failed, push our fail value and put the last call data in place. Stack.Push(0); ExecutionState.LastCallResult = innerVMResult; } } 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; } }
private void Step() { // Verify we're not at the end of the stream if (ExecutionState.PC >= Code.Length) { // If we reached the end, exit with the remainder of the gas and a success status. ExecutionState.Result = new EVMExecutionResult(this, null, true); return; } // Set our position back to the start of the instruction, grab our opcode and verify it. InstructionOpcode opcode = (InstructionOpcode)Code.Span[(int)ExecutionState.PC]; // Obtain our base cost for this opcode. If we fail to, the instruction isn't implemented yet. uint?instructionBaseGasCost = GasDefinitions.GetInstructionBaseGasCost(State.Configuration.Version, opcode); if (instructionBaseGasCost == null) { throw new EVMException($"Invalid opcode {opcode.ToString()} read when executing!"); } // If we just jumped, then this next opcode should be a JUMPDEST. if (ExecutionState.JumpedLastInstruction && opcode != InstructionOpcode.JUMPDEST) { throw new EVMException($"Invalid jump to offset {ExecutionState.PC} in code!"); } // Obtain our instruction implementation for this opcode var opcodeDescriptor = opcode.GetDescriptor(); InstructionBase instruction = opcodeDescriptor.GetInstructionImplementation(this); // Record our code coverage for this execution. CoverageMap?.RecordExecution(instruction.Offset, (ExecutionState.PC - instruction.Offset)); // Record our instruction execution tracing if (State.Configuration.DebugConfiguration.IsTracing) { State.Configuration.DebugConfiguration.ExecutionTrace?.RecordExecution(this, instruction, GasState.Gas, (BigInteger)instructionBaseGasCost); } // Deduct base gas cost GasState.Deduct((BigInteger)instructionBaseGasCost); // Debug: Print out instruction execution information. //if (opcode == InstructionOpcode.JUMPDEST) // Console.WriteLine($"\r\n---------------------------------------------------------------\r\n"); //Console.WriteLine($"0x{instruction.Offset.ToString("X4")}: {instruction}"); //Console.WriteLine($"Stack: {ExecutionState.Stack}"); // Execute the instruction instruction.Execute(); }
public void TestBaseGasCostUpdates() { // Make static assertions about updates made during Tangerine Whistle release. EthereumRelease[] releases = (EthereumRelease[])Enum.GetValues(typeof(EthereumRelease)); foreach (EthereumRelease release in releases) { if (release < EthereumRelease.TangerineWhistle) { Assert.Equal <uint>(20, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.BALANCE)); Assert.Equal <uint>(20, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.EXTCODESIZE)); Assert.Equal <uint>(20, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.EXTCODECOPY)); Assert.Equal <uint>(50, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.SLOAD)); Assert.Equal <uint>(40, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.CALL)); Assert.Equal <uint>(40, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.CALLCODE)); // DELEGATE CALL WAS INTRODUCED IN HOMESTEAD WITH 40 BASE GAS, NULL BEFORE. if (release >= EthereumRelease.Homestead) { Assert.Equal <uint>(40, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.DELEGATECALL)); } else { Assert.Null(GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.DELEGATECALL)); } // STATIC CALL WAS INTRODUCED IN BYZANTIUM WITH 700 BASE GAS, NULL BEFORE if (release >= EthereumRelease.Byzantium) { Assert.Equal <uint>(700, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.STATICCALL)); } else { Assert.Null(GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.STATICCALL)); } Assert.Equal <uint>(0, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.SELFDESTRUCT)); } else { Assert.Equal <uint>(400, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.BALANCE)); Assert.Equal <uint>(700, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.EXTCODESIZE)); Assert.Equal <uint>(700, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.EXTCODECOPY)); Assert.Equal <uint>(200, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.SLOAD)); Assert.Equal <uint>(700, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.CALL)); Assert.Equal <uint>(700, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.CALLCODE)); Assert.Equal <uint>(700, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.DELEGATECALL)); Assert.Equal <uint>(5000, (uint)GasDefinitions.GetInstructionBaseGasCost(release, InstructionOpcode.SELFDESTRUCT)); } } }
/// <summary> /// Expands the memory stream if needed (and charges gas) to accomodate for an operation to occur at the given address with a given size. /// </summary> /// <param name="address">The address where data is presumed to be read or written.</param> /// <param name="size">The size of the data presumed to be read or written.</param> public void ExpandStream(BigInteger address, BigInteger size) { // If our address space doesn't extend to handle data in these bounds, expand memory. if (address + size > Length) { // Memory is allocated such that it is aligned to the size of a WORD. BigInteger currentWordCount = EVMDefinitions.GetWordCount(Length); BigInteger targetWordCount = EVMDefinitions.GetWordCount(address + size); // Calculate cost of gas for expanding our array. BigInteger currentMemoryCost = GasDefinitions.GetMemoryAllocationCost(EVM.Version, currentWordCount); BigInteger targetMemoryCost = GasDefinitions.GetMemoryAllocationCost(EVM.Version, targetWordCount); BigInteger costDelta = targetMemoryCost - currentMemoryCost; // Deduct the difference in cost for expanding our memory. EVM.GasState.Deduct(costDelta); // Set the size of our stream _internalBufferStream.SetLength((long)targetWordCount * EVMDefinitions.WORD_SIZE); // Update our change count ChangeCount++; } }
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); } }