diff --git a/CHANGELOG.md b/CHANGELOG.md
index 46a7f63a..d89cf710 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,6 @@
## Unreleased changes
+- Added
+ - New transaction `InitContract`
## 4.3.1
- Added
diff --git a/ConcordiumNetSdk.sln b/ConcordiumNetSdk.sln
index 96db10c8..ed3e8bea 100644
--- a/ConcordiumNetSdk.sln
+++ b/ConcordiumNetSdk.sln
@@ -81,6 +81,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Transations.UpdateContract"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeployModule", "examples\DeployModule\DeployModule.csproj", "{D35681A3-04AE-41BA-86F3-3CF5369D6D97}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InitContract", "examples\InitContract\InitContract.csproj", "{E68CBBAC-7BC9-46D3-AA04-2B7A62BD6921}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -239,6 +241,10 @@ Global
{D35681A3-04AE-41BA-86F3-3CF5369D6D97}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D35681A3-04AE-41BA-86F3-3CF5369D6D97}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D35681A3-04AE-41BA-86F3-3CF5369D6D97}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E68CBBAC-7BC9-46D3-AA04-2B7A62BD6921}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E68CBBAC-7BC9-46D3-AA04-2B7A62BD6921}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E68CBBAC-7BC9-46D3-AA04-2B7A62BD6921}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E68CBBAC-7BC9-46D3-AA04-2B7A62BD6921}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -283,5 +289,6 @@ Global
{E2CC6AD7-98CE-41F5-8C66-AE8781F29C77} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
{DBFBB7D1-E82D-4380-8263-B4B0AC3A6266} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
{D35681A3-04AE-41BA-86F3-3CF5369D6D97} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
+ {E68CBBAC-7BC9-46D3-AA04-2B7A62BD6921} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
EndGlobalSection
EndGlobal
diff --git a/examples/InitContract/InitContract.csproj b/examples/InitContract/InitContract.csproj
new file mode 100644
index 00000000..5e61ee63
--- /dev/null
+++ b/examples/InitContract/InitContract.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/InitContract/Program.cs b/examples/InitContract/Program.cs
new file mode 100644
index 00000000..1a30f8aa
--- /dev/null
+++ b/examples/InitContract/Program.cs
@@ -0,0 +1,95 @@
+using System.Globalization;
+using CommandLine;
+using Concordium.Sdk.Client;
+using Concordium.Sdk.Types;
+using Concordium.Sdk.Wallets;
+
+// We disable these warnings since CommandLine needs to set properties in options
+// but we don't want to give default values.
+#pragma warning disable CS8618
+
+namespace InitContract;
+
+internal sealed class InitContractOptions
+{
+ [Option(
+ 'k',
+ "keys",
+ HelpText = "Path to a file with contents that is in the Concordium browser wallet key export format.",
+ Required = true
+ )]
+ public string WalletKeysFile { get; set; }
+ [Option(HelpText = "URL representing the endpoint where the gRPC V2 API is served.",
+ Default = "http://grpc.testnet.concordium.com:20000/")]
+ public string Endpoint { get; set; }
+ [Option('a', "amount", HelpText = "Amount of CCD to deposit.", Default = 0)]
+ public ulong Amount { get; set; }
+
+ [Option('m', "module-ref", HelpText = "The module reference of the smart contract.", Required = true)]
+ public string ModuleRef { get; set; }
+
+ [Option('i', "init-name", HelpText = "The init_name of the module.", Required = true)]
+ public string InitName { get; set; }
+
+ [Option('e', "max-energy", HelpText = "The maximum energy to spend on the module.", Required = true)]
+ public string MaxEnergy { get; set; }
+}
+
+public static class Program
+{
+ ///
+ /// Example demonstrating how to submit a smart contract initialization
+ /// transaction.
+ ///
+ /// The example assumes you have your account key information stored
+ /// in the Concordium browser wallet key export format, and that a path
+ /// pointing to it is supplied to it from the command line.
+ ///
+ public static async Task Main(string[] args) =>
+ await Parser.Default
+ .ParseArguments(args)
+ .WithParsedAsync(Run);
+
+ private static async Task Run(InitContractOptions o)
+ {
+ // Read the account keys from a file.
+ var walletData = File.ReadAllText(o.WalletKeysFile);
+ var account = WalletAccount.FromWalletKeyExportFormat(walletData);
+
+ // Construct the client.
+ var clientOptions = new ConcordiumClientOptions
+ {
+ Timeout = TimeSpan.FromSeconds(10)
+ };
+ using var client = new ConcordiumClient(new Uri(o.Endpoint), clientOptions);
+
+ // Create the init transaction.
+ var successfulParse = ContractName.TryParse(o.InitName, out var parsed);
+ if (!successfulParse)
+ {
+ throw new ArgumentException("Error parsing (" + o.InitName + "): " + parsed.Error.ToString());
+ };
+
+ var amount = CcdAmount.FromCcd(o.Amount);
+ var moduleRef = new ModuleReference(o.ModuleRef);
+ var param = new Parameter(Array.Empty());
+ var maxEnergy = new EnergyAmount(uint.Parse(o.MaxEnergy, CultureInfo.InvariantCulture));
+ var payload = new Concordium.Sdk.Transactions.InitContract(amount, moduleRef, parsed.ContractName!, param);
+
+ // Prepare the transaction for signing.
+ var sender = account.AccountAddress;
+ var sequenceNumber = client.GetNextAccountSequenceNumber(sender).Item1;
+ var expiry = Expiry.AtMinutesFromNow(30);
+ var preparedPayload = payload.Prepare(sender, sequenceNumber, expiry, maxEnergy);
+
+ // Sign the transaction using the account keys.
+ var signedTrx = preparedPayload.Sign(account);
+
+ // Submit the transaction.
+ var txHash = client.SendAccountTransaction(signedTrx);
+
+ // Print the transaction hash.
+ Console.WriteLine($"Successfully submitted init-contract transaction with hash {txHash}");
+ }
+}
+
diff --git a/src/Transactions/AccountTransactionPayload.cs b/src/Transactions/AccountTransactionPayload.cs
index 988e37e4..8694fff6 100644
--- a/src/Transactions/AccountTransactionPayload.cs
+++ b/src/Transactions/AccountTransactionPayload.cs
@@ -88,13 +88,18 @@ private static AccountTransactionPayload ParseRawPayload(Google.Protobuf.ByteStr
parsedPayload = output;
break;
}
+ case TransactionType.InitContract:
+ {
+ InitContract.TryDeserial(payload.ToArray(), out var output);
+ parsedPayload = output;
+ break;
+ }
case TransactionType.Update:
{
UpdateContract.TryDeserial(payload.ToArray(), out var output);
parsedPayload = output;
break;
}
- case TransactionType.InitContract:
case TransactionType.AddBaker:
case TransactionType.RemoveBaker:
case TransactionType.UpdateBakerStake:
diff --git a/src/Transactions/InitContract.cs b/src/Transactions/InitContract.cs
new file mode 100644
index 00000000..af9fcadf
--- /dev/null
+++ b/src/Transactions/InitContract.cs
@@ -0,0 +1,130 @@
+using Concordium.Sdk.Types;
+
+namespace Concordium.Sdk.Transactions;
+
+///
+/// Represents an "init_contract" transaction.
+///
+/// Used for initializing deployed smart contracts.
+///
+/// Deposit this amount of CCD.
+/// The smart contract module reference.
+/// The init name of the smart contract.
+/// The parameters for the smart contract.
+public sealed record InitContract(CcdAmount Amount, ModuleReference ModuleRef, ContractName ContractName, Parameter Parameter) : AccountTransactionPayload
+{
+ ///
+ /// The init contract transaction type to be used in the serialized payload.
+ ///
+ private const byte TransactionType = (byte)Types.TransactionType.InitContract;
+
+ ///
+ /// The minimum serialized length in the serialized payload.
+ ///
+ internal const uint MinSerializedLength =
+ CcdAmount.BytesLength +
+ Hash.BytesLength + // ModuleRef
+ ContractName.MinSerializedLength +
+ Parameter.MinSerializedLength;
+
+ ///
+ /// Prepares the account transaction payload for signing.
+ ///
+ /// Address of the sender of the transaction.
+ /// Account sequence number to use for the transaction.
+ /// Expiration time of the transaction.
+ ///
+ /// The amount of energy that can be used for contract execution.
+ /// The base energy amount for transaction verification will be added to this cost.
+ ///
+ public PreparedAccountTransaction Prepare(
+ AccountAddress sender,
+ AccountSequenceNumber sequenceNumber,
+ Expiry expiry,
+ EnergyAmount energy
+ ) => new(sender, sequenceNumber, expiry, energy, this);
+
+ ///
+ /// Gets the size (number of bytes) of the payload.
+ ///
+ internal override PayloadSize Size() => new(
+ sizeof(TransactionType) +
+ CcdAmount.BytesLength +
+ Hash.BytesLength + // ModuleRef
+ this.ContractName.SerializedLength() +
+ this.Parameter.SerializedLength());
+
+ ///
+ /// Deserialize a "InitContract" payload from a serialized byte array.
+ ///
+ /// The serialized InitContract payload.
+ /// Where to write the result of the operation.
+ public static bool TryDeserial(ReadOnlySpan bytes, out (InitContract? InitContract, string? Error) output)
+ {
+ if (bytes.Length < MinSerializedLength)
+ {
+ var msg = $"Invalid length in `InitContract.TryDeserial`. Expected at least {MinSerializedLength}, found {bytes.Length}";
+ output = (null, msg);
+ return false;
+ };
+ if (bytes[0] != TransactionType)
+ {
+ var msg = $"Invalid transaction type in `InitContract.TryDeserial`. expected {TransactionType}, found {bytes[0]}";
+ output = (null, msg);
+ return false;
+ };
+
+ var remainingBytes = bytes[sizeof(TransactionType)..];
+
+ if (!CcdAmount.TryDeserial(remainingBytes, out var amount))
+ {
+ output = (null, amount.Error);
+ return false;
+ };
+ remainingBytes = remainingBytes[(int)CcdAmount.BytesLength..];
+
+ if (!ModuleReference.TryDeserial(remainingBytes, out var moduleRef))
+ {
+ output = (null, moduleRef.Error);
+ return false;
+ };
+ remainingBytes = remainingBytes[Hash.BytesLength..]; // ModuleRef
+
+ if (!ContractName.TryDeserial(remainingBytes, out var name))
+ {
+ output = (null, name.Error);
+ return false;
+ };
+ remainingBytes = remainingBytes[(int)name.ContractName!.SerializedLength()..];
+
+ if (!Parameter.TryDeserial(remainingBytes, out var param))
+ {
+ output = (null, param.Error);
+ return false;
+ };
+
+ if (amount.Amount == null || moduleRef.Ref == null || name.ContractName == null || param.Parameter == null)
+ {
+ var msg = $"Amount, ModuleRef, ContractName or Parameter were null, but did not produce an error";
+ output = (null, msg);
+ return false;
+ }
+
+ output = (new InitContract(amount.Amount.Value, moduleRef.Ref, name.ContractName, param.Parameter), null);
+ return true;
+ }
+
+ ///
+ /// Copies the "init_contract" transaction in the binary format expected by the node to a byte array.
+ ///
+ public override byte[] ToBytes()
+ {
+ using var memoryStream = new MemoryStream((int)this.Size().Size);
+ memoryStream.WriteByte(TransactionType);
+ memoryStream.Write(this.Amount.ToBytes());
+ memoryStream.Write(this.ModuleRef.ToBytes());
+ memoryStream.Write(this.ContractName.ToBytes());
+ memoryStream.Write(this.Parameter.ToBytes());
+ return memoryStream.ToArray();
+ }
+}
diff --git a/src/Types/ContractName.cs b/src/Types/ContractName.cs
index f72e148b..585c89a6 100644
--- a/src/Types/ContractName.cs
+++ b/src/Types/ContractName.cs
@@ -1,3 +1,5 @@
+using System.Buffers.Binary;
+using System.Text;
using Concordium.Sdk.Helpers;
namespace Concordium.Sdk.Types;
@@ -17,6 +19,16 @@ public sealed record ContractName
///
public string Name { get; init; }
+ ///
+ /// Gets the minimum serialized length (number of bytes) of the init name.
+ ///
+ internal const uint MinSerializedLength = sizeof(ushort);
+
+ ///
+ /// Gets the serialized length (number of bytes) of the init name.
+ ///
+ internal uint SerializedLength() => sizeof(ushort) + (uint)this.Name.Length;
+
private ContractName(string name) => this.Name = name;
internal static ContractName From(Grpc.V2.InitName initName) => new(initName.Value);
@@ -36,6 +48,59 @@ public static bool TryParse(string name, out (ContractName? ContractName, Valida
return validate;
}
+ ///
+ /// Copies the init name to a byte array which has the length preprended.
+ ///
+ public byte[] ToBytes()
+ {
+ var bytes = Encoding.ASCII.GetBytes(this.Name);
+
+ using var memoryStream = new MemoryStream((int)this.SerializedLength());
+ memoryStream.Write(Serialization.ToBytes((ushort)bytes.Length));
+ memoryStream.Write(bytes);
+ return memoryStream.ToArray();
+ }
+
+ ///
+ /// Deserialize an init name from a serialized byte array.
+ ///
+ /// The serialized init name.
+ /// Where to write the result of the operation.
+ public static bool TryDeserial(ReadOnlySpan bytes, out (ContractName? ContractName, string? Error) output)
+ {
+ if (bytes.Length < MinSerializedLength)
+ {
+ var msg = $"Invalid length of input in `InitName.TryDeserial`. Expected at least {MinSerializedLength}, found {bytes.Length}";
+ output = (null, msg);
+ return false;
+ };
+
+ var sizeRead = BinaryPrimitives.ReadUInt16BigEndian(bytes);
+ var size = sizeRead + sizeof(ushort);
+ if (size > bytes.Length)
+ {
+ var msg = $"Invalid length of input in `InitName.TryDeserial`. Expected array of size at least {size}, found {bytes.Length}";
+ output = (null, msg);
+ return false;
+ };
+
+ try
+ {
+ var initNameBytes = bytes.Slice(sizeof(ushort), sizeRead).ToArray();
+ var ascii = Encoding.ASCII.GetString(initNameBytes);
+
+ var correctlyParsed = TryParse(ascii, out var parseOut);
+ output = correctlyParsed ? (parseOut.ContractName, null) : (null, "Error parsing contract name (" + ascii + "): " + parseOut.Error.ToString());
+ return correctlyParsed;
+ }
+ catch (ArgumentException e)
+ {
+ var msg = $"Invalid InitName in `InitName.TryDeserial`: {e.Message}";
+ output = (null, msg);
+ return false;
+ };
+ }
+
///
/// Get the contract name part of .
///
@@ -79,4 +144,10 @@ private static bool IsValid(string name, out ValidationError? error)
error = null;
return true;
}
+
+ /// Check for equality.
+ public bool Equals(ContractName? other) => other != null && this.Name == other.Name;
+
+ /// Gets hash code.
+ public override int GetHashCode() => this.Name.GetHashCode();
}
diff --git a/src/Types/EnergyAmount.cs b/src/Types/EnergyAmount.cs
index 9def3443..7b0d1a12 100644
--- a/src/Types/EnergyAmount.cs
+++ b/src/Types/EnergyAmount.cs
@@ -8,6 +8,7 @@ namespace Concordium.Sdk.Types;
/// Value of the energy amount.
public readonly record struct EnergyAmount(ulong Value)
{
+ ///Byte length of Energy. Used for serialization.
public const uint BytesLength = sizeof(ulong);
///
@@ -16,4 +17,41 @@ public readonly record struct EnergyAmount(ulong Value)
public byte[] ToBytes() => Serialization.ToBytes(this.Value);
internal static EnergyAmount From(Grpc.V2.Energy energy) => new(energy.Value);
+
+ ///
+ /// Add Energy amounts.
+ ///
+ /// The result odoes not fit in
+ public static EnergyAmount operator +(EnergyAmount a, EnergyAmount b)
+ {
+ try
+ {
+ return new EnergyAmount(checked(a.Value + b.Value));
+ }
+ catch (OverflowException e)
+ {
+ throw new ArgumentException(
+ $"The result of {a.Value} + {b.Value} does not fit in UInt64.", e
+ );
+ }
+ }
+
+ ///
+ /// Subtract Energy amounts.
+ ///
+ /// The result does not fit in
+ public static EnergyAmount operator -(EnergyAmount a, EnergyAmount b)
+ {
+ try
+ {
+ return new EnergyAmount(checked(a.Value - b.Value));
+ }
+ catch (OverflowException e)
+ {
+ throw new ArgumentException(
+ $"The result of {a.Value} - {b.Value} does not fit in UInt64.", e
+ );
+ }
+ }
+
}
diff --git a/src/Types/ModuleReference.cs b/src/Types/ModuleReference.cs
index e91d55e6..19065a7b 100644
--- a/src/Types/ModuleReference.cs
+++ b/src/Types/ModuleReference.cs
@@ -14,10 +14,30 @@ internal ModuleReference(ByteString byteString) : base(byteString.ToArray())
internal ModuleRef Into() => new() { Value = ByteString.CopyFrom(this.AsSpan()) };
+ ///
+ /// Create a module reference from a byte array.
+ ///
+ /// The serialized module reference.
+ /// Where to write the result of the operation.
+ public static bool TryDeserial(ReadOnlySpan bytes, out (ModuleReference? Ref, string? Error) output)
+ {
+ if (bytes.Length < BytesLength)
+ {
+ var msg = $"Invalid length of input in `ModuleReference.TryDeserial`. Expected at least {BytesLength}, found {bytes.Length}";
+ output = (null, msg);
+ return false;
+ };
+
+ output = (new ModuleReference(bytes[..BytesLength].ToArray()), null);
+ return true;
+ }
+
///
/// Initializes a new instance.
///
/// A hash represented as a length-64 hex encoded string.
/// The supplied string is not a 64-character hex encoded string.
public ModuleReference(string hexString) : base(hexString) { }
+
+ private ModuleReference(byte[] bytes) : base(bytes) { }
}
diff --git a/src/Types/OnChainData.cs b/src/Types/OnChainData.cs
index 9191d73c..112b774a 100644
--- a/src/Types/OnChainData.cs
+++ b/src/Types/OnChainData.cs
@@ -147,7 +147,7 @@ public byte[] ToBytes()
public override string ToString() => Convert.ToHexString(this._value).ToLowerInvariant();
///
- /// Create an "OnChainData" from a byte array.
+ /// Deserialize an "OnChainData" from a byte array.
///
/// The serialized "OnChainData".
/// Where to write the result of the operation.
diff --git a/src/Types/Parameter.cs b/src/Types/Parameter.cs
index 56d389f4..15f193be 100644
--- a/src/Types/Parameter.cs
+++ b/src/Types/Parameter.cs
@@ -6,14 +6,13 @@ namespace Concordium.Sdk.Types;
///
/// Parameter to the init function or entrypoint.
///
-public sealed record Parameter(byte[] Param)
+public sealed record Parameter(byte[] Param) : IEquatable
{
///
/// Construct an empty smart contract parameter.
///
public static Parameter Empty() => new(Array.Empty());
- private const uint MaxByteLength = 65535;
///
/// Gets the serialized length (number of bytes) of the parameter.
///
@@ -24,6 +23,11 @@ public sealed record Parameter(byte[] Param)
///
internal const uint MinSerializedLength = sizeof(ushort);
+ ///
+ /// Gets the maximum serialized length (number of bytes) of the parameter.
+ ///
+ internal const uint MaxSerializedLength = 65535;
+
internal static Parameter From(Grpc.V2.Parameter parameter) => new(parameter.Value.ToArray());
///
@@ -52,9 +56,9 @@ public static bool TryDeserial(ReadOnlySpan bytes, out (Parameter? Paramet
};
var sizeRead = BinaryPrimitives.ReadUInt16BigEndian(bytes);
- if (sizeRead > MaxByteLength)
+ if (sizeRead > MaxSerializedLength)
{
- var msg = $"Invalid length of input in `Parameter.TryDeserial`. The parameter size can be at most {MaxByteLength} bytes, found {bytes.Length}";
+ var msg = $"Invalid length of input in `Parameter.TryDeserial`. The parameter size can be at most {MaxSerializedLength} bytes, found {bytes.Length}";
output = (null, msg);
return false;
}
@@ -66,11 +70,21 @@ public static bool TryDeserial(ReadOnlySpan bytes, out (Parameter? Paramet
output = (null, msg);
return false;
};
- var parameter = new Parameter(bytes.Slice(sizeof(ushort), sizeRead).ToArray());
- output = (parameter, null);
+
+ output = (new Parameter(bytes.Slice(sizeof(ushort), sizeRead).ToArray()), null);
return true;
}
+ /// Check for equality.
+ public bool Equals(Parameter? other) => other != null && this.Param.SequenceEqual(other.Param);
+
+ /// Gets hash code.
+ public override int GetHashCode()
+ {
+ var paramHash = Helpers.HashCode.GetHashCodeByteArray(this.Param);
+ return paramHash;
+ }
+
///
/// Convert parameters to hex string.
///
diff --git a/tests/UnitTests/Transactions/InitContract.cs b/tests/UnitTests/Transactions/InitContract.cs
new file mode 100644
index 00000000..b6f6aa1d
--- /dev/null
+++ b/tests/UnitTests/Transactions/InitContract.cs
@@ -0,0 +1,100 @@
+using Concordium.Sdk.Transactions;
+using Concordium.Sdk.Types;
+using FluentAssertions;
+using Xunit;
+
+namespace Concordium.Sdk.Tests.UnitTests.Transactions;
+
+public sealed class InitContractTests
+{
+ ///
+ /// Creates a new instance of the
+ /// transaction.
+ ///
+ public static InitContract NewInitContract()
+ {
+ var amount = CcdAmount.FromCcd(100);
+ var moduleRef = new ModuleReference("0000000000000000000000000000000000000000000000000000000000000000");
+ var contractName = ContractName.TryParse("init_name", out var parsed);
+ var parameter = new Parameter(System.Array.Empty());
+
+ return new InitContract(amount, moduleRef, parsed.ContractName!, parameter);
+ }
+
+ [Fact]
+ public void ToBytes_ReturnsCorrectValue()
+ {
+ // The expected payload was generated using the Concordium Rust SDK.
+ var expectedBytes = new byte[]
+ {
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 5,
+ 245,
+ 225,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 9,
+ 105,
+ 110,
+ 105,
+ 116,
+ 95,
+ 110,
+ 97,
+ 109,
+ 101,
+ 0,
+ 0
+ };
+ NewInitContract().ToBytes().Should().BeEquivalentTo(expectedBytes);
+ }
+
+ [Fact]
+ public void ToBytes_InverseOfFromBytes()
+ {
+ if (InitContract.TryDeserial(NewInitContract().ToBytes(), out var deserial))
+ {
+ NewInitContract().Should().Be(deserial.InitContract);
+ }
+ else
+ {
+ Assert.Fail(deserial.Error);
+ }
+ }
+}
diff --git a/tests/UnitTests/Types/ContractNameTests.cs b/tests/UnitTests/Types/ContractNameTests.cs
index 1794d90c..8393feca 100644
--- a/tests/UnitTests/Types/ContractNameTests.cs
+++ b/tests/UnitTests/Types/ContractNameTests.cs
@@ -54,4 +54,47 @@ public void WhenGetContractNamePart_ThenReturnContractName()
// Assert
actual.ContractName.Should().Be(expected);
}
+
+ [Fact]
+ public void DeserializesCorrectly()
+ {
+ var success = ContractName.TryParse("init_name", out var parsed);
+ if (!success)
+ {
+ Assert.Fail(parsed.Error.ToString());
+ }
+
+ var bytes = new byte[] {
+ 0,
+ 9,
+ 105,
+ 110,
+ 105,
+ 116,
+ 95,
+ 110,
+ 97,
+ 109,
+ 101
+ };
+ Assert.Equal(parsed.ContractName!.ToBytes(), bytes);
+ Assert.Equal(parsed.ContractName!.SerializedLength(), (uint)bytes.Length);
+ }
+
+ [Fact]
+ public void SerializeDeserialize()
+ {
+ var parseSuccess = ContractName.TryParse("init_some_name", out var parsed);
+ if (!parseSuccess)
+ {
+ Assert.Fail(parsed.Error.ToString());
+ }
+ var deserialSuccess = ContractName.TryDeserial(parsed.ContractName!.ToBytes(), out var deserial);
+ if (!deserialSuccess)
+ {
+ Assert.Fail(deserial.Error);
+ }
+ deserial.ContractName.Should().Be(parsed.ContractName);
+ Assert.Equal(parsed.ContractName!.SerializedLength(), (uint)parsed.ContractName!.ToBytes().Length);
+ }
}