/// <summary> /// Creates a new instance of a smart contract by invoking the contract's constructor /// </summary> public VmExecutionResult Create(IStateRepository repository, ISmartContractState contractState, ExecutionContext executionContext, byte[] contractCode, object[] parameters, string typeName = null) { // The type and code that will ultimately be executed. Assigned based on which method we use to rewrite contract code. string typeToInstantiate; IContract contract; Observer previousObserver = null; // Hash the code byte[] codeHash = HashHelper.Keccak256(contractCode); uint256 codeHashUint256 = new uint256(codeHash); // Lets see if we already have an assembly CachedAssemblyPackage assemblyPackage = this.assemblyCache.Retrieve(codeHashUint256); if (assemblyPackage != null) { // If the assembly is in the cache, keep a reference to its observer around. // We might be in a nested execution for the same assembly, // in which case we need to restore the previous observer later. previousObserver = assemblyPackage.Assembly.GetObserver(); typeToInstantiate = typeName ?? assemblyPackage.Assembly.DeployedType.Name; Type type = assemblyPackage.Assembly.GetType(typeToInstantiate); uint160 address = contractState.Message.ContractAddress.ToUint160(); contract = Contract.CreateUninitialized(type, contractState, address); // TODO: Type not found? // TODO: Setting observer error? // TODO: Error instantiating contract? } else { // Create from scratch // Validate then rewrite the entirety of the incoming code. Result <IContractModuleDefinition> moduleResult = this.moduleDefinitionReader.Read(contractCode); if (moduleResult.IsFailure) { this.logger.LogDebug(moduleResult.Error); this.logger.LogTrace("(-)[CONTRACT_BYTECODE_INVALID]"); return(VmExecutionResult.Fail(VmExecutionErrorKind.LoadFailed, "Contract bytecode is not valid IL.")); } using (IContractModuleDefinition moduleDefinition = moduleResult.Value) { SmartContractValidationResult validation = moduleDefinition.Validate(this.validator); // If validation failed, refund the sender any remaining gas. if (!validation.IsValid) { this.logger.LogTrace("(-)[CONTRACT_VALIDATION_FAILED]"); return(VmExecutionResult.Fail(VmExecutionErrorKind.ValidationFailed, new SmartContractValidationException(validation.Errors).ToString())); } var rewriter = new ObserverInstanceRewriter(); if (!this.Rewrite(moduleDefinition, rewriter)) { return(VmExecutionResult.Fail(VmExecutionErrorKind.RewriteFailed, "Rewrite module failed")); } Result <ContractByteCode> getCodeResult = this.GetByteCode(moduleDefinition); if (!getCodeResult.IsSuccess) { return(VmExecutionResult.Fail(VmExecutionErrorKind.RewriteFailed, "Serialize module failed")); } // Everything worked. Assign what will get executed. typeToInstantiate = typeName ?? moduleDefinition.ContractType.Name; ContractByteCode code = getCodeResult.Value; Result <IContract> contractLoadResult = this.Load( code, typeToInstantiate, contractState.Message.ContractAddress.ToUint160(), contractState); if (!contractLoadResult.IsSuccess) { return(VmExecutionResult.Fail(VmExecutionErrorKind.LoadFailed, contractLoadResult.Error)); } contract = contractLoadResult.Value; assemblyPackage = new CachedAssemblyPackage(new ContractAssembly(contract.Type.Assembly)); // Cache this completely validated and rewritten contract to reuse later. this.assemblyCache.Store(codeHashUint256, assemblyPackage); } } this.LogExecutionContext(contract.State.Block, contract.State.Message, contract.Address); // Set the code and the Type before the method is invoked repository.SetCode(contract.Address, contractCode); repository.SetContractType(contract.Address, typeToInstantiate); // Set Observer and load and execute. assemblyPackage.Assembly.SetObserver(executionContext.Observer); // Invoke the constructor of the provided contract code IContractInvocationResult invocationResult = contract.InvokeConstructor(parameters); // Always reset the observer, even if the previous was null. assemblyPackage.Assembly.SetObserver(previousObserver); if (!invocationResult.IsSuccess) { this.logger.LogDebug("CREATE_CONTRACT_INSTANTIATION_FAILED"); return(GetInvocationVmErrorResult(invocationResult)); } this.logger.LogDebug("CREATE_CONTRACT_INSTANTIATION_SUCCEEDED"); return(VmExecutionResult.Ok(invocationResult.Return, typeToInstantiate)); }
public void VM_ExecuteContract_CachedAssembly_WithExistingObserver() { ContractCompilationResult compilationResult = ContractCompiler.Compile( @" using System; using Stratis.SmartContracts; public class Contract : SmartContract { public Contract(ISmartContractState state) : base(state) {} public void Test() {} } "); Assert.True(compilationResult.Success); byte[] contractExecutionCode = compilationResult.Compilation; byte[] codeHash = HashHelper.Keccak256(contractExecutionCode); byte[] rewrittenCode; // Rewrite the assembly to have an observer. using (IContractModuleDefinition moduleDefinition = this.context.ModuleDefinitionReader.Read(contractExecutionCode).Value) { var rewriter = new ObserverInstanceRewriter(); moduleDefinition.Rewrite(rewriter); rewrittenCode = moduleDefinition.ToByteCode().Value; } var contractAssembly = new ContractAssembly(Assembly.Load(rewrittenCode)); // Cache the assembly. this.context.ContractCache.Store(new uint256(codeHash), new CachedAssemblyPackage(contractAssembly)); // Set an observer on the cached rewritten assembly. var initialObserver = new Observer(new GasMeter((Gas)(this.gasMeter.GasAvailable + 1000)), new MemoryMeter(100_000)); Assert.True(contractAssembly.SetObserver(initialObserver)); var callData = new MethodCall("Test"); // Run the execution with an empty gas meter, which means it should fail if the correct observer is used. var emptyGasMeter = new GasMeter((Gas)0); var executionContext = new ExecutionContext(new Observer(emptyGasMeter, new MemoryMeter(100_000))); VmExecutionResult result = this.vm.ExecuteMethod(this.contractState, executionContext, callData, contractExecutionCode, "Contract"); CachedAssemblyPackage cachedAssembly = this.context.ContractCache.Retrieve(new uint256(codeHash)); // Check that it's still cached. Assert.NotNull(cachedAssembly); // Check that the observer has been reset to the original. Assert.Same(initialObserver, cachedAssembly.Assembly.GetObserver()); Assert.False(result.IsSuccess); Assert.Equal(VmExecutionErrorKind.OutOfGas, result.Error.ErrorKind); }
/// <summary> /// Invokes a method on an existing smart contract /// </summary> public VmExecutionResult ExecuteMethod(ISmartContractState contractState, ExecutionContext executionContext, MethodCall methodCall, byte[] contractCode, string typeName) { IContract contract; Observer previousObserver = null; // Hash the code byte[] codeHash = HashHelper.Keccak256(contractCode); uint256 codeHashUint256 = new uint256(codeHash); // Lets see if we already have an assembly CachedAssemblyPackage assemblyPackage = this.assemblyCache.Retrieve(codeHashUint256); if (assemblyPackage != null) { // If the assembly is in the cache, keep a reference to its observer around. // We might be in a nested execution for the same assembly, // in which case we need to restore the previous observer later. previousObserver = assemblyPackage.Assembly.GetObserver(); Type type = assemblyPackage.Assembly.GetType(typeName); uint160 address = contractState.Message.ContractAddress.ToUint160(); contract = Contract.CreateUninitialized(type, contractState, address); } else { // Rewrite from scratch. using (IContractModuleDefinition moduleDefinition = this.moduleDefinitionReader.Read(contractCode).Value) { var rewriter = new ObserverInstanceRewriter(); if (!this.Rewrite(moduleDefinition, rewriter)) { return(VmExecutionResult.Fail(VmExecutionErrorKind.RewriteFailed, "Rewrite module failed")); } Result <ContractByteCode> getCodeResult = this.GetByteCode(moduleDefinition); if (!getCodeResult.IsSuccess) { return(VmExecutionResult.Fail(VmExecutionErrorKind.RewriteFailed, "Serialize module failed")); } // Everything worked. Assign the code that will be executed. ContractByteCode code = getCodeResult.Value; // Creating a new observer instance here is necessary due to nesting. // If a nested call takes place it will use a new gas meter instance, // due to the fact that the nested call's gas limit may be specified by the user. // Because of that we can't reuse the same observer for a single execution. Result <IContract> contractLoadResult = this.Load( code, typeName, contractState.Message.ContractAddress.ToUint160(), contractState); if (!contractLoadResult.IsSuccess) { return(VmExecutionResult.Fail(VmExecutionErrorKind.LoadFailed, contractLoadResult.Error)); } contract = contractLoadResult.Value; assemblyPackage = new CachedAssemblyPackage(new ContractAssembly(contract.Type.Assembly)); // Cache this completely validated and rewritten contract to reuse later. this.assemblyCache.Store(codeHashUint256, assemblyPackage); } } this.LogExecutionContext(contract.State.Block, contract.State.Message, contract.Address); // Set new Observer and load and execute. assemblyPackage.Assembly.SetObserver(executionContext.Observer); IContractInvocationResult invocationResult = contract.Invoke(methodCall); // Always reset the observer, even if the previous was null. assemblyPackage.Assembly.SetObserver(previousObserver); if (!invocationResult.IsSuccess) { this.logger.LogTrace("(-)[CALLCONTRACT_INSTANTIATION_FAILED]"); return(GetInvocationVmErrorResult(invocationResult)); } this.logger.LogDebug("CALL_CONTRACT_INSTANTIATION_SUCCEEDED"); return(VmExecutionResult.Ok(invocationResult.Return, typeName)); }