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 @@
+