Skip to content

Commit 3c98e9b

Browse files
fassadlrrowandh
andauthored
Merge Cirrus Hotfix to release/1.1.1.0 (#805)
* Fix serialization and add tests * PR #662 Local call log serialization * Fix zero local call message value * Fix issue where local call contract address was unable to be found in state tree * Nest deserialized log data * Fix test * Merge branch 'hotfix/param' into release/1.1.0.0-opdex * Fix Tests * Merge pull request #801 from rowandh/opdex Opdex Co-authored-by: Rowan de Haas <rowandh@users.noreply.github.com>
1 parent 8b8a3b5 commit 3c98e9b

File tree

17 files changed

+206
-41
lines changed

17 files changed

+206
-41
lines changed

src/Stratis.Bitcoin.Features.SmartContracts.Tests/ApiLogDeserializerTests.cs

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Stratis.SmartContracts.CLR.Serialization;
1212
using Stratis.SmartContracts.Core.Receipts;
1313
using Stratis.SmartContracts.Core.State;
14+
using Stratis.SmartContracts.Core.State.AccountAbstractionLayer;
1415
using Stratis.SmartContracts.Networks;
1516
using Xunit;
1617

@@ -53,14 +54,14 @@ public void Deserialize_Basic_Log_Success()
5354
var serializer = new ApiLogDeserializer(primitiveSerializer, network, Mock.Of<IStateRepositoryRoot>(), Mock.Of<IContractAssemblyCache>());
5455
dynamic deserializedLog = serializer.DeserializeLogData(testBytes, typeof(TestLog));
5556

56-
Assert.Equal(testStruct.Id, deserializedLog.Id);
57-
Assert.Equal(testStruct.Name, deserializedLog.Name);
58-
Assert.Equal(testStruct.Data, deserializedLog.Data);
59-
Assert.True(testStruct.Datas.SequenceEqual((byte[])deserializedLog.Datas));
60-
Assert.Equal(testStruct.Truth, deserializedLog.Truth);
61-
Assert.Equal(testStruct.Address.ToUint160().ToBase58Address(network), deserializedLog.Address);
62-
Assert.Equal(testStruct.Value128.ToString(), deserializedLog.Value128.ToString());
63-
Assert.Equal(testStruct.Value256.ToString(), deserializedLog.Value256.ToString());
57+
Assert.Equal(testStruct.Id, deserializedLog.Data.Id);
58+
Assert.Equal(testStruct.Name, deserializedLog.Data.Name);
59+
Assert.Equal(testStruct.Data, deserializedLog.Data.Data);
60+
Assert.True(testStruct.Datas.SequenceEqual((byte[])deserializedLog.Data.Datas));
61+
Assert.Equal(testStruct.Truth, deserializedLog.Data.Truth);
62+
Assert.Equal(testStruct.Address.ToUint160().ToBase58Address(network), deserializedLog.Data.Address);
63+
Assert.Equal(testStruct.Value128.ToString(), deserializedLog.Data.Value128.ToString());
64+
Assert.Equal(testStruct.Value256.ToString(), deserializedLog.Data.Value256.ToString());
6465
}
6566

6667
[Fact]
@@ -103,15 +104,45 @@ public void Deserialize_Logs_With_Different_Addresses_From_Cache()
103104

104105
var serializer = new ApiLogDeserializer(primitiveSerializer, network, stateRoot.Object, assemblyCache.Object);
105106

106-
var responses = serializer.MapLogResponses(logs);
107+
List<SmartContracts.Models.LogResponse> responses = serializer.MapLogResponses(logs);
107108

108109
// Verify that we deserialized the logs correctly.
109-
Assert.Equal(testStruct0.Name, ((dynamic)responses[0].Log).Name);
110-
Assert.Equal(testStruct1.Name, ((dynamic)responses[1].Log).Name);
110+
Assert.Equal(testStruct0.Name, ((dynamic)responses[0].Log.Data).Name);
111+
Assert.Equal(testStruct1.Name, ((dynamic)responses[1].Log.Data).Name);
111112

112113
// Verify that we got the code for both log assemblies.
113114
stateRoot.Verify(s => s.GetCodeHash(logs[0].Address), Times.Once);
114115
stateRoot.Verify(s => s.GetCodeHash(logs[1].Address), Times.Once);
115116
}
117+
118+
[Fact]
119+
public void MapTransferInfo_Success()
120+
{
121+
var network = new SmartContractsRegTest();
122+
var primitiveSerializer = new ContractPrimitiveSerializer(network);
123+
124+
var stateRoot = new Mock<IStateRepositoryRoot>();
125+
stateRoot.Setup(r => r.GetCodeHash(It.IsAny<uint160>())).Returns(uint256.Zero.ToBytes());
126+
127+
var assemblyCache = new Mock<IContractAssemblyCache>();
128+
var contractAssembly = new Mock<IContractAssembly>();
129+
130+
var serializer = new ApiLogDeserializer(primitiveSerializer, network, stateRoot.Object, assemblyCache.Object);
131+
132+
var transferInfos = new List<TransferInfo>
133+
{
134+
new TransferInfo(uint160.Zero, uint160.One, 12345),
135+
new TransferInfo(uint160.One, uint160.Zero, 12345),
136+
};
137+
138+
List<SmartContracts.Models.TransferResponse> result = serializer.MapTransferInfo(transferInfos.ToArray());
139+
140+
for (var i = 0; i < transferInfos.Count; i++)
141+
{
142+
Assert.Equal(transferInfos[i].From.ToBase58Address(network), result[i].From);
143+
Assert.Equal(transferInfos[i].To.ToBase58Address(network), result[i].To);
144+
Assert.Equal(transferInfos[i].Value, result[i].Value);
145+
}
146+
}
116147
}
117148
}

src/Stratis.Bitcoin.Features.SmartContracts/ApiLogDeserializer.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Stratis.SmartContracts.CLR.Serialization;
1515
using Stratis.SmartContracts.Core.Receipts;
1616
using Stratis.SmartContracts.Core.State;
17+
using Stratis.SmartContracts.Core.State.AccountAbstractionLayer;
1718

1819
namespace Stratis.Bitcoin.Features.SmartContracts
1920
{
@@ -24,17 +25,28 @@ public class ApiLogDeserializer
2425
{
2526
private readonly IContractPrimitiveSerializer primitiveSerializer;
2627
private readonly Network network;
27-
private readonly IStateRepositoryRoot stateRepositoryRoot;
28+
private readonly IStateRepository stateRepositoryRoot;
2829
private readonly IContractAssemblyCache contractAssemblyCache;
2930

30-
public ApiLogDeserializer(IContractPrimitiveSerializer primitiveSerializer, Network network, IStateRepositoryRoot stateRepositoryRoot, IContractAssemblyCache contractAssemblyCache)
31+
public ApiLogDeserializer(IContractPrimitiveSerializer primitiveSerializer, Network network, IStateRepository stateRepositoryRoot, IContractAssemblyCache contractAssemblyCache)
3132
{
3233
this.primitiveSerializer = primitiveSerializer;
3334
this.network = network;
3435
this.stateRepositoryRoot = stateRepositoryRoot;
3536
this.contractAssemblyCache = contractAssemblyCache;
3637
}
3738

39+
public List<TransferResponse> MapTransferInfo(TransferInfo[] transferInfos)
40+
{
41+
return transferInfos.Select(t => new TransferResponse
42+
{
43+
From = t.From.ToBase58Address(this.network),
44+
To = t.To.ToBase58Address(this.network),
45+
Value = t.Value
46+
})
47+
.ToList();
48+
}
49+
3850
public List<LogResponse> MapLogResponses(Log[] logs)
3951
{
4052
var logResponses = new List<LogResponse>();
@@ -71,7 +83,7 @@ public List<LogResponse> MapLogResponses(Log[] logs)
7183
}
7284

7385
// Deserialize it
74-
dynamic deserialized = DeserializeLogData(log.Data, eventType);
86+
LogData deserialized = DeserializeLogData(log.Data, eventType);
7587

7688
logResponse.Log = deserialized;
7789
}
@@ -83,7 +95,7 @@ private Assembly GetAssembly(uint160 address)
8395
{
8496
var codeHashBytes = this.stateRepositoryRoot.GetCodeHash(address);
8597

86-
if (codeHashBytes == null)
98+
if (codeHashBytes == null || codeHashBytes.Length != 32)
8799
return null;
88100

89101
var codeHash = new uint256(codeHashBytes);
@@ -114,14 +126,12 @@ private Assembly GetAssembly(uint160 address)
114126
/// <param name="bytes">The raw event log data.</param>
115127
/// <param name="type">The type to attempt to deserialize.</param>
116128
/// <returns>An <see cref="ExpandoObject"/> containing the fields of the Type and its deserialized values.</returns>
117-
public dynamic DeserializeLogData(byte[] bytes, Type type)
129+
public LogData DeserializeLogData(byte[] bytes, Type type)
118130
{
119131
RLPCollection collection = (RLPCollection)RLP.Decode(bytes);
120132

121133
var instance = new ExpandoObject() as IDictionary<string, object>;
122134

123-
instance["Event"] = type.Name;
124-
125135
FieldInfo[] fields = type.GetFields();
126136

127137
for (int i = 0; i < fields.Length; i++)
@@ -147,7 +157,7 @@ public dynamic DeserializeLogData(byte[] bytes, Type type)
147157
}
148158
}
149159

150-
return instance;
160+
return new LogData(type.Name, instance);
151161
}
152162
}
153163
}

src/Stratis.Bitcoin.Features.SmartContracts/Models/ReceiptResponse.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class LogResponse
4646
public string[] Topics { get; }
4747
public string Data { get; }
4848

49-
public object Log { get; set; }
49+
public LogData Log { get; set; }
5050

5151
public LogResponse(Log log, Network network)
5252
{
@@ -55,4 +55,42 @@ public LogResponse(Log log, Network network)
5555
this.Data = log.Data.ToHexString();
5656
}
5757
}
58+
59+
public class LogData
60+
{
61+
public LogData(string eventName, IDictionary<string, object> data)
62+
{
63+
this.@Event = eventName;
64+
65+
this.Data = data;
66+
}
67+
68+
public string Event { get; }
69+
70+
public IDictionary<string, object> Data { get; }
71+
}
72+
73+
public class LocalExecutionResponse
74+
{
75+
public IReadOnlyList<TransferResponse> InternalTransfers { get; set; }
76+
77+
public Stratis.SmartContracts.RuntimeObserver.Gas GasConsumed { get; set; }
78+
79+
public bool Revert { get; set; }
80+
81+
public ContractErrorMessage ErrorMessage { get; set; }
82+
83+
public object Return { get; set; }
84+
85+
public IReadOnlyList<LogResponse> Logs { get; set; }
86+
}
87+
88+
public class TransferResponse
89+
{
90+
public string From { get; set; }
91+
92+
public string To { get; set; }
93+
94+
public ulong Value { get; set; }
95+
}
5896
}

src/Stratis.Bitcoin.Features.SmartContracts/ReflectionExecutor/Controllers/SmartContractsController.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,10 +576,22 @@ public IActionResult LocalCallSmartContractTransaction([FromBody] LocalCallContr
576576
ILocalExecutionResult result = this.localExecutor.Execute(
577577
height,
578578
request.Sender?.ToUint160(this.network) ?? new uint160(),
579-
string.IsNullOrWhiteSpace(request.Amount) ? (Money)request.Amount : 0,
579+
!string.IsNullOrWhiteSpace(request.Amount) ? (Money)request.Amount : 0,
580580
txData);
581581

582-
return this.Json(result, new JsonSerializerSettings
582+
var deserializer = new ApiLogDeserializer(this.primitiveSerializer, this.network, result.StateRoot, this.contractAssemblyCache);
583+
584+
var response = new LocalExecutionResponse
585+
{
586+
InternalTransfers = deserializer.MapTransferInfo(result.InternalTransfers.ToArray()),
587+
Logs = deserializer.MapLogResponses(result.Logs.ToArray()),
588+
GasConsumed = result.GasConsumed,
589+
Revert = result.Revert,
590+
ErrorMessage = result.ErrorMessage,
591+
Return = result.Return // All return values should be primitives, let default serializer handle.
592+
};
593+
594+
return this.Json(response, new JsonSerializerSettings
583595
{
584596
ContractResolver = new ContractParametersContractResolver(this.network)
585597
});

src/Stratis.SmartContracts.CLR.Tests/ContractPrimitiveSerializerTests.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Linq;
23
using System.Text;
34
using NBitcoin;
45
using Newtonsoft.Json;
@@ -157,6 +158,21 @@ public void PersistentState_Nullable_Behaviour()
157158
Assert.ThrowsAny<Exception>(() => this.serializer.Deserialize<ContainsNullInt>(serialized));
158159
}
159160

161+
[Fact]
162+
public void Deserialize_Empty_String()
163+
{
164+
var empty = this.serializer.Serialize(string.Empty);
165+
Assert.Equal(string.Empty, this.serializer.Deserialize(typeof(string), empty));
166+
}
167+
168+
[Fact]
169+
public void Deserialize_Empty_Byte_Array()
170+
{
171+
var emptyByte = new byte[0];
172+
var empty = this.serializer.Serialize(emptyByte);
173+
Assert.True(emptyByte.SequenceEqual(this.serializer.Deserialize<byte[]>(empty)));
174+
}
175+
160176
private TestValueType NewTestValueType()
161177
{
162178
var instance = new TestValueType();

src/Stratis.SmartContracts.CLR.Tests/MethodParameterByteSerializerTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,15 @@ public void Deserialize_Garbage_Throws()
7474
Assert.ThrowsAny<Exception>(() => this.Serializer.Deserialize(new byte[] { 0x00, 0x11, 0x22, 0x33 }));
7575
}
7676

77-
7877
public static IEnumerable<object[]> GetData(int numTests)
7978
{
8079
yield return new object[] { true }; // MethodParameterDataType.Bool
8180
yield return new object[] { (byte)1 }; // MethodParameterDataType.Byte
8281
yield return new object[] { Encoding.UTF8.GetBytes("test") }; // MethodParameterDataType.ByteArray
82+
yield return new object[] { new byte[0] { } }; // MethodParameterDataType.ByteArray
8383
yield return new object[] { 's' }; // MethodParameterDataType.Char
8484
yield return new object[] { "test" }; // MethodParameterDataType.String
85+
yield return new object[] { string.Empty }; // MethodParameterDataType.String
8586
yield return new object[] { (uint)36 }; // MethodParameterDataType.UInt
8687
yield return new object[] { (ulong)29 }; // MethodParameterDataType.ULong
8788
yield return new object[] { "0x0000000000000000000000000000000000000001".HexToAddress() }; // MethodParameterDataType.Address

src/Stratis.SmartContracts.CLR.Tests/MethodParameterStringSerializerTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ public static IEnumerable<object[]> GetData(int numTests)
7070
yield return new object[] { true }; // MethodParameterDataType.Bool
7171
yield return new object[] { (byte)1 }; // MethodParameterDataType.Byte
7272
yield return new object[] { Encoding.UTF8.GetBytes("test") }; // MethodParameterDataType.ByteArray
73+
yield return new object[] { new byte[0] }; // MethodParameterDataType.ByteArray
7374
yield return new object[] { 's' }; // MethodParameterDataType.Char
7475
yield return new object[] { "test" }; // MethodParameterDataType.String
76+
yield return new object[] { "" }; // MethodParameterDataType.String
7577
yield return new object[] { (uint)36 }; // MethodParameterDataType.UInt
7678
yield return new object[] { (ulong)29 }; // MethodParameterDataType.ULong
7779
yield return new object[] { new uint160("0x0000000000000000000000000000000000000001").ToBase58Address(Network) }; // MethodParameterDataType.Address

src/Stratis.SmartContracts.CLR/Contract.cs

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,24 +89,31 @@ public static bool ConstructorExists(Type type, IReadOnlyList<object> parameters
8989
/// <inheritdoc />
9090
public IContractInvocationResult InvokeConstructor(IReadOnlyList<object> parameters)
9191
{
92-
// If it's a constructor we need to append the ISmartContractState to the start of the parameters array
93-
object[] invokeParams = { this.State };
92+
try
93+
{
94+
// If it's a constructor we need to append the ISmartContractState to the start of the parameters array
95+
object[] invokeParams = { this.State };
9496

95-
if (parameters != null)
96-
invokeParams = invokeParams.Concat(parameters).ToArray();
97+
if (parameters != null)
98+
invokeParams = invokeParams.Concat(parameters).ToArray();
9799

98-
Type[] types = invokeParams.Select(p => p.GetType()).ToArray();
100+
Type[] types = invokeParams.Select(p => p.GetType()).ToArray();
99101

100-
ConstructorInfo methodToInvoke = this.Type.GetConstructor(types);
102+
ConstructorInfo methodToInvoke = this.Type.GetConstructor(types);
101103

102-
if (methodToInvoke == null)
103-
return ContractInvocationResult.Failure(ContractInvocationErrorType.MethodDoesNotExist);
104+
if (methodToInvoke == null)
105+
return ContractInvocationResult.Failure(ContractInvocationErrorType.MethodDoesNotExist);
104106

105-
IContractInvocationResult result = this.InvokeInternal(methodToInvoke, invokeParams);
107+
IContractInvocationResult result = this.InvokeInternal(methodToInvoke, invokeParams);
106108

107-
this.initialized = result.IsSuccess;
109+
this.initialized = result.IsSuccess;
108110

109-
return result;
111+
return result;
112+
}
113+
catch (Exception)
114+
{
115+
return ContractInvocationResult.Failure(ContractInvocationErrorType.ParameterTypesDontMatch);
116+
}
110117
}
111118

112119
/// <inheritdoc />

src/Stratis.SmartContracts.CLR/Local/ILocalExecutionResult.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using Stratis.SmartContracts.Core;
33
using Stratis.SmartContracts.Core.Receipts;
4+
using Stratis.SmartContracts.Core.State;
45
using Stratis.SmartContracts.Core.State.AccountAbstractionLayer;
56

67
namespace Stratis.SmartContracts.CLR.Local
@@ -13,5 +14,6 @@ public interface ILocalExecutionResult
1314
ContractErrorMessage ErrorMessage { get; }
1415
object Return { get; }
1516
IList<Log> Logs { get; }
17+
IStateRepository StateRoot { get; set; }
1618
}
1719
}

src/Stratis.SmartContracts.CLR/Local/LocalExecutionResult.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Generic;
22
using Stratis.SmartContracts.Core;
33
using Stratis.SmartContracts.Core.Receipts;
4+
using Stratis.SmartContracts.Core.State;
45
using Stratis.SmartContracts.Core.State.AccountAbstractionLayer;
56

67
namespace Stratis.SmartContracts.CLR.Local
@@ -13,5 +14,6 @@ public class LocalExecutionResult : ILocalExecutionResult
1314
public ContractErrorMessage ErrorMessage { get; set; }
1415
public object Return { get; set; }
1516
public IList<Log> Logs { get; set; }
17+
public IStateRepository StateRoot { get; set; }
1618
}
1719
}

0 commit comments

Comments
 (0)