///<inheritdoc /> public ITransferResult TransferFunds(ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer, TransferFundsToContract contractDetails) { this.logger.LogTrace("({0}:{1},{2}:{3})", nameof(addressTo), addressTo, nameof(amountToTransfer), amountToTransfer); // TODO: Spend BaseFee here var balance = smartContractState.GetBalance(); if (balance < amountToTransfer) { this.logger.LogTrace("(-)[INSUFFICIENT_BALANCE]:{0}={1}", nameof(balance), balance); throw new InsufficientBalanceException(); } // Discern whether this is a contract or an ordinary address. byte[] contractCode = this.contractStateRepository.GetCode(addressTo.ToUint160(this.network)); if (contractCode == null || contractCode.Length == 0) { this.internalTransferList.Add(new TransferInfo { From = smartContractState.Message.ContractAddress.ToUint160(this.network), To = addressTo.ToUint160(this.network), Value = amountToTransfer }); this.logger.LogTrace("(-)[TRANSFER_TO_SENDER]:Transfer {0} from {1} to {2}.", smartContractState.Message.ContractAddress, addressTo, amountToTransfer); return(TransferResult.Empty()); } this.logger.LogTrace("(-)[TRANSFER_TO_CONTRACT]"); return(ExecuteTransferFundsToContract(contractCode, smartContractState, addressTo, amountToTransfer, contractDetails)); }
///<inheritdoc /> public ITransferResult Transfer(ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer) { this.logger.LogTrace("({0}:{1},{2}:{3})", nameof(addressTo), addressTo, nameof(amountToTransfer), amountToTransfer); // TODO: Spend BaseFee here EnsureContractHasEnoughBalance(smartContractState, amountToTransfer); // Discern whether this is a contract or an ordinary address. byte[] contractCode = this.contractStateRepository.GetCode(addressTo.ToUint160(this.network)); if (contractCode == null || contractCode.Length == 0) { this.internalTransferList.Add(new TransferInfo { From = smartContractState.Message.ContractAddress.ToUint160(this.network), To = addressTo.ToUint160(this.network), Value = amountToTransfer }); this.logger.LogTrace("(-)[TRANSFER_TO_SENDER]:Transfer {0} from {1} to {2}.", smartContractState.Message.ContractAddress, addressTo, amountToTransfer); return(TransferResult.Empty()); } this.logger.LogTrace("(-)[TRANSFER_TO_CONTRACT]"); // Calling a receive handler: string methodName = MethodCall.ExternalReceiveHandlerName; object[] parameters = new object[] { }; ulong gasBudget = DefaultGasLimit; // for Transfer always send limited gas to prevent re-entrance. return(ExecuteTransferFundsToContract(contractCode, smartContractState, addressTo, amountToTransfer, methodName, parameters, gasBudget)); }
///<inheritdoc /> public ITransferResult Call( ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer, string methodName, object[] parameters, ulong gasLimit = 0) { // For a method call, send all the gas unless an amount was selected.Should only call trusted methods so re - entrance is less problematic. ulong gasBudget = (gasLimit != 0) ? gasLimit : smartContractState.GasMeter.GasAvailable; var message = new InternalCallMessage( addressTo.ToUint160(this.network), smartContractState.Message.ContractAddress.ToUint160(this.network), amountToTransfer, (Gas)gasBudget, new MethodCall(methodName, parameters) ); var result = this.state.Apply(message); return(result.IsSuccess ? TransferResult.Transferred(result.Success.ExecutionResult) : TransferResult.Failed()); }
/// <summary> /// Constructs a result when a transfer from a contract succeeded. /// </summary> /// <param name="returnValue">The object that was returned from the executor.</param> public static TransferResult Transferred(object returnValue) { var result = new TransferResult { ReturnValue = returnValue }; return(result); }
/// <summary> /// Constructs a result when a transfer from a contract failed. /// </summary> /// <param name="thrownException">The exception that was thrown by the executor.</param> public static TransferResult Failed(Exception thrownException) { var result = new TransferResult { ThrownException = thrownException }; return(result); }
/// <summary> /// If the address to where the funds will be tranferred to is a contract, instantiate and execute it. /// </summary> private ITransferResult ExecuteTransferFundsToContract(byte[] contractCode, ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer, TransferFundsToContract contractDetails) { this.logger.LogTrace("({0}:{1},{2}:{3})", nameof(addressTo), addressTo, nameof(amountToTransfer), amountToTransfer); ulong gasBudget = contractDetails.GasBudget == 0 ? DefaultGasLimit : contractDetails.GasBudget; // Ensure we have enough gas left to be able to fund the new GasMeter. if (smartContractState.GasMeter.GasAvailable < gasBudget) { throw new InsufficientGasException(); } var nestedGasMeter = new GasMeter((Gas)gasBudget); IContractStateRepository track = this.contractStateRepository.StartTracking(); var callData = new CallData(smartContractState.GasMeter.GasLimit, addressTo.ToUint160(this.network), contractDetails.ContractMethodName, contractDetails.MethodParameters); var context = new TransactionContext( this.transactionContext.TransactionHash, this.transactionContext.BlockHeight, this.transactionContext.Coinbase, smartContractState.Message.ContractAddress.ToUint160(this.network), amountToTransfer, this.transactionContext.GetNonceAndIncrement()); VmExecutionResult result = this.vm.ExecuteMethod(nestedGasMeter, track, callData, context); // Update parent gas meter. smartContractState.GasMeter.Spend(nestedGasMeter.GasConsumed); var revert = result.ExecutionException != null; if (revert) { track.Rollback(); return(TransferResult.Failed(result.ExecutionException)); } track.Commit(); this.internalTransferList.Add(new TransferInfo { From = smartContractState.Message.ContractAddress.ToUint160(this.network), To = addressTo.ToUint160(this.network), Value = amountToTransfer }); this.logger.LogTrace("(-)"); return(TransferResult.Transferred(result.Result)); }
///<inheritdoc /> public ITransferResult Call( ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer, string methodName, object[] parameters, ulong gasLimit = 0) { Gas gasRemaining = smartContractState.GasMeter.GasAvailable; // For a method call, send all the gas unless an amount was selected.Should only call trusted methods so re - entrance is less problematic. ulong gasBudget = (gasLimit != 0) ? gasLimit : gasRemaining; if (gasRemaining < gasBudget || gasRemaining < GasPriceList.BaseCost) { return(TransferResult.Failed()); } var message = new InternalCallMessage( addressTo.ToUint160(this.network), smartContractState.Message.ContractAddress.ToUint160(this.network), amountToTransfer, (Gas)gasBudget, new MethodCall(methodName, parameters) ); // Create a snapshot of the current state IState newState = this.state.Snapshot(); // Apply the message to the snapshot StateTransitionResult result = this.stateProcessor.Apply(newState, message); // Transition the current state to the new state if (result.IsSuccess) { this.state.TransitionTo(newState); } smartContractState.GasMeter.Spend(result.GasConsumed); return(result.IsSuccess ? TransferResult.Transferred(result.Success.ExecutionResult) : TransferResult.Failed()); }
/// <summary> /// If the address to where the funds will be tranferred to is a contract, instantiate and execute it. /// </summary> private ITransferResult ExecuteTransferFundsToContract(byte[] contractCode, ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer, TransferFundsToContract contractDetails) { this.logger.LogTrace("({0}:{1},{2}:{3})", nameof(addressTo), addressTo, nameof(amountToTransfer), amountToTransfer); IContractStateRepository track = this.contractStateRepository.StartTracking(); IPersistenceStrategy persistenceStrategy = new MeteredPersistenceStrategy(track, smartContractState.GasMeter, this.keyEncodingStrategy); IPersistentState newPersistentState = new PersistentState(persistenceStrategy, addressTo.ToUint160(this.network), this.network); var newMessage = new Message(addressTo, smartContractState.Message.ContractAddress, amountToTransfer, (Gas)(smartContractState.Message.GasLimit - smartContractState.GasMeter.GasConsumed)); ISmartContractExecutionContext newContext = new SmartContractExecutionContext(smartContractState.Block, newMessage, addressTo.ToUint160(this.network), 0, contractDetails.MethodParameters); ISmartContractVirtualMachine vm = new ReflectionVirtualMachine(new InternalTransactionExecutorFactory(this.keyEncodingStrategy, this.loggerFactory, this.network), this.loggerFactory); var result = vm.ExecuteMethod( contractCode, contractDetails.ContractMethodName, newContext, smartContractState.GasMeter, newPersistentState, track); var revert = result.ExecutionException != null; if (revert) { track.Rollback(); return(TransferResult.Failed(result.ExecutionException)); } track.Commit(); this.internalTransferList.Add(new TransferInfo { From = smartContractState.Message.ContractAddress.ToUint160(this.network), To = addressTo.ToUint160(this.network), Value = amountToTransfer }); this.logger.LogTrace("(-)"); return(TransferResult.Transferred(result.Result)); }
///<inheritdoc /> public ITransferResult Transfer(ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer) { this.logger.LogTrace("({0}:{1},{2}:{3})", nameof(addressTo), addressTo, nameof(amountToTransfer), amountToTransfer); ulong gasBudget = DefaultGasLimit; // for Transfer always send limited gas to prevent re-entrance. var message = new ContractTransferMessage( addressTo.ToUint160(this.network), smartContractState.Message.ContractAddress.ToUint160(this.network), amountToTransfer, (Gas)gasBudget ); var result = this.state.Apply(message); return(result.IsSuccess ? TransferResult.Empty() : TransferResult.Failed()); }
/// <summary> /// If the address to where the funds will be tranferred to is a contract, instantiate and execute it. /// </summary> private ITransferResult ExecuteTransferFundsToContract(byte[] contractCode, ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer, TransferFundsToContract contractDetails) { this.logger.LogTrace("({0}:{1},{2}:{3})", nameof(addressTo), addressTo, nameof(amountToTransfer), amountToTransfer); IContractStateRepository track = this.contractStateRepository.StartTracking(); var callData = new CallData(smartContractState.GasMeter.GasLimit, addressTo.ToUint160(this.network), contractDetails.ContractMethodName, contractDetails.MethodParameters); var context = new TransactionContext( this.transactionContext.TransactionHash, this.transactionContext.BlockHeight, this.transactionContext.Coinbase, smartContractState.Message.ContractAddress.ToUint160(this.network), amountToTransfer); var result = this.vm.ExecuteMethod(smartContractState.GasMeter, track, callData, context); var revert = result.ExecutionException != null; if (revert) { track.Rollback(); return(TransferResult.Failed(result.ExecutionException)); } track.Commit(); this.internalTransferList.Add(new TransferInfo { From = smartContractState.Message.ContractAddress.ToUint160(this.network), To = addressTo.ToUint160(this.network), Value = amountToTransfer }); this.logger.LogTrace("(-)"); return(TransferResult.Transferred(result.Result)); }
///<inheritdoc /> public ITransferResult Transfer(ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer) { this.logger.LogTrace("({0}:{1},{2}:{3})", nameof(addressTo), addressTo, nameof(amountToTransfer), amountToTransfer); ulong gasBudget = DefaultGasLimit; // for Transfer always send limited gas to prevent re-entrance. Gas gasRemaining = smartContractState.GasMeter.GasAvailable; if (gasRemaining < gasBudget || gasRemaining < GasPriceList.BaseCost) { return(TransferResult.Failed()); } var message = new ContractTransferMessage( addressTo.ToUint160(this.network), smartContractState.Message.ContractAddress.ToUint160(this.network), amountToTransfer, (Gas)gasBudget ); // Create a snapshot of the current state IState newState = this.state.Snapshot(); // Apply the message to the snapshot StateTransitionResult result = this.stateProcessor.Apply(newState, message); // Transition the current state to the new state if (result.IsSuccess) { this.state.TransitionTo(newState); } smartContractState.GasMeter.Spend(result.GasConsumed); return(result.IsSuccess ? TransferResult.Empty() : TransferResult.Failed()); }
///<inheritdoc /> public ITransferResult Transfer(ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer) { Gas gasRemaining = smartContractState.GasMeter.GasAvailable; if (gasRemaining < GasPriceList.TransferCost) { return(TransferResult.Failed()); } ulong gasBudget = (gasRemaining < DefaultGasLimit) ? gasRemaining // have enough for at least a transfer but not for the DefaultGasLimit : DefaultGasLimit; // have enough for anything var message = new ContractTransferMessage( addressTo.ToUint160(), smartContractState.Message.ContractAddress.ToUint160(), amountToTransfer, (Gas)gasBudget ); // Create a snapshot of the current state IState newState = this.state.Snapshot(); // Apply the message to the snapshot StateTransitionResult result = this.stateProcessor.Apply(newState, message); // Transition the current state to the new state if (result.IsSuccess) { this.state.TransitionTo(newState); } smartContractState.GasMeter.Spend(result.GasConsumed); return(result.IsSuccess ? TransferResult.Empty() : TransferResult.Failed()); }
///<inheritdoc /> public ITransferResult Call( ISmartContractState smartContractState, Address addressTo, ulong amountToTransfer, string methodName, object[] parameters, ulong gasLimit = 0) { // TODO: Spend BaseFee here EnsureContractHasEnoughBalance(smartContractState, amountToTransfer); byte[] contractCode = this.contractStateRepository.GetCode(addressTo.ToUint160(this.network)); if (contractCode == null || contractCode.Length == 0) { return(TransferResult.Empty()); } // Here, we know contract has code, so we execute it // For a method call, send all the gas unless an amount was selected.Should only call trusted methods so re - entrance is less problematic. ulong gasBudget = (gasLimit != 0) ? gasLimit : smartContractState.GasMeter.GasAvailable; return(ExecuteTransferFundsToContract(contractCode, smartContractState, addressTo, amountToTransfer, methodName, parameters, gasBudget)); }