diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/PoW/SmartContractBlockDefinition.cs b/src/Stratis.Bitcoin.Features.SmartContracts/PoW/SmartContractBlockDefinition.cs
index 83b76537f0e..8d33263c702 100644
--- a/src/Stratis.Bitcoin.Features.SmartContracts/PoW/SmartContractBlockDefinition.cs
+++ b/src/Stratis.Bitcoin.Features.SmartContracts/PoW/SmartContractBlockDefinition.cs
@@ -11,6 +11,7 @@
using Stratis.Bitcoin.Features.Miner;
using Stratis.Bitcoin.Features.SmartContracts.Caching;
using Stratis.Bitcoin.Features.SmartContracts.ReflectionExecutor.Consensus.Rules;
+using Stratis.Bitcoin.Features.SmartContracts.Rules;
using Stratis.Bitcoin.Mining;
using Stratis.Bitcoin.Utilities;
using Stratis.SmartContracts.CLR;
@@ -233,6 +234,8 @@ private IContractExecutionResult ExecuteSmartContract(TxMempoolEntry mempoolEntr
this.blockGasConsumed += result.GasConsumed;
+ string returnValue = SmartContractCoinViewRuleLogic.GetSerializedReturnValue(result.Return);
+
// Store all fields. We will reuse these in CoinviewRule.
var receipt = new Receipt(
new uint256(this.stateSnapshot.Root),
@@ -243,7 +246,7 @@ private IContractExecutionResult ExecuteSmartContract(TxMempoolEntry mempoolEntr
result.To,
result.NewContractAddress,
!result.Revert,
- result.Return?.ToString(),
+ returnValue,
result.ErrorMessage,
deserializedCallData.Value.GasPrice,
transactionContext.TxOutValue,
diff --git a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/SmartContractCoinViewRuleLogic.cs b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/SmartContractCoinViewRuleLogic.cs
index 89ba6776964..531aad16022 100644
--- a/src/Stratis.Bitcoin.Features.SmartContracts/Rules/SmartContractCoinViewRuleLogic.cs
+++ b/src/Stratis.Bitcoin.Features.SmartContracts/Rules/SmartContractCoinViewRuleLogic.cs
@@ -5,6 +5,7 @@
using CSharpFunctionalExtensions;
using Microsoft.Extensions.Logging;
using NBitcoin;
+using Newtonsoft.Json;
using Stratis.Bitcoin.Base.Deployments;
using Stratis.Bitcoin.Consensus;
using Stratis.Bitcoin.Consensus.Rules;
@@ -218,6 +219,8 @@ public void ExecuteContractTransaction(RuleContext context, Transaction transact
IContractExecutionResult result = executor.Execute(txContext);
+ string returnValue = GetSerializedReturnValue(result.Return);
+
var receipt = new Receipt(
new uint256(this.mutableStateRepository.Root),
result.GasConsumed,
@@ -227,7 +230,7 @@ public void ExecuteContractTransaction(RuleContext context, Transaction transact
result.To,
result.NewContractAddress,
!result.Revert,
- result.Return?.ToString(),
+ returnValue,
result.ErrorMessage,
deserializedCallData.Value.GasPrice,
txContext.TxOutValue,
@@ -252,6 +255,22 @@ public void ExecuteContractTransaction(RuleContext context, Transaction transact
this.CheckBlockGasLimit(result.GasConsumed);
}
+ ///
+ /// Get a string representation of a smart contract execution's return value.
+ ///
+ public static string GetSerializedReturnValue(object returnValue)
+ {
+ if (returnValue == null)
+ return null;
+
+ // All primitives will come from "a real place". Return the standard string representation.
+ if (!String.IsNullOrEmpty(returnValue.GetType().Namespace))
+ return returnValue.ToString();
+
+ // A custom type. From an empty namespace. Get a JSON representation.
+ return JsonConvert.SerializeObject(returnValue);
+ }
+
///
/// Update the total gas expenditure for this block and verify that it has not exceeded the limit.
///
diff --git a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractWalletTests.cs b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractWalletTests.cs
index fabacb608fe..aa682b7a53f 100644
--- a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractWalletTests.cs
+++ b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractWalletTests.cs
@@ -4,9 +4,14 @@
using System.Linq;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
+using Flurl;
+using Flurl.Http;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitcoin.Networks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using Stratis.Bitcoin.Features.SmartContracts;
using Stratis.Bitcoin.Features.SmartContracts.Models;
using Stratis.Bitcoin.Features.SmartContracts.ReflectionExecutor.Consensus.Rules;
@@ -418,6 +423,78 @@ public void MockChain_NonFungibleToken()
}
}
+ [Fact]
+ public async Task Struct_ReturnValue()
+ {
+ using (PoWMockChain chain = new PoWMockChain(2))
+ {
+ MockChainNode sender = chain.Nodes[0];
+
+ // Mine some coins so we have balance
+ int maturity = (int)sender.CoreNode.FullNode.Network.Consensus.CoinbaseMaturity;
+ sender.MineBlocks(maturity + 1);
+ int spendable = GetSpendableBlocks(maturity + 1, maturity);
+ Assert.Equal(Money.COIN * spendable * 50, (long)sender.WalletSpendableBalance);
+
+ // Compile file
+ ContractCompilationResult compilationResult = ContractCompiler.CompileFile("SmartContracts/ReturnStruct.cs");
+ Assert.True(compilationResult.Success);
+
+ // Create contract
+ BuildCreateContractTransactionResponse createResponse = sender.SendCreateContractTransaction(compilationResult.Compilation, 30);
+ Assert.True(createResponse.Success);
+ sender.WaitMempoolCount(1);
+ sender.MineBlocks(1);
+ ReceiptResponse createReceipt = sender.GetReceipt(createResponse.TransactionId.ToString());
+ Assert.True(createReceipt.Success);
+
+ // Get the representation we expect to see
+ var theStruct = new ReturnStruct.TheStruct
+ {
+ TestBool = true,
+ TestString = "My String"
+ };
+ string jsonStruct = JsonConvert.SerializeObject(theStruct);
+
+ // Make a local call and check the result matches.
+ var localCallResult = await $"http://localhost:{sender.CoreNode.ApiPort}/api"
+ .AppendPathSegment("SmartContracts/local-call")
+ .PostJsonAsync(new LocalCallContractRequest
+ {
+ Amount = "0",
+ ContractAddress = createResponse.NewContractAddress,
+ GasLimit = 100_000,
+ GasPrice = 100,
+ MethodName = "CallMe",
+ Sender = sender.MinerAddress.Address
+ });
+
+ string content = await localCallResult.Content.ReadAsStringAsync();
+ JObject fullExecutionResult = JObject.Parse(content);
+ JObject jsonReturnValue = (JObject) fullExecutionResult["return"];
+
+ Assert.Equal(CleanJson(jsonStruct), CleanJson(jsonReturnValue.ToString()));
+
+ // Call contract for real
+ BuildCallContractTransactionResponse callResponse = sender.SendCallContractTransaction("CallMe", createResponse.NewContractAddress, 0);
+ Assert.True(callResponse.Success);
+ sender.WaitMempoolCount(1);
+ sender.MineBlocks(1);
+
+ // Check the receipt
+ var receipt = sender.GetReceipt(callResponse.TransactionId.ToString());
+ Assert.Equal(CleanJson(jsonStruct), CleanJson(receipt.ReturnValue));
+ }
+ }
+
+ private string CleanJson(string json)
+ {
+ return json.Replace("/", "")
+ .Replace(" ", "")
+ .Replace("\r\n", "")
+ .ToLower();
+ }
+
[Fact]
public void Create_WithFunds()
{
diff --git a/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/ReturnStruct.cs b/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/ReturnStruct.cs
new file mode 100644
index 00000000000..41ae701ae5d
--- /dev/null
+++ b/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/ReturnStruct.cs
@@ -0,0 +1,24 @@
+
+using Stratis.SmartContracts;
+
+public class ReturnStruct : SmartContract
+{
+ public ReturnStruct(ISmartContractState state) : base(state)
+ {
+ }
+
+ public TheStruct CallMe()
+ {
+ return new TheStruct
+ {
+ TestBool = true,
+ TestString = "My String"
+ };
+ }
+
+ public struct TheStruct
+ {
+ public string TestString;
+ public bool TestBool;
+ }
+}
diff --git a/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj b/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj
index e20aefcadde..d1e8fcd10c5 100644
--- a/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj
+++ b/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj
@@ -36,6 +36,7 @@
+
@@ -150,6 +151,9 @@
PreserveNewest
+
+ PreserveNewest
+
Always
diff --git a/src/Stratis.SmartContracts.Tests.Common/StratisSmartContractNode.cs b/src/Stratis.SmartContracts.Tests.Common/StratisSmartContractNode.cs
index cadf1aa0274..5ae3c762559 100644
--- a/src/Stratis.SmartContracts.Tests.Common/StratisSmartContractNode.cs
+++ b/src/Stratis.SmartContracts.Tests.Common/StratisSmartContractNode.cs
@@ -3,6 +3,7 @@
using Stratis.Bitcoin.Base;
using Stratis.Bitcoin.Builder;
using Stratis.Bitcoin.Configuration;
+using Stratis.Bitcoin.Features.Api;
using Stratis.Bitcoin.Features.BlockStore;
using Stratis.Bitcoin.Features.MemoryPool;
using Stratis.Bitcoin.Features.RPC;
@@ -41,7 +42,8 @@ public override void BuildNode()
.UseSmartContractWallet()
.UseSmartContractPowMining()
.MockIBD()
- .UseTestChainedHeaderTree();
+ .UseTestChainedHeaderTree()
+ .UseApi();
if (!this.EnablePeerDiscovery)
{