public void SmartContracts_GasPrice_TestOperandInstructionPrices() { var priceList = new Dictionary <Instruction, ulong> { { Instruction.Create(OpCodes.Ldstr, "test"), 1 } }; foreach (KeyValuePair <Instruction, ulong> kvp in priceList) { Instruction instruction = kvp.Key; ulong price = kvp.Value; Assert.Equal(price, GasPriceList.InstructionOperationCost(instruction)); } }
/// <summary> /// Total gas cost to execute the instructions in this segment. /// </summary> public Gas CalculateGasCost() { Gas gasTally = (Gas)0; foreach (Instruction instruction in this.Instructions) { Gas instructionCost = GasPriceList.InstructionOperationCost(instruction); gasTally = (Gas)(gasTally + instructionCost); if (instruction.IsMethodCall()) { var methodToCall = (MethodReference)instruction.Operand; // If it's a method outside this contract then we will add some cost. if (this.methodDefinition.DeclaringType != methodToCall.DeclaringType) { Gas methodCallCost = GasPriceList.MethodCallCost(methodToCall); gasTally = (Gas)(gasTally + methodCallCost); } } } return(gasTally); }
/// <inheritdoc /> public void Rewrite(MethodDefinition methodDefinition, ILProcessor il, ObserverRewriterContext context) { List <Instruction> branches = methodDefinition.Body.Instructions.Where(x => BranchingOps.Contains(x.OpCode)).ToList(); List <Instruction> branchTos = branches.Select(x => (Instruction)x.Operand).ToList(); Gas gasTally = Gas.None; Dictionary <Instruction, Gas> gasToSpendForSegment = new Dictionary <Instruction, Gas>(); // To account for variable load at start. int position = 2; Instruction currentSegmentStart = methodDefinition.Body.Instructions[position]; while (position < methodDefinition.Body.Instructions.Count) { Instruction instruction = methodDefinition.Body.Instructions[position]; Gas instructionCost = GasPriceList.InstructionOperationCost(instruction); // is the end of a segment. Include the current instruction in the count. if (branches.Contains(instruction)) { gasTally = (Gas)(gasTally + instructionCost); gasToSpendForSegment.Add(currentSegmentStart, gasTally); gasTally = Gas.None; position++; if (position == methodDefinition.Body.Instructions.Count) { break; } currentSegmentStart = methodDefinition.Body.Instructions[position]; } // is the start of a new segment. Don't include the current instruction in count. else if (branchTos.Contains(instruction) && instruction != currentSegmentStart) { gasToSpendForSegment.Add(currentSegmentStart, gasTally); gasTally = Gas.None; currentSegmentStart = instruction; position++; } // is a call to another method else if (CallingOps.Contains(instruction.OpCode)) { var methodToCall = (MethodReference)instruction.Operand; // If it's a method inside this contract then the gas will be injected no worries. if (methodToCall.DeclaringType == methodDefinition.DeclaringType) { position++; gasTally = (Gas)(gasTally + instructionCost); } // If it's a method outside this contract then we will need to get some average in future. else { Gas methodCallCost = GasPriceList.MethodCallCost(methodToCall); position++; gasTally = (Gas)(gasTally + instructionCost + methodCallCost); } } // any other instruction. just increase counter. else { position++; gasTally = (Gas)(gasTally + instructionCost); } } if (!gasToSpendForSegment.ContainsKey(currentSegmentStart)) { gasToSpendForSegment.Add(currentSegmentStart, gasTally); } foreach (Instruction instruction in gasToSpendForSegment.Keys) { Instruction injectAfterInstruction = instruction; // If it's a constructor we need to skip the first 3 instructions. // These will always be invoking the base constructor // ldarg.0 // ldarg.0 // call SmartContract::ctor if (methodDefinition.IsConstructor) { injectAfterInstruction = instruction.Next.Next.Next; } AddSpendGasMethodBeforeInstruction(methodDefinition, context.Observer, context.ObserverVariable, injectAfterInstruction, gasToSpendForSegment[instruction]); } }
private void RewriteMethod(MethodDefinition methodDefinition, ObserverReferences observer) { if (methodDefinition.DeclaringType == observer.InstanceField.DeclaringType) { return; // don't inject on our injected type. } if (!methodDefinition.HasBody || methodDefinition.Body.Instructions.Count == 0) { return; // don't inject on method without a Body } List <Instruction> branches = methodDefinition.Body.Instructions.Where(x => BranchingOps.Contains(x.OpCode)).ToList(); List <Instruction> branchTos = branches.Select(x => (Instruction)x.Operand).ToList(); Instruction currentSegmentStart = methodDefinition.Body.Instructions.FirstOrDefault(); Gas gasTally = Gas.None; Dictionary <Instruction, Gas> gasToSpendForSegment = new Dictionary <Instruction, Gas>(); // Inject observer instance to method. ILProcessor il = methodDefinition.Body.GetILProcessor(); var observerVariable = new VariableDefinition(observer.InstanceField.FieldType); il.Body.Variables.Add(observerVariable); Instruction start = methodDefinition.Body.Instructions[0]; il.InsertBefore(start, il.Create(OpCodes.Ldsfld, observer.InstanceField)); il.InsertBefore(start, il.CreateStlocBest(observerVariable)); // Start at 2 because of the instructions we just added. int position = 2; while (position < methodDefinition.Body.Instructions.Count) { Instruction instruction = methodDefinition.Body.Instructions[position]; Gas instructionCost = GasPriceList.InstructionOperationCost(instruction); // is the end of a segment. Include the current instruction in the count. if (branches.Contains(instruction)) { gasTally = (Gas)(gasTally + instructionCost); gasToSpendForSegment.Add(currentSegmentStart, gasTally); gasTally = Gas.None; position++; if (position == methodDefinition.Body.Instructions.Count) { break; } currentSegmentStart = methodDefinition.Body.Instructions[position]; } // is the start of a new segment. Don't include the current instruction in count. else if (branchTos.Contains(instruction) && instruction != currentSegmentStart) { gasToSpendForSegment.Add(currentSegmentStart, gasTally); gasTally = Gas.None; currentSegmentStart = instruction; position++; } // is a call to another method else if (CallingOps.Contains(instruction.OpCode)) { var methodToCall = (MethodReference)instruction.Operand; // If it's a method inside this contract then the gas will be injected no worries. if (methodToCall.DeclaringType == methodDefinition.DeclaringType) { position++; gasTally = (Gas)(gasTally + instructionCost); } // If it's a method outside this contract then we will need to get some average in future. else { Gas methodCallCost = GasPriceList.MethodCallCost(methodToCall); position++; gasTally = (Gas)(gasTally + instructionCost + methodCallCost); } } // any other instruction. just increase counter. else { position++; gasTally = (Gas)(gasTally + instructionCost); } } if (!gasToSpendForSegment.ContainsKey(currentSegmentStart)) { gasToSpendForSegment.Add(currentSegmentStart, gasTally); } foreach (Instruction instruction in gasToSpendForSegment.Keys) { Instruction injectAfterInstruction = instruction; // If it's a constructor we need to skip the first 3 instructions. // These will always be invoking the base constructor // ldarg.0 // ldarg.0 // call SmartContract::ctor if (methodDefinition.IsConstructor) { injectAfterInstruction = instruction.Next.Next.Next; } AddSpendGasMethodBeforeInstruction(methodDefinition, observer, observerVariable, injectAfterInstruction, gasToSpendForSegment[instruction]); } foreach (Instruction instruction in branches) { var oldReference = (Instruction)instruction.Operand; Instruction newReference = oldReference.Previous.Previous.Previous; // 3 were inserted Instruction newInstruction = il.Create(instruction.OpCode, newReference); il.Replace(instruction, newInstruction); } }