Example #1
0
        /// <summary>
        /// Applies an internally generated contract funds transfer message to the current state.
        /// </summary>
        public StateTransitionResult Apply(IState state, ContractTransferMessage message)
        {
            bool enoughBalance = this.EnsureSenderHasEnoughBalance(state, message.From, message.Amount);

            if (!enoughBalance)
            {
                return(StateTransitionResult.Fail((Gas)0, StateTransitionErrorKind.InsufficientBalance));
            }

            var gasMeter = new GasMeter(message.GasLimit);

            // If it's not a contract, create a regular P2PKH tx
            // If it is a contract, do a regular contract call
            byte[] contractCode = state.ContractState.GetCode(message.To);

            if (contractCode == null || contractCode.Length == 0)
            {
                gasMeter.Spend((Gas)GasPriceList.TransferCost);

                // No contract at this address, create a regular P2PKH xfer
                state.AddInternalTransfer(new TransferInfo(message.From, message.To, message.Amount));

                return(StateTransitionResult.Ok(gasMeter.GasConsumed, message.To));
            }

            // For internal contract-contract transfers we need to add the value contained in the contract invocation transaction
            // to the internal transfer list. This must occur before we apply the message to the state.
            state.AddInternalTransfer(new TransferInfo(message.From, message.To, message.Amount));

            gasMeter.Spend((Gas)GasPriceList.BaseCost);
            StateTransitionResult result = this.ApplyCall(state, message, contractCode, gasMeter);

            return(result);
        }
        private StateTransitionResult ApplyCreate(IState state, object[] parameters, byte[] code, BaseMessage message,
                                                  uint160 address,
                                                  string type = null)
        {
            var gasMeter = new GasMeter(message.GasLimit);

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

            state.ContractState.CreateAccount(address);

            ISmartContractState smartContractState = state.CreateSmartContractState(state, gasMeter, address, message, state.ContractState);

            VmExecutionResult result = this.Vm.Create(state.ContractState, smartContractState, code, parameters, type);

            bool revert = !result.IsSuccess;

            if (revert)
            {
                return(StateTransitionResult.Fail(
                           gasMeter.GasConsumed,
                           result.Error));
            }

            return(StateTransitionResult.Ok(
                       gasMeter.GasConsumed,
                       address,
                       result.Success.Result
                       ));
        }
        private StateTransitionResult ApplyCall(IState state, CallMessage message, byte[] contractCode)
        {
            var gasMeter = new GasMeter(message.GasLimit);

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

            // This needs to happen after the base fee is charged, which is why it's in here.
            // TODO - Remove this check. It isn't possible for the method name to be null.
            if (message.Method.Name == null)
            {
                return(StateTransitionResult.Fail(gasMeter.GasConsumed, StateTransitionErrorKind.NoMethodName));
            }

            string type = state.ContractState.GetContractType(message.To);

            ISmartContractState smartContractState = state.CreateSmartContractState(state, gasMeter, message.To, message, state.ContractState);

            VmExecutionResult result = this.Vm.ExecuteMethod(smartContractState, message.Method, contractCode, type);

            bool revert = !result.IsSuccess;

            if (revert)
            {
                return(StateTransitionResult.Fail(
                           gasMeter.GasConsumed,
                           result.Error));
            }

            return(StateTransitionResult.Ok(
                       gasMeter.GasConsumed,
                       message.To,
                       result.Success.Result
                       ));
        }
Example #4
0
        /// <summary>
        /// Applies an internally generated contract creation message to the current state.
        /// </summary>
        public StateTransitionResult Apply(IState state, InternalCreateMessage message)
        {
            bool enoughBalance = this.EnsureSenderHasEnoughBalance(state, message.From, message.Amount);

            if (!enoughBalance)
            {
                return(StateTransitionResult.Fail((Gas)0, StateTransitionErrorKind.InsufficientBalance)); // Trivial - just return and let the MethodCall gas account for it.
            }
            var gasMeter = new GasMeter(message.GasLimit);

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

            byte[] contractCode = state.ContractState.GetCode(message.From);

            uint160 address = state.GenerateAddress(this.AddressGenerator);

            // For internal creates we need to add the value contained in the contract invocation transaction
            // to the internal transfer list. This must occur before we apply the message to the state.
            // For external creates we do not need to do this.
            state.AddInternalTransfer(new TransferInfo(message.From, address, message.Amount));

            StateTransitionResult result = this.ApplyCreate(state, message.Parameters, contractCode, message, address, gasMeter, message.Type);

            return(result);
        }
Example #5
0
        /// <summary>
        /// Applies an internally generated contract method call message to the current state.
        /// </summary>
        public StateTransitionResult Apply(IState state, InternalCallMessage message)
        {
            bool enoughBalance = this.EnsureSenderHasEnoughBalance(state, message.From, message.Amount);

            if (!enoughBalance)
            {
                return(StateTransitionResult.Fail((Gas)0, StateTransitionErrorKind.InsufficientBalance)); // Trivial - just return and let the MethodCall gas account for it.
            }
            var gasMeter = new GasMeter(message.GasLimit);

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

            byte[] contractCode = state.ContractState.GetCode(message.To);

            if (contractCode == null || contractCode.Length == 0)
            {
                return(StateTransitionResult.Fail(gasMeter.GasConsumed, StateTransitionErrorKind.NoCode));
            }

            // For internal calls we need to add the value contained in the contract invocation transaction
            // to the internal transfer list. This must occur before we apply the message to the state.
            // For external calls we do not need to do this.
            state.AddInternalTransfer(new TransferInfo(message.From, message.To, message.Amount));

            StateTransitionResult result = this.ApplyCall(state, message, contractCode, gasMeter);

            return(result);
        }
Example #6
0
        /// <summary>
        /// Applies an externally generated contract creation message to the current state.
        /// </summary>
        public StateTransitionResult Apply(IState state, ExternalCreateMessage message)
        {
            var gasMeter = new GasMeter(message.GasLimit);

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

            // We need to generate an address here so that we can set the initial balance.
            uint160 address = state.GenerateAddress(this.AddressGenerator);

            // For external creates we need to increment the balance state to take into
            // account any funds sent as part of the original contract invocation transaction.
            state.AddInitialTransfer(new TransferInfo(message.From, address, message.Amount));

            return(this.ApplyCreate(state, message.Parameters, message.Code, message, address, gasMeter));
        }
Example #7
0
        private StateTransitionResult ApplyCall(CallMessage message, byte[] contractCode)
        {
            if (this.GasRemaining < message.GasLimit || this.GasRemaining < GasPriceList.BaseCost)
            {
                return(StateTransitionResult.Fail((Gas)0, StateTransitionErrorKind.InsufficientGas));
            }

            var gasMeter = new GasMeter(message.GasLimit);

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

            if (message.Method.Name == null)
            {
                return(StateTransitionResult.Fail(gasMeter.GasConsumed, StateTransitionErrorKind.NoMethodName));
            }

            StateSnapshot stateSnapshot = this.TakeSnapshot();

            IContractState state = this.CreateIntermediateState();

            string type = state.GetContractType(message.To);

            ISmartContractState smartContractState = this.CreateSmartContractState(gasMeter, message.To, message, state);

            VmExecutionResult result = this.Vm.ExecuteMethod(smartContractState, message.Method, contractCode, type);

            this.GasRemaining -= gasMeter.GasConsumed;

            bool revert = result.ExecutionException != null;

            if (revert)
            {
                this.Rollback(stateSnapshot);

                return(StateTransitionResult.Fail(
                           gasMeter.GasConsumed,
                           result.ExecutionException));
            }

            state.Commit();
            this.GasRemaining -= gasMeter.GasConsumed;

            return(StateTransitionResult.Ok(
                       gasMeter.GasConsumed,
                       message.To,
                       result.Result
                       ));
        }
Example #8
0
        private StateTransitionResult ApplyCreate(object[] parameters, byte[] code, BaseMessage message, string type = null)
        {
            if (this.GasRemaining < message.GasLimit || this.GasRemaining < GasPriceList.BaseCost)
            {
                return(StateTransitionResult.Fail((Gas)0, StateTransitionErrorKind.InsufficientGas));
            }

            StateSnapshot stateSnapshot = this.TakeSnapshot();

            var gasMeter = new GasMeter(message.GasLimit);

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

            uint160 address = this.GetNewAddress();

            // Begin tracking the new intermediate state. We need to keep this reference around
            // for the scope of the transaction, so we can commit it later.
            IContractState state = this.CreateIntermediateState();

            state.CreateAccount(address);

            ISmartContractState smartContractState = this.CreateSmartContractState(gasMeter, address, message, state);

            VmExecutionResult result = this.Vm.Create(state, smartContractState, code, parameters, type);

            this.GasRemaining -= gasMeter.GasConsumed;

            bool revert = result.ExecutionException != null;

            if (revert)
            {
                this.Rollback(stateSnapshot);

                return(StateTransitionResult.Fail(
                           gasMeter.GasConsumed,
                           result.ExecutionException));
            }

            state.Commit();

            return(StateTransitionResult.Ok(
                       gasMeter.GasConsumed,
                       address,
                       result.Result
                       ));
        }
Example #9
0
        /// <summary>
        /// Applies an externally generated contract method call message to the current state.
        /// </summary>
        public StateTransitionResult Apply(IState state, ExternalCallMessage message)
        {
            var gasMeter = new GasMeter(message.GasLimit);

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

            byte[] contractCode = state.ContractState.GetCode(message.To);

            if (contractCode == null || contractCode.Length == 0)
            {
                return(StateTransitionResult.Fail(gasMeter.GasConsumed, StateTransitionErrorKind.NoCode));
            }

            // For external calls we need to increment the balance state to take into
            // account any funds sent as part of the original contract invocation transaction.
            state.AddInitialTransfer(new TransferInfo(message.From, message.To, message.Amount));

            return(this.ApplyCall(state, message, contractCode, gasMeter));
        }
        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);
        }
Example #11
0
        public ISmartContractExecutionResult Execute(ISmartContractTransactionContext transactionContext)
        {
            this.logger.LogTrace("()");

            // Deserialization can't fail because this has already been through SmartContractFormatRule.
            Result <ContractTxData> callDataDeserializationResult = this.serializer.Deserialize(transactionContext.ScriptPubKey.ToBytes());
            ContractTxData          callData = callDataDeserializationResult.Value;

            var gasMeter = new GasMeter(callData.GasLimit);

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

            var context = new TransactionContext(
                transactionContext.TransactionHash,
                transactionContext.BlockHeight,
                transactionContext.CoinbaseAddress,
                transactionContext.Sender,
                transactionContext.TxOutValue);

            var creation = IsCreateContract(callData);

            VmExecutionResult result = creation
                ? this.vm.Create(gasMeter, this.stateSnapshot, callData, context)
                : this.vm.ExecuteMethod(gasMeter, this.stateSnapshot, callData, context);

            var revert = result.ExecutionException != null;

            Transaction internalTransaction = this.transferProcessor.Process(
                this.stateSnapshot,
                creation ? result.NewContractAddress : callData.ContractAddress,
                transactionContext,
                result.InternalTransfers,
                revert);

            (Money fee, List <TxOut> refundTxOuts) = this.refundProcessor.Process(
                callData,
                transactionContext.MempoolFee,
                transactionContext.Sender,
                result.GasConsumed,
                result.ExecutionException);

            var executionResult = new SmartContractExecutionResult
            {
                NewContractAddress  = !revert && creation ? result.NewContractAddress : null,
                Exception           = result.ExecutionException,
                GasConsumed         = result.GasConsumed,
                Return              = result.Result,
                InternalTransaction = internalTransaction,
                Fee     = fee,
                Refunds = refundTxOuts,
                Logs    = result.RawLogs.ToLogs(this.contractPrimitiveSerializer)
            };

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

                this.stateSnapshot.Rollback();
            }
            else
            {
                this.logger.LogTrace("(-)[CONTRACT_EXECUTION_SUCCEEDED]");

                this.stateSnapshot.Commit();
            }

            return(executionResult);
        }
        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);
        }