diff --git a/src/Stratis.Features.Unity3dApi/Controllers/Unity3dController.cs b/src/Stratis.Features.Unity3dApi/Controllers/Unity3dController.cs index c13c4b078d..443709ef4f 100644 --- a/src/Stratis.Features.Unity3dApi/Controllers/Unity3dController.cs +++ b/src/Stratis.Features.Unity3dApi/Controllers/Unity3dController.cs @@ -4,9 +4,11 @@ using System.Net; using System.Threading; using System.Threading.Tasks; +using CSharpFunctionalExtensions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using NBitcoin; +using Newtonsoft.Json; using Stratis.Bitcoin.Base; using Stratis.Bitcoin.Controllers.Models; using Stratis.Bitcoin.Features.BlockStore.AddressIndexing; @@ -14,11 +16,23 @@ using Stratis.Bitcoin.Features.BlockStore.Models; using Stratis.Bitcoin.Features.Consensus; using Stratis.Bitcoin.Features.Consensus.CoinViews; +using Stratis.Bitcoin.Features.SmartContracts; +using Stratis.Bitcoin.Features.SmartContracts.Models; +using Stratis.Bitcoin.Features.SmartContracts.ReflectionExecutor; +using Stratis.Bitcoin.Features.SmartContracts.Wallet; using Stratis.Bitcoin.Features.Wallet.Controllers; using Stratis.Bitcoin.Features.Wallet.Models; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; using Stratis.Bitcoin.Utilities.JsonErrors; +using Stratis.Bitcoin.Utilities.ModelStateErrors; +using Stratis.SmartContracts.CLR; +using Stratis.SmartContracts.CLR.Caching; +using Stratis.SmartContracts.CLR.Compilation; +using Stratis.SmartContracts.CLR.Local; +using Stratis.SmartContracts.CLR.Serialization; +using Stratis.SmartContracts.Core.Receipts; +using Stratis.SmartContracts.Core.State; namespace Stratis.Features.Unity3dApi.Controllers { @@ -45,8 +59,22 @@ public class Unity3dController : Controller /// Instance logger. private readonly ILogger logger; + private readonly IContractPrimitiveSerializer primitiveSerializer; + + private readonly IStateRepositoryRoot stateRoot; + + private readonly IContractAssemblyCache contractAssemblyCache; + + private readonly IReceiptRepository receiptRepository; + + private readonly ISmartContractTransactionService smartContractTransactionService; + + private readonly ILocalExecutor localExecutor; + public Unity3dController(ILoggerFactory loggerFactory, IAddressIndexer addressIndexer, - IBlockStore blockStore, IChainState chainState, Network network, ICoinView coinView, WalletController walletController, ChainIndexer chainIndexer, IStakeChain stakeChain = null) + IBlockStore blockStore, IChainState chainState, Network network, ICoinView coinView, WalletController walletController, ChainIndexer chainIndexer, IStakeChain stakeChain = null, + IContractPrimitiveSerializer primitiveSerializer = null, IStateRepositoryRoot stateRoot = null, IContractAssemblyCache contractAssemblyCache = null, + IReceiptRepository receiptRepository = null, ISmartContractTransactionService smartContractTransactionService = null, ILocalExecutor localExecutor = null) { Guard.NotNull(loggerFactory, nameof(loggerFactory)); this.logger = loggerFactory.CreateLogger(this.GetType().FullName); @@ -58,6 +86,13 @@ public Unity3dController(ILoggerFactory loggerFactory, IAddressIndexer addressIn this.walletController = Guard.NotNull(walletController, nameof(walletController)); this.chainIndexer = Guard.NotNull(chainIndexer, nameof(chainIndexer)); this.stakeChain = stakeChain; + + this.primitiveSerializer = primitiveSerializer; + this.stateRoot = stateRoot; + this.contractAssemblyCache = contractAssemblyCache; + this.receiptRepository = receiptRepository; + this.smartContractTransactionService = smartContractTransactionService; + this.localExecutor = localExecutor; } /// @@ -406,5 +441,101 @@ public TipModel GetTip() return null; } } + + /// + /// Gets a smart contract transaction receipt. Receipts contain information about how a smart contract transaction was executed. + /// This includes the value returned from a smart contract call and how much gas was used. + /// + /// A hash of the smart contract transaction (the transaction ID). + /// The receipt for the smart contract. + /// Returns transaction receipt + /// Transaction not found + [Route("api/[controller]/receipt")] + [HttpGet] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + public ReceiptResponse GetReceiptAPI([FromQuery] string txHash) + { + ReceiptResponse receiptResponse; + uint256 txHashNum = new uint256(txHash); + Receipt receipt = this.receiptRepository.Retrieve(txHashNum); + + if (receipt == null) + return null; + + uint160 address = receipt.NewContractAddress ?? receipt.To; + + if (!receipt.Logs.Any()) + { + receiptResponse = new ReceiptResponse(receipt, new List(), this.network); + } + else + { + var deserializer = new ApiLogDeserializer(this.primitiveSerializer, this.network, this.stateRoot, this.contractAssemblyCache); + List logResponses = deserializer.MapLogResponses(receipt.Logs); + + receiptResponse = new ReceiptResponse(receipt, logResponses, this.network); + } + + return receiptResponse; + } + + /// + /// Makes a local call to a method on a smart contract that has been successfully deployed. A transaction + /// is not created as the call is never propagated across the network. All persistent data held by the + /// smart contract is copied before the call is made. Only this copy is altered by the call + /// and the actual data is unaffected. Even if an amount of funds are specified to send with the call, + /// no funds are in fact sent. + /// The purpose of this function is to query and test methods. + /// + /// An object containing the necessary parameters to build the transaction. + /// The result of the local call to the smart contract method. + /// Returns call response + /// Invalid request + /// Unable to deserialize method parameters + [Route("api/[controller]/local-call")] + [HttpPost] + [ProducesResponseType((int)HttpStatusCode.OK)] + [ProducesResponseType((int)HttpStatusCode.BadRequest)] + [ProducesResponseType((int)HttpStatusCode.InternalServerError)] + public LocalExecutionResult LocalCallSmartContractTransaction([FromBody] LocalCallContractRequest request) + { + // Rewrite the method name to a property name + this.RewritePropertyGetterName(request); + + ContractTxData txData = this.smartContractTransactionService.BuildLocalCallTxData(request); + + ulong height = request.BlockHeight.HasValue ? request.BlockHeight.Value : (ulong)this.chainIndexer.Height; + + ILocalExecutionResult result = this.localExecutor.Execute(height, request.Sender?.ToUint160(this.network) ?? new uint160(), + string.IsNullOrWhiteSpace(request.Amount) ? (Money)request.Amount : 0, txData); + + return result as LocalExecutionResult; + } + + /// + /// If the call is to a property, rewrites the method name to the getter method's name. + /// + private void RewritePropertyGetterName(LocalCallContractRequest request) + { + // Don't rewrite if there are params + if (request.Parameters != null && request.Parameters.Any()) + return; + + byte[] contractCode = this.stateRoot.GetCode(request.ContractAddress.ToUint160(this.network)); + + string contractType = this.stateRoot.GetContractType(request.ContractAddress.ToUint160(this.network)); + + Result readResult = ContractDecompiler.GetModuleDefinition(contractCode); + + if (readResult.IsSuccess) + { + IContractModuleDefinition contractModule = readResult.Value; + string propertyGetterName = contractModule.GetPropertyGetterMethodName(contractType, request.MethodName); + + if (propertyGetterName != null) + request.MethodName = propertyGetterName; + } + } } } diff --git a/src/Stratis.Features.Unity3dApi/Stratis.Features.Unity3dApi.csproj b/src/Stratis.Features.Unity3dApi/Stratis.Features.Unity3dApi.csproj index 50438c1f88..17fd6d60ef 100644 --- a/src/Stratis.Features.Unity3dApi/Stratis.Features.Unity3dApi.csproj +++ b/src/Stratis.Features.Unity3dApi/Stratis.Features.Unity3dApi.csproj @@ -8,6 +8,7 @@ +