public (Money, List <TxOut>) Process(SmartContractCarrier carrier, Money mempoolFee,
                                             Gas gasConsumed,
                                             Exception exception)
        {
            this.logger.LogTrace("(){0}:{1}", nameof(mempoolFee), mempoolFee);

            Money fee = mempoolFee;

            var refunds = new List <TxOut>();

            if (exception is OutOfGasException)
            {
                this.logger.LogTrace("(-)[OUTOFGAS_EXCEPTION]");
                return(fee, refunds);
            }

            var refund = new Money(carrier.GasCostBudget - (gasConsumed * carrier.CallData.GasPrice));

            this.logger.LogTrace("{0}:{1},{2}:{3},{4}:{5},{6}:{7}", nameof(carrier.GasCostBudget), carrier.GasCostBudget, nameof(gasConsumed), gasConsumed, nameof(carrier.CallData.GasPrice), carrier.CallData.GasPrice, nameof(refund), refund);

            if (refund > 0)
            {
                fee -= refund;
                refunds.Add(CreateRefund(carrier.Sender, refund));
            }

            this.logger.LogTrace("(-)");

            return(fee, refunds);
        }
        /// <summary>
        /// Contract validation failed, so set the gas units used to a value from the price list and set
        /// the validation errors in a <see cref="SmartContractValidationException"/>.
        /// </summary>
        public static SmartContractExecutionResult ValidationFailed(SmartContractCarrier carrier, SmartContractValidationResult validationResult)
        {
            var executionResult = new SmartContractExecutionResult
            {
                Exception   = new SmartContractValidationException(validationResult.Errors),
                GasConsumed = GasPriceList.ContractValidationFailed()
            };

            return(executionResult);
        }
        /// <summary>
        /// Contract does not exist, so set the gas units used to a value from the price list and set
        /// a <see cref="SmartContractDoesNotExistException"/>.
        /// </summary>
        internal static ISmartContractExecutionResult ContractDoesNotExist(SmartContractCarrier carrier)
        {
            var executionResult = new SmartContractExecutionResult
            {
                Exception   = new SmartContractDoesNotExistException(carrier.CallData.MethodName),
                GasConsumed = GasPriceList.ContractDoesNotExist()
            };

            return(executionResult);
        }
        /// <inheritdoc />
        public Transaction Process(SmartContractCarrier carrier,
                                   IContractStateRepository stateSnapshot,
                                   ISmartContractTransactionContext transactionContext,
                                   IList <TransferInfo> internalTransfers,
                                   bool reversionRequired)
        {
            if (reversionRequired)
            {
                // Send back funds
                if (carrier.Value > 0)
                {
                    return(CreateRefundTransaction(transactionContext));
                }
            }

            // If contract received no funds and made no transfers, do nothing.
            if (carrier.Value == 0 && !internalTransfers.Any())
            {
                return(null);
            }

            // TODO we should not be generating addresses in here!
            uint160 contractAddress = null;

            if (carrier.CallData.ContractAddress == uint160.Zero)
            {
                contractAddress = carrier.GetNewContractAddress();
            }
            else
            {
                contractAddress = carrier.CallData.ContractAddress;
            }

            // If contract had no balance, received funds, but made no transfers, assign the current UTXO.
            if (stateSnapshot.GetUnspent(contractAddress) == null && carrier.Value > 0 && !internalTransfers.Any())
            {
                stateSnapshot.SetUnspent(contractAddress, new ContractUnspentOutput
                {
                    Value = carrier.Value,
                    Hash  = carrier.TransactionHash,
                    Nvout = carrier.Nvout
                });

                return(null);
            }
            // All other cases we need a condensing transaction

            var transactionCondenser = new TransactionCondenser(contractAddress, this.loggerFactory, internalTransfers, stateSnapshot, this.network, transactionContext);

            return(transactionCondenser.CreateCondensingTransaction());
        }
        public ISmartContractExecutionResult Execute(ISmartContractTransactionContext transactionContext)
        {
            this.logger.LogTrace("()");

            var carrier = SmartContractCarrier.Deserialize(transactionContext);

            // Get the contract code (dll) from the repository.
            byte[] contractExecutionCode = this.stateSnapshot.GetCode(carrier.CallData.ContractAddress);
            if (contractExecutionCode == null)
            {
                return(SmartContractExecutionResult.ContractDoesNotExist(carrier));
            }

            // Execute the call to the contract.
            return(this.CreateContextAndExecute(carrier.CallData.ContractAddress, contractExecutionCode, carrier.CallData.MethodName, transactionContext, carrier));
        }
        /// <summary>
        /// Instantiates a <see cref="ScOpcodeType.OP_CALLCONTRACT"/> smart contract carrier.
        /// </summary>
        public static SmartContractCarrier CallContract(int vmVersion, uint160 contractAddress, string methodName, ulong gasPrice, Gas gasLimit, string[] methodParameters = null)
        {
            if (string.IsNullOrWhiteSpace(methodName))
            {
                throw new SmartContractCarrierException(nameof(methodName) + " is null or empty");
            }

            var    serializer   = new MethodParameterSerializer();
            string methodParams = GetMethodParams(serializer, methodParameters);
            var    carrier      = new SmartContractCarrier(new MethodParameterSerializer());

            carrier.CallData = new CallData((byte)ScOpcodeType.OP_CALLCONTRACT, vmVersion, gasPrice, gasLimit, contractAddress, methodName, methodParams);

            if (!string.IsNullOrWhiteSpace(methodParams))
            {
                carrier.MethodParameters = serializer.ToObjects(methodParams);
            }

            return(carrier);
        }
        /// <summary>
        /// Deserializes the smart contract execution code and other related information.
        /// </summary>
        public static SmartContractCarrier Deserialize(ISmartContractTransactionContext transactionContext)
        {
            var byteCursor = 0;
            var takeLength = 0;

            var callData = Deserialize(transactionContext.ScriptPubKey);

            var carrier = new SmartContractCarrier(new MethodParameterSerializer());

            carrier.CallData        = callData;
            carrier.Nvout           = transactionContext.Nvout;
            carrier.Sender          = transactionContext.Sender;
            carrier.TransactionHash = transactionContext.TransactionHash;
            carrier.Value           = transactionContext.TxOutValue;

            if (!string.IsNullOrWhiteSpace(callData.MethodParameters))
            {
                carrier.MethodParameters = carrier.serializer.ToObjects(callData.MethodParameters);
            }

            return(carrier);
        }
        /// <summary>
        /// Instantiates a <see cref="ScOpcodeType.OP_CREATECONTRACT"/> smart contract carrier.
        /// </summary>
        public static SmartContractCarrier CreateContract(int vmVersion, byte[] contractExecutionCode, ulong gasPrice,
                                                          Gas gasLimit, string[] methodParameters = null)
        {
            if (contractExecutionCode == null)
            {
                throw new SmartContractCarrierException(nameof(contractExecutionCode) + " is null");
            }

            var    serializer   = new MethodParameterSerializer();
            string methodParams = GetMethodParams(serializer, methodParameters);

            var callData = new CallData((byte)ScOpcodeType.OP_CREATECONTRACT, vmVersion, gasPrice, gasLimit, contractExecutionCode, methodParams);

            var carrier = new SmartContractCarrier(new MethodParameterSerializer());

            carrier.CallData = callData;

            if (!string.IsNullOrWhiteSpace(methodParams))
            {
                carrier.MethodParameters = serializer.ToObjects(methodParams);
            }
            return(carrier);
        }
        private ISmartContractExecutionResult CreateContextAndExecute(uint160 contractAddress, byte[] contractCode,
                                                                      string methodName, ISmartContractTransactionContext transactionContext, SmartContractCarrier carrier)
        {
            this.logger.LogTrace("()");

            var block = new Block(transactionContext.BlockHeight,
                                  transactionContext.CoinbaseAddress.ToAddress(this.network));

            var executionContext = new SmartContractExecutionContext
                                   (
                block,
                new Message(
                    contractAddress.ToAddress(this.network),
                    carrier.Sender.ToAddress(this.network),
                    carrier.Value,
                    carrier.CallData.GasLimit
                    ),
                contractAddress,
                carrier.CallData.GasPrice,
                carrier.MethodParameters
                                   );

            LogExecutionContext(this.logger, block, executionContext.Message, contractAddress, carrier);

            var gasMeter = new GasMeter(carrier.CallData.GasLimit);

            IPersistenceStrategy persistenceStrategy =
                new MeteredPersistenceStrategy(this.stateSnapshot, gasMeter, this.keyEncodingStrategy);

            var persistentState = new PersistentState(persistenceStrategy, contractAddress, this.network);

            gasMeter.Spend((Gas)GasPriceList.BaseCost);

            var result = this.vm.ExecuteMethod(
                contractCode,
                methodName,
                executionContext,
                gasMeter,
                persistentState,
                this.stateSnapshot);

            var revert = result.ExecutionException != null;

            this.logger.LogTrace("(-)");

            var internalTransaction = this.transferProcessor.Process(
                carrier,
                this.stateSnapshot,
                transactionContext,
                result.InternalTransfers,
                revert);

            (var fee, var refundTxOuts) = this.refundProcessor.Process(
                carrier,
                transactionContext.MempoolFee,
                result.GasConsumed,
                result.ExecutionException);

            var executionResult = new SmartContractExecutionResult
            {
                Exception           = result.ExecutionException,
                GasConsumed         = result.GasConsumed,
                Return              = result.Result,
                InternalTransaction = internalTransaction,
                Fee     = fee,
                Refunds = refundTxOuts
            };

            if (revert)
            {
                this.stateSnapshot.Rollback();
            }
            else
            {
                this.stateSnapshot.Commit();
            }

            return(executionResult);
        }
        internal void LogExecutionContext(ILogger logger, IBlock block, IMessage message, uint160 contractAddress, SmartContractCarrier carrier)
        {
            var builder = new StringBuilder();

            builder.Append(string.Format("{0}:{1},{2}:{3},", nameof(block.Coinbase), block.Coinbase, nameof(block.Number), block.Number));
            builder.Append(string.Format("{0}:{1},", nameof(contractAddress), contractAddress.ToAddress(this.network)));
            builder.Append(string.Format("{0}:{1},", nameof(carrier.CallData.GasPrice), carrier.CallData.GasPrice));
            builder.Append(string.Format("{0}:{1},{2}:{3},{4}:{5},{6}:{7}", nameof(message.ContractAddress), message.ContractAddress, nameof(message.GasLimit), message.GasLimit, nameof(message.Sender), message.Sender, nameof(message.Value), message.Value));

            if (carrier.MethodParameters != null && carrier.MethodParameters.Length > 0)
            {
                builder.Append(string.Format(",{0}:{1}", nameof(carrier.MethodParameters), carrier.MethodParameters));
            }

            logger.LogTrace("{0}", builder.ToString());
        }
        public ISmartContractExecutionResult Execute(ISmartContractTransactionContext transactionContext)
        {
            this.logger.LogTrace("()");

            var carrier = SmartContractCarrier.Deserialize(transactionContext);

            // Create a new address for the contract.
            uint160 newContractAddress = carrier.GetNewContractAddress();

            // Create an account for the contract in the state repository.
            this.stateSnapshot.CreateAccount(newContractAddress);

            // Decompile the contract execution code and validate it.
            SmartContractDecompilation    decompilation = SmartContractDecompiler.GetModuleDefinition(carrier.CallData.ContractExecutionCode);
            SmartContractValidationResult validation    = this.validator.Validate(decompilation);

            // If validation failed, refund the sender any remaining gas.
            if (!validation.IsValid)
            {
                this.logger.LogTrace("(-)[CONTRACT_VALIDATION_FAILED]");
                return(SmartContractExecutionResult.ValidationFailed(carrier, validation));
            }

            var block            = new Block(transactionContext.BlockHeight, transactionContext.CoinbaseAddress.ToAddress(this.network));
            var executionContext = new SmartContractExecutionContext
                                   (
                block,
                new Message(
                    newContractAddress.ToAddress(this.network),
                    carrier.Sender.ToAddress(this.network),
                    carrier.Value,
                    carrier.CallData.GasLimit
                    ),
                newContractAddress,
                carrier.CallData.GasPrice,
                carrier.MethodParameters
                                   );

            LogExecutionContext(this.logger, block, executionContext.Message, newContractAddress, carrier);

            var gasMeter = new GasMeter(carrier.CallData.GasLimit);

            IPersistenceStrategy persistenceStrategy = new MeteredPersistenceStrategy(this.stateSnapshot, gasMeter, new BasicKeyEncodingStrategy());
            var persistentState = new PersistentState(persistenceStrategy, newContractAddress, this.network);

            gasMeter.Spend((Gas)GasPriceList.BaseCost);

            var result = this.vm.Create(carrier.CallData.ContractExecutionCode, executionContext, gasMeter, persistentState, this.stateSnapshot);

            var revert = result.ExecutionException != null;

            var internalTransaction = this.transferProcessor.Process(
                carrier,
                this.stateSnapshot,
                transactionContext,
                result.InternalTransfers,
                revert);

            (var fee, var refundTxOuts) = this.refundProcessor.Process(
                carrier,
                transactionContext.MempoolFee,
                result.GasConsumed,
                result.ExecutionException);

            var executionResult = new SmartContractExecutionResult
            {
                NewContractAddress  = revert ? null : newContractAddress,
                Exception           = result.ExecutionException,
                GasConsumed         = result.GasConsumed,
                Return              = result.Result,
                InternalTransaction = internalTransaction,
                Fee     = fee,
                Refunds = refundTxOuts
            };

            if (revert)
            {
                this.logger.LogTrace("(-)[CONTRACT_EXECUTION_FAILED]");

                this.stateSnapshot.Rollback();
            }
            else
            {
                this.logger.LogTrace("(-):{0}={1}", nameof(newContractAddress), newContractAddress);

                this.stateSnapshot.SetCode(newContractAddress, carrier.CallData.ContractExecutionCode);

                this.stateSnapshot.Commit();
            }

            return(executionResult);
        }
Example #12
0
 /// <summary>
 /// Get the address for a newly deployed contract.
 /// </summary>
 /// <param name="transaction"></param>
 public static uint160 GetNewContractAddress(this SmartContractCarrier carrier)
 {
     return(Core.NewContractAddressExtension.GetContractAddressFromTransactionHash(carrier.TransactionHash));
 }