diff --git a/CHANGELOG.md b/CHANGELOG.md
index d89cf710..b6d3deaf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
## Unreleased changes
- Added
- New transaction `InitContract`
+ - Add `WaitUntilFinalized` method on `ConcordiumClient` for waiting for transactions to finalized.
+ - Add `Parameter.UpdateJson` and `Parameter.InitJson` for constructing parameters for update and init transactions using JSON and smart contract module schemas (see the example in example/UpdateContractMint).
+ - Add class `SchemaType` for representing a single type in a module schema, such as the parameter.
+ - Add `Parameter.FromJson` for constructing parameters using `SchemaType` and JSON.
## 4.3.1
- Added
diff --git a/ConcordiumNetSdk.sln b/ConcordiumNetSdk.sln
index ed3e8bea..de06e00a 100644
--- a/ConcordiumNetSdk.sln
+++ b/ConcordiumNetSdk.sln
@@ -83,6 +83,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeployModule", "examples\De
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InitContract", "examples\InitContract\InitContract.csproj", "{E68CBBAC-7BC9-46D3-AA04-2B7A62BD6921}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Transations.UpdateContractMint", "examples\UpdateContractMint\Transations.UpdateContractMint.csproj", "{A1B271B6-CC7B-4949-A3F5-4B7F1C2080CA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -245,6 +247,10 @@ Global
{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
+ {A1B271B6-CC7B-4949-A3F5-4B7F1C2080CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A1B271B6-CC7B-4949-A3F5-4B7F1C2080CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A1B271B6-CC7B-4949-A3F5-4B7F1C2080CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A1B271B6-CC7B-4949-A3F5-4B7F1C2080CA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -290,5 +296,6 @@ Global
{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}
+ {A1B271B6-CC7B-4949-A3F5-4B7F1C2080CA} = {FD2CDD9F-4650-4705-9CA2-98CC81F8891D}
EndGlobalSection
EndGlobal
diff --git a/examples/UpdateContractMint/Program.cs b/examples/UpdateContractMint/Program.cs
new file mode 100644
index 00000000..f09dbc8b
--- /dev/null
+++ b/examples/UpdateContractMint/Program.cs
@@ -0,0 +1,263 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using CommandLine;
+using Concordium.Sdk.Client;
+using Concordium.Sdk.Types;
+using Concordium.Sdk.Wallets;
+
+namespace Transactions.UpdateContractMint;
+
+// 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
+
+internal sealed class Options
+{
+ [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 = "https://grpc.testnet.concordium.com:20000/")]
+ public string Endpoint { get; set; }
+ [Option("contract", HelpText = "The index of the smart contract.", Default = "7936")]
+ public ulong Contract { get; set; }
+ [Option("token", HelpText = "Token ID to mint", Default = "00")]
+ public string TokenId { get; set; }
+ [Option("metadata-url", HelpText = "URL to the metadata", Default = "")]
+ public string MetadataUrl { get; set; }
+ [Option("max-energy", HelpText = "The maximum energy to spend on the module.", Default = 5000)]
+ public ulong MaxEnergy { get; set; }
+}
+
+///
+/// Mint CIS-2 tokens for the 'cis2_multi' smart contract example.
+///
+/// https://github.com/Concordium/concordium-rust-smart-contracts/blob/86511efac8e335abac66176df895c21a5cde252c/examples/cis2-multi/src/lib.rs
+///
+/// This example demonstrates:
+///
+/// - Submitting a transaction updating a smart contract, where the parameter is constructed
+/// using a smart contract module schema.
+/// - Waiting for the transaction to finalize.
+/// - Checking the outcome and reading the logged events of the contract, parsing the events using
+/// a smart contract module schema.
+///
+/// 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.
+/// - The 'cis2_multi' smart contract example is deployed on chain (already on testnet) with a
+/// module reference matching the value of `CIS2_MULTI_MODULE_REF`.
+/// - You have the contract address of an instance of the 'cis2_multi' smart contract (On testnet
+/// the contract with index 7936 can be used).
+///
+internal class Program
+{
+ ///
+ /// Example send a contract update transaction.
+ ///
+ public static async Task Main(string[] args) =>
+ await Parser.Default
+ .ParseArguments(args)
+ .WithParsedAsync(Run);
+
+ /// Module reference for a module containing the 'cis2_multi' smart contract example
+ private static readonly string _cis2MultiModuleRef =
+ "755d0e1f2820a3285e23ac4fa1862ae2dfa75ab8927133904e04fea7e9f1f4c9";
+
+ /// Receive name for the 'mint' entrypoint of the contract.
+ private static readonly string _cis2MultiReceiveMint = "cis2_multi.mint";
+
+ private static async Task Run(Options options)
+ {
+ // Read the account keys from a file.
+ var walletData = File.ReadAllText(options.WalletKeysFile);
+ var account = WalletAccount.FromWalletKeyExportFormat(walletData);
+
+ // Construct the client.
+ var clientOptions = new ConcordiumClientOptions
+ {
+ Timeout = TimeSpan.FromSeconds(10)
+ };
+ using var client = new ConcordiumClient(new Uri(options.Endpoint), clientOptions);
+
+ // Create the update transaction.
+ var amount = CcdAmount.Zero;
+ var contract = ContractAddress.From(options.Contract, 0);
+
+ // Fetch the module source from chain, to extract the embedded schema.
+ var moduleReference = new ModuleReference(_cis2MultiModuleRef);
+ var moduleSourceResponse = await client.GetModuleSourceAsync(new LastFinal(), moduleReference);
+ var moduleSchema = moduleSourceResponse.Response.GetModuleSchema()!;
+
+ var receiveName = ReceiveName.Parse(_cis2MultiReceiveMint);
+
+ // Construct the mint parameter, this parameter class is tied to details of the smart
+ // contract itself.
+ var parameter = new MintParameter
+ {
+ Owner = new JsonAccountAddr
+ {
+ Account = new[] { account.AccountAddress.ToString() }
+ },
+ MetadataUrl = new JsonMetadataUrl
+ {
+ Url = options.MetadataUrl,
+ ContentHash = new JsonNoHash
+ {
+ None = Array.Empty()
+ },
+ },
+ TokenId = options.TokenId
+ };
+ var jsonString = JsonSerializer.Serialize(parameter, new JsonSerializerOptions { WriteIndented = true });
+ Console.WriteLine($"Mint using the JSON parameter:\n{jsonString}");
+ var jsonParameter = new Utf8Json(JsonSerializer.SerializeToUtf8Bytes(parameter));
+
+ // Convert the JSON parameter into the byte format expected by the smart contract using the
+ // information in the smart contract module schema.
+ var parameterBytes = Parameter.UpdateJson(
+ moduleSchema,
+ receiveName.GetContractName(),
+ receiveName.GetEntrypoint(),
+ jsonParameter
+ );
+
+
+ // Prepare the transaction for signing.
+ var updatePayload = new Concordium.Sdk.Transactions.UpdateContract(
+ amount,
+ contract,
+ receiveName,
+ parameterBytes
+ );
+ var sender = account.AccountAddress;
+ var sequenceNumber = client.GetNextAccountSequenceNumber(sender).Item1;
+ var expiry = Expiry.AtMinutesFromNow(30);
+ var maxEnergy = new EnergyAmount(options.MaxEnergy);
+ var preparedPayload = updatePayload.Prepare(sender, sequenceNumber, expiry, maxEnergy);
+
+ // Sign the transaction using the account keys.
+ var signedTransaction = preparedPayload.Sign(account);
+
+ // Submit the transaction.
+ var txHash = await client.SendAccountTransactionAsync(signedTransaction);
+ Console.WriteLine($"Successfully submitted transfer transaction with hash {txHash}");
+
+ // Watch the status of the transaction, until it becomes finalized in a block.
+ Console.WriteLine($"Waiting for the transaction to finalize...");
+ var finalized = await client.WaitUntilFinalized(txHash);
+
+ Console.WriteLine($"Finalized in block with hash {finalized.BlockHash}");
+
+ // Check whether the transaction was rejected and if so exit.
+ if (finalized.Summary.TryGetRejectedAccountTransaction(out var reason))
+ {
+ Console.WriteLine($"Transaction got rejected:\n{reason}");
+ return;
+ }
+ Console.WriteLine($"Transaction got accepted!");
+
+ // Exact the logs of updated smart contract instances from the outcome.
+ if (!finalized.Summary.TryGetContractUpdateLogs(out var updates))
+ {
+ throw new InvalidOperationException(
+ "Transaction summary failed to parse as a contract update transaction."
+ );
+ }
+ // Print out the events from each updated contract in the block.
+ foreach (var update in updates)
+ {
+ var updatedContract = update.Item1;
+ Console.WriteLine($"Contract {updatedContract} logged:");
+ // If this is our contract, then we use the module schema to parse the events.
+ if (updatedContract == contract)
+ {
+ foreach (var evt in update.Item2)
+ {
+ // Use the smart contract module schema to deserialize the events.
+ var json = evt.GetDeserializeEvent(moduleSchema, receiveName.GetContractName());
+ Console.WriteLine($"- {json}");
+ }
+ }
+ else
+ {
+ // For other contracts we just log the event bytes as a hex encoded string.
+ foreach (var evt in update.Item2)
+ {
+ Console.WriteLine($"- {evt.ToHexString()}");
+ }
+ }
+ }
+ }
+
+ ///
+ /// Class for constructing the JSON representation of the smart contract parameter for 'mint'
+ /// entrypoint of 'cis2_multi' contract.
+ /// The exact structure will depend on the details of the smart contract module.
+ ///
+ private class MintParameter
+ {
+ ///
+ /// The owner for the newly minted tokens.
+ ///
+ /// This property only represents the owner being an account address, but the smart contract
+ /// also allow for a contract address here, which we choose not include in the type for
+ /// simplicity.
+ ///
+ [JsonPropertyName("owner")]
+ public JsonAccountAddr Owner { get; set; }
+ ///
+ /// The metadata url for a newly minted token type.
+ ///
+ [JsonPropertyName("metadata_url")]
+ public JsonMetadataUrl MetadataUrl { get; set; }
+ ///
+ /// The TokenID identifying the token type to mint.
+ ///
+ [JsonPropertyName("token_id")]
+ public string TokenId { get; set; }
+ }
+
+ ///
+ /// Indicator class for some address being an account address.
+ /// Note this only represents a single account address, but still contains an array of strings
+ /// for the JSON format to match.
+ ///
+ private class JsonAccountAddr
+ {
+ public string[] Account { get; set; }
+ }
+
+ ///
+ /// Class for constructing the JSON representation of the token metadata URL.
+ ///
+ private class JsonMetadataUrl
+ {
+ ///
+ /// The URL pointing to the token metadata.
+ ///
+ [JsonPropertyName("url")]
+ public string Url { get; set; }
+ ///
+ /// The hash of the token metadata.
+ ///
+ /// This property only supports not providing a content hash of the metadata, but the smart
+ /// contract supports this as well, we choose not include this in the type for simplicity.
+ ///
+ [JsonPropertyName("hash")]
+ public JsonNoHash ContentHash { get; set; }
+ }
+
+ ///
+ /// Indicator of no checksum (SHA256 hash) provided for the token metadata.
+ /// Note this still contains an array of int for the JSON format to match.
+ ///
+ private class JsonNoHash
+ {
+ public int[] None { get; set; }
+ }
+}
diff --git a/examples/UpdateContractMint/Transations.UpdateContractMint.csproj b/examples/UpdateContractMint/Transations.UpdateContractMint.csproj
new file mode 100644
index 00000000..74addacb
--- /dev/null
+++ b/examples/UpdateContractMint/Transations.UpdateContractMint.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
diff --git a/rust-bindings/src/lib.rs b/rust-bindings/src/lib.rs
index 016c0ace..c7cd1972 100644
--- a/rust-bindings/src/lib.rs
+++ b/rust-bindings/src/lib.rs
@@ -1,8 +1,8 @@
use anyhow::Result;
use concordium_contracts_common::{
+ from_bytes,
schema::{Type, VersionedModuleSchema, VersionedSchemaError},
- schema_json::ToJsonError,
- Cursor,
+ schema_json, Cursor,
};
use serde_json::to_vec;
use std::{ffi::CStr, os::raw::c_char};
@@ -149,6 +149,136 @@ pub unsafe extern "C" fn get_event_contract(
})
}
+/// Construct smart contract receive parameter from a JSON string and a smart
+/// contract schema.
+///
+/// # Arguments
+///
+/// * 'schema_ptr' - Pointer to smart contract module schema.
+/// * 'schema_size' - The byte size of the smart contract module schema.
+/// * 'schema_version' - Version of the smart contract module schema (Optional).
+/// * 'contract_name' - Contract name provided as a null terminated string.
+/// * 'function_name' - Receive function name provided as a null terminated
+/// string.
+/// * 'json_ptr' - Pointer to the UTF8 encoded JSON parameter.
+/// * 'json_size' - The byte size of the encoded JSON parameter.
+/// * 'callback' - Callback which can be used to set resulting output
+///
+/// # Returns
+///
+/// 0 if the call succeeded otherwise the return value corresponds to some error
+/// code.
+///
+/// # Safety
+///
+/// Every pointer provided as an argument is assumed to be alive for the
+/// duration of the call.
+#[no_mangle]
+pub unsafe extern "C" fn into_receive_parameter(
+ schema_ptr: *const u8,
+ schema_size: i32,
+ schema_version: FFIByteOption,
+ contract_name: *const c_char,
+ function_name: *const c_char,
+ json_ptr: *const u8,
+ json_size: i32,
+ callback: ResultCallback,
+) -> u16 {
+ assign_result(callback, || {
+ let schema = std::slice::from_raw_parts(schema_ptr, schema_size as usize);
+ let contract_name_str = get_str_from_pointer(contract_name)?;
+ let function_name_str = get_str_from_pointer(function_name)?;
+ let json_slice = std::slice::from_raw_parts(json_ptr, json_size as usize);
+ let module_schema = VersionedModuleSchema::new(schema, &schema_version.into_option())?;
+ let parameter_schema_type =
+ module_schema.get_receive_param_schema(contract_name_str, function_name_str)?;
+ let json_value: serde_json::Value = serde_json::from_slice(json_slice)?;
+ Ok(parameter_schema_type.serial_value(&json_value)?)
+ })
+}
+
+/// Construct smart contract init parameter from a JSON string and a smart
+/// contract schema.
+///
+/// # Arguments
+///
+/// * 'schema_ptr' - Pointer to smart contract module schema.
+/// * 'schema_size' - The byte size of the smart contract module schema.
+/// * 'schema_version' - Version of the smart contract module schema (Optional).
+/// * 'contract_name' - Contract name provided as a null terminated string.
+/// * 'json_ptr' - Pointer to the UTF8 encoded JSON parameter.
+/// * 'json_size' - The byte size of the encoded JSON parameter.
+/// * 'callback' - Callback which can be used to set resulting output
+///
+/// # Returns
+///
+/// 0 if the call succeeded otherwise the return value corresponds to some error
+/// code.
+///
+/// # Safety
+///
+/// Every pointer provided as an argument is assumed to be alive for the
+/// duration of the call.
+#[no_mangle]
+pub unsafe extern "C" fn into_init_parameter(
+ schema_ptr: *const u8,
+ schema_size: i32,
+ schema_version: FFIByteOption,
+ contract_name: *const c_char,
+ json_ptr: *const u8,
+ json_size: i32,
+ callback: ResultCallback,
+) -> u16 {
+ assign_result(callback, || {
+ let schema = std::slice::from_raw_parts(schema_ptr, schema_size as usize);
+ let contract_name_str = get_str_from_pointer(contract_name)?;
+ let json_slice = std::slice::from_raw_parts(json_ptr, json_size as usize);
+ let module_schema = VersionedModuleSchema::new(schema, &schema_version.into_option())?;
+ let parameter_schema_type = module_schema.get_init_param_schema(contract_name_str)?;
+ let json_value: serde_json::Value = serde_json::from_slice(json_slice)?;
+ Ok(parameter_schema_type.serial_value(&json_value)?)
+ })
+}
+
+/// Convert some JSON representation into bytes using a smart contract schema
+/// type.
+///
+/// # Arguments
+///
+/// * 'schema_type_ptr' - Pointer to smart contract schema type.
+/// * 'schema_type_size' - The byte size of the smart contract schema type.
+/// * 'json_ptr' - Pointer to the UTF8 encoded JSON parameter.
+/// * 'json_size' - The byte size of the encoded JSON parameter.
+/// * 'callback' - Callback which can be used to set resulting output
+///
+/// # Returns
+///
+/// 0 if the call succeeded otherwise the return value corresponds to some error
+/// code.
+///
+/// # Safety
+///
+/// Every pointer provided as an argument is assumed to be alive for the
+/// duration of the call.
+#[no_mangle]
+pub unsafe extern "C" fn schema_json_to_bytes(
+ schema_type_ptr: *const u8,
+ schema_type_size: i32,
+ json_ptr: *const u8,
+ json_size: i32,
+ callback: ResultCallback,
+) -> u16 {
+ assign_result(callback, || {
+ let schema_type_bytes =
+ std::slice::from_raw_parts(schema_type_ptr, schema_type_size as usize);
+ let json_slice = std::slice::from_raw_parts(json_ptr, json_size as usize);
+ let parameter_schema_type: Type =
+ from_bytes(&schema_type_bytes).map_err(|_| FFIError::ParseSchemaType)?;
+ let json_value: serde_json::Value = serde_json::from_slice(json_slice)?;
+ Ok(parameter_schema_type.serial_value(&json_value)?)
+ })
+}
+
/// Compute result using the provided callback f, convert it into a C string and
/// assign it to the provided target.
///
@@ -169,7 +299,7 @@ fn assign_result Result, FFIError>>(callback: ResultCallb
0
}
Err(e) => {
- let error = format!("{}", e).into_bytes();
+ let error = e.to_string().into_bytes();
let error_length = error.len() as i32;
let ptr = error.as_ptr();
callback(ptr, error_length);
@@ -178,7 +308,7 @@ fn assign_result Result, FFIError>>(callback: ResultCallb
}
}
-pub fn get_receive_contract_parameter_aux(
+fn get_receive_contract_parameter_aux(
schema: &[u8],
schema_version: Option,
contract_name: &str,
@@ -197,15 +327,19 @@ fn schema_display_aux(schema: &[u8], schema_version: Option) -> Result u16 {
match self {
- FFIError::JsonError(_) => 1,
- FFIError::SerdeJsonError => 2,
- FFIError::Utf8Error => 3,
+ FFIError::ToJsonError(_) => 1,
+ FFIError::SerdeJsonError(_) => 2,
+ FFIError::Utf8Error(_) => 3,
FFIError::VersionedSchemaError(schema_error) => match schema_error {
VersionedSchemaError::ParseError => 4,
VersionedSchemaError::MissingSchemaVersion => 5,
@@ -233,22 +367,12 @@ impl FFIError {
VersionedSchemaError::NoEventInContract => 17,
VersionedSchemaError::EventNotSupported => 18,
},
+ FFIError::FromJsonError(_) => 19,
+ FFIError::ParseSchemaType => 20,
}
}
}
-impl From for FFIError {
- fn from(_: std::str::Utf8Error) -> Self { FFIError::Utf8Error }
-}
-
-impl From for FFIError {
- fn from(_: serde_json::Error) -> Self { FFIError::SerdeJsonError }
-}
-
-impl From for FFIError {
- fn from(value: ToJsonError) -> Self { FFIError::JsonError(value.display(true)) }
-}
-
fn get_event_contract_aux(
schema: &[u8],
schema_version: Option,
@@ -266,6 +390,7 @@ fn deserialize_type_value(value: &[u8], value_type: &Type) -> Result, FF
let v = value_type.to_json(&mut cursor)?;
Ok(to_vec(&v)?)
}
+
/// The provided raw pointer [`c_char`] must be a [`std::ffi::CString`].
/// The content of the pointer [`c_char`] must not be mutated for the duration
/// of lifetime 'a.
diff --git a/src/Client/ConcordiumClient.cs b/src/Client/ConcordiumClient.cs
index dfd249f7..f8e5ccc6 100644
--- a/src/Client/ConcordiumClient.cs
+++ b/src/Client/ConcordiumClient.cs
@@ -205,6 +205,59 @@ public async Task GetBlockItemStatusAsync(TransactionHash tr
return TransactionStatusFactory.CreateTransactionStatus(blockItemStatus);
}
+ ///
+ /// Wait until the transaction is finalized.
+ ///
+ /// For production it is recommended to provide a with a timeout,
+ /// since a faulty/misbehaving node could otherwise make this wait indefinitely.
+ ///
+ /// The with of NotFound is thrown
+ /// if the transaction is not known by the node. Sending a transaction right before calling this
+ /// method, might produce this exception, due to the node still processing the transaction.
+ ///
+ /// Transaction Hash which is included in blocks returned.
+ /// Cancellation token
+ /// Hash of the block finalizing the transaction and the block summary of the transaction.
+ /// RPC error occurred, access for more information.
+ public async Task<(BlockHash BlockHash, BlockItemSummary Summary)> WaitUntilFinalized(
+ TransactionHash transactionHash,
+ CancellationToken token = default
+ )
+ {
+ static bool isFinalized(ITransactionStatus status, out (BlockHash, BlockItemSummary)? output)
+ {
+ switch (status)
+ {
+ case TransactionStatusFinalized finalized:
+ output = finalized.State;
+ return true;
+ default:
+ output = null;
+ return false;
+ };
+ }
+
+ // Check the transaction status straight away and return if finalized already.
+ var status = await this.GetBlockItemStatusAsync(transactionHash, token);
+ if (isFinalized(status, out var finalized))
+ {
+ return finalized!.Value;
+ }
+
+ // Otherwise listen for new finalized blocks and recheck the status.
+ var blocks = this.GetFinalizedBlocks(token);
+ await foreach (var block in blocks)
+ {
+ var nowStatus = await this.GetBlockItemStatusAsync(transactionHash, token);
+ if (isFinalized(nowStatus, out var nowFinalized))
+ {
+ return nowFinalized!.Value;
+ }
+ }
+ // The above loop only exits when the block is finalized.
+ throw new InvalidOperationException("Unreachable code");
+ }
+
///
/// Get the information for the given account in the given block.
///
diff --git a/src/Interop/InteropBinding.cs b/src/Interop/InteropBinding.cs
index 54f02d39..9a931c06 100644
--- a/src/Interop/InteropBinding.cs
+++ b/src/Interop/InteropBinding.cs
@@ -41,6 +41,35 @@ private static extern SchemaJsonResult GetEventContract(
int value_size,
[MarshalAs(UnmanagedType.FunctionPtr)] SetResultCallback callback);
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "into_receive_parameter")]
+ private static extern SchemaJsonResult IntoReceiveParameter(
+ [MarshalAs(UnmanagedType.LPArray)] byte[] schema,
+ int schema_size,
+ FfiByteOption schema_version,
+ [MarshalAs(UnmanagedType.LPUTF8Str)] string contract_name,
+ [MarshalAs(UnmanagedType.LPUTF8Str)] string function_name,
+ [MarshalAs(UnmanagedType.LPArray)] byte[] json_ptr,
+ int json_size,
+ [MarshalAs(UnmanagedType.FunctionPtr)] SetResultCallback callback);
+
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "into_init_parameter")]
+ private static extern SchemaJsonResult IntoInitParameter(
+ [MarshalAs(UnmanagedType.LPArray)] byte[] schema,
+ int schema_size,
+ FfiByteOption schema_version,
+ [MarshalAs(UnmanagedType.LPUTF8Str)] string contract_name,
+ [MarshalAs(UnmanagedType.LPArray)] byte[] json_ptr,
+ int json_size,
+ [MarshalAs(UnmanagedType.FunctionPtr)] SetResultCallback callback);
+
+ [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, EntryPoint = "schema_json_to_bytes")]
+ private static extern SchemaJsonResult SchemaJsonToBytes(
+ [MarshalAs(UnmanagedType.LPArray)] byte[] schema_type,
+ int schema_type_size,
+ [MarshalAs(UnmanagedType.LPArray)] byte[] json_ptr,
+ int json_size,
+ [MarshalAs(UnmanagedType.FunctionPtr)] SetResultCallback callback);
+
///
/// Callback to set byte array result of interop call.
///
@@ -136,6 +165,118 @@ internal static Utf8Json GetEventContract(VersionedModuleSchema schema, Contract
throw interopException;
}
+ ///
+ /// Contruct a smart contract receive parameter from a JSON string and the module schema.
+ ///
+ /// Smart contract module schema
+ /// Name of the smart contract
+ /// Entrypoint of the contract to construct the parameter for
+ /// JSON representation of the smart contract parameter
+ /// Smart contract parameter
+ internal static Parameter IntoReceiveParameter(
+ VersionedModuleSchema schema,
+ ContractIdentifier contractName,
+ EntryPoint functionName,
+ Utf8Json json
+ )
+ {
+ var ffiOption = FfiByteOption.Create(schema.Version);
+ var result = Array.Empty();
+
+ var statusCode = IntoReceiveParameter(
+ schema.Schema,
+ schema.Schema.Length,
+ ffiOption,
+ contractName.ContractName,
+ functionName.Name,
+ json.Bytes,
+ json.Bytes.Length,
+ (ptr, size) =>
+ {
+ result = new byte[size];
+ Marshal.Copy(ptr, result, 0, size);
+ });
+
+ if (!statusCode.IsError() && result != null)
+ {
+ return new Parameter(result);
+ }
+
+ var interopException = SchemaJsonException.Create(statusCode, result);
+ throw interopException;
+ }
+
+ ///
+ /// Contruct a smart contract init parameter from a JSON string and the module schema.
+ ///
+ /// Smart contract module schema
+ /// Name of the smart contract
+ /// JSON representation of the smart contract parameter
+ /// Smart contract parameter
+ internal static Parameter IntoInitParameter(
+ VersionedModuleSchema schema,
+ ContractIdentifier contractName,
+ Utf8Json json
+ )
+ {
+ var ffiOption = FfiByteOption.Create(schema.Version);
+ var result = Array.Empty();
+
+ var statusCode = IntoInitParameter(
+ schema.Schema,
+ schema.Schema.Length,
+ ffiOption,
+ contractName.ContractName,
+ json.Bytes,
+ json.Bytes.Length,
+ (ptr, size) =>
+ {
+ result = new byte[size];
+ Marshal.Copy(ptr, result, 0, size);
+ });
+
+ if (!statusCode.IsError() && result != null)
+ {
+ return new Parameter(result);
+ }
+
+ var interopException = SchemaJsonException.Create(statusCode, result);
+ throw interopException;
+ }
+
+ ///
+ /// Contruct a smart contract init parameter from a JSON string and the module schema.
+ ///
+ /// Smart contract schema type
+ /// JSON representation of the smart contract parameter
+ /// Smart contract parameter as bytes
+ internal static byte[] SchemaJsonToBytes(
+ SchemaType schemaType,
+ Utf8Json json
+ )
+ {
+ var result = Array.Empty();
+
+ var statusCode = SchemaJsonToBytes(
+ schemaType.Type,
+ schemaType.Type.Length,
+ json.Bytes,
+ json.Bytes.Length,
+ (ptr, size) =>
+ {
+ result = new byte[size];
+ Marshal.Copy(ptr, result, 0, size);
+ });
+
+ if (!statusCode.IsError() && result != null)
+ {
+ return result;
+ }
+
+ var interopException = SchemaJsonException.Create(statusCode, result);
+ throw interopException;
+ }
+
///
/// A C# layout which compiled to a C interpretable structure. This is used as an optional parameter.
///
diff --git a/src/Interop/SchemaJsonResult.cs b/src/Interop/SchemaJsonResult.cs
index 1a399b6d..85961218 100644
--- a/src/Interop/SchemaJsonResult.cs
+++ b/src/Interop/SchemaJsonResult.cs
@@ -3,14 +3,14 @@ namespace Concordium.Sdk.Interop;
///
/// Result type which on errors hold error type information.
///
-public enum SchemaJsonResult
+public enum SchemaJsonResult : ushort
{
///
/// No error
///
NoError = 0,
///
- /// Represents errors occurring while deserializing to the schema JSON format.
+ /// Represents errors occurring while converting to the schema JSON format.
///
JsonError = 1,
///
@@ -83,6 +83,14 @@ public enum SchemaJsonResult
/// Versioned Schema Error - Events not supported for this module version
///
VersionedSchemaErrorEventNotSupported = 18,
+ ///
+ /// Represents errors occurring while converting from the schema JSON format.
+ ///
+ FromJsonError = 19,
+ ///
+ /// Represents errors occurring parsing a smart contract schema type.
+ ///
+ ParseSchemaType = 20
}
internal static class ErrorExtensions
diff --git a/src/Types/ContractEvent.cs b/src/Types/ContractEvent.cs
index dfdac385..448330fc 100644
--- a/src/Types/ContractEvent.cs
+++ b/src/Types/ContractEvent.cs
@@ -14,7 +14,7 @@ public sealed record ContractEvent(byte[] Bytes)
public string ToHexString() => Convert.ToHexString(this.Bytes).ToLowerInvariant();
///
- /// Deserialize event from .
+ /// Deserialize event from .
///
/// Module schema in hexadecimal.
/// Contract name.
diff --git a/src/Types/Parameter.cs b/src/Types/Parameter.cs
index 15f193be..66c3fd64 100644
--- a/src/Types/Parameter.cs
+++ b/src/Types/Parameter.cs
@@ -33,7 +33,7 @@ public sealed record Parameter(byte[] Param) : IEquatable
///
/// Copies the parameters to a byte array which has the length preprended.
///
- public byte[] ToBytes()
+ public byte[] ToBytes()
{
using var memoryStream = new MemoryStream((int)this.SerializedLength());
memoryStream.Write(Serialization.ToBytes((ushort)this.Param.Length));
@@ -41,6 +41,42 @@ public byte[] ToBytes()
return memoryStream.ToArray();
}
+ ///
+ /// Create a parameter from JSON representation using the smart contract module schema for a smart contract update transaction.
+ ///
+ /// The smart contract module schema.
+ /// The name of the contract.
+ /// The name of entrypoint of the smart contract.
+ /// The UTF8 encoding of the JSON representation of the smart contract parameter.
+ public static Parameter UpdateJson(
+ VersionedModuleSchema moduleSchema,
+ ContractIdentifier contractName,
+ EntryPoint functionName,
+ Utf8Json jsonParameter
+ ) => Interop.InteropBinding.IntoReceiveParameter(moduleSchema, contractName, functionName, jsonParameter);
+
+ ///
+ /// Create a parameter from JSON representation using the smart contract module schema for a smart contract init transaction.
+ ///
+ /// The smart contract module schema.
+ /// The name of the contract.
+ /// The UTF8 encoding of the JSON representation of the smart contract parameter.
+ public static Parameter InitJson(
+ VersionedModuleSchema moduleSchema,
+ ContractIdentifier contractName,
+ Utf8Json jsonParameter
+ ) => Interop.InteropBinding.IntoInitParameter(moduleSchema, contractName, jsonParameter);
+
+ ///
+ /// Create a parameter from JSON representation using the smart contract schema type.
+ ///
+ /// The smart contract schema type for the parameter.
+ /// The UTF8 encoding of the JSON representation of the smart contract parameter.
+ public static Parameter FromJson(
+ SchemaType schemaType,
+ Utf8Json jsonParameter
+ ) => new(Interop.InteropBinding.SchemaJsonToBytes(schemaType, jsonParameter));
+
///
/// Create a parameter from a byte array.
///
diff --git a/src/Types/SchemaType.cs b/src/Types/SchemaType.cs
new file mode 100644
index 00000000..7f99fa97
--- /dev/null
+++ b/src/Types/SchemaType.cs
@@ -0,0 +1,39 @@
+namespace Concordium.Sdk.Types;
+
+///
+/// Smart contract schema type.
+/// This represents a single type as part of a smart contract module schema, and allows for
+/// converting structure data, such as JSON, from and to the binary representation used by a
+/// smart contract.
+///
+public sealed record SchemaType(byte[] Type) : IEquatable
+{
+ /// Construct SchemaType from a HEX encoding.
+ public static SchemaType FromHexString(string hexString)
+ {
+ var value = Convert.FromHexString(hexString);
+ return new(value);
+ }
+
+ /// Construct SchemaType from a base64 encoding.
+ public static SchemaType FromBase64String(string base64)
+ {
+ var value = Convert.FromBase64String(base64);
+ return new(value);
+ }
+
+ /// Check for equality.
+ public bool Equals(SchemaType? other) => other != null && this.Type.SequenceEqual(other.Type);
+
+ /// Gets hash code.
+ public override int GetHashCode()
+ {
+ var paramHash = Helpers.HashCode.GetHashCodeByteArray(this.Type);
+ return paramHash;
+ }
+
+ ///
+ /// Convert schema type to hex string.
+ ///
+ public string ToHexString() => Convert.ToHexString(this.Type).ToLowerInvariant();
+}
diff --git a/src/Types/VersionedModuleSchema.cs b/src/Types/VersionedModuleSchema.cs
index 767cd8cf..dfe1eafa 100644
--- a/src/Types/VersionedModuleSchema.cs
+++ b/src/Types/VersionedModuleSchema.cs
@@ -11,7 +11,7 @@ namespace Concordium.Sdk.Types;
public sealed record VersionedModuleSchema(byte[] Schema, ModuleSchemaVersion Version)
{
///
- /// Constructor which converts into hexadecimal string.
+ /// Constructor which converts into hexadecimal string.
///
/// Module schema given as an hexadecimal string.
/// Module schema version.
diff --git a/src/Types/VersionedModuleSource.cs b/src/Types/VersionedModuleSource.cs
index da2c29db..e4d980cf 100644
--- a/src/Types/VersionedModuleSource.cs
+++ b/src/Types/VersionedModuleSource.cs
@@ -90,7 +90,7 @@ private protected abstract (byte[]? Schema, ModuleSchemaVersion SchemaVersion)?
ExtractSchemaFromWebAssemblyModule(Module module);
///
- /// From custom sections in get entry with name .
+ /// From custom sections in get entry with name .
///
/// Fails if multiple entries exist with the same name.
///
diff --git a/tests/UnitTests/Interop/InteropBindingTests.cs b/tests/UnitTests/Interop/InteropBindingTests.cs
index 492b719e..ec5ebc6a 100644
--- a/tests/UnitTests/Interop/InteropBindingTests.cs
+++ b/tests/UnitTests/Interop/InteropBindingTests.cs
@@ -256,6 +256,63 @@ public async Task GivenBadContractEvent_WhenDisplayEvent_ThenThrowException()
e.Message.StartsWith("Failed to deserialize AccountAddress due to: Could not parse"));
}
+ [Fact]
+ public async Task WhenIntoReceiveParamFromJson_ThenReturnParams()
+ {
+ // Arrange
+ var schema = Convert.FromHexString((await File.ReadAllTextAsync("./Data/cis2_wCCD_sub")).Trim());
+ const string contractName = "cis2_wCCD";
+ const string entrypoint = "wrap";
+ var versionedModuleSchema = new VersionedModuleSchema(schema, ModuleSchemaVersion.Undefined);
+ var json = new Utf8Json(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(new
+ {
+ to = new
+ {
+ Account = new string[] {
+ "4tUoKeVapaTwwi2yY3Vwe5auM65VL2vk31R3eVhTW94hnB159F"
+ },
+ },
+ data = ""
+ }));
+ var contractIdentifier = new ContractIdentifier(contractName);
+ var entryPoint = new EntryPoint(entrypoint);
+
+ // Act
+ var parameter = InteropBinding.IntoReceiveParameter(versionedModuleSchema, contractIdentifier, entryPoint, json);
+
+ // Assert
+ await Verifier.Verify(parameter.ToHexString())
+ .UseFileName("receive-params-hex-from-json")
+ .UseDirectory("__snapshots__");
+ }
+
+ [Fact]
+ public async Task WhenParameterFromJson_ThenReturnBytes()
+ {
+ // Arrange
+ var wCcdWrapSchemaType = SchemaType.FromBase64String("FAACAAAAAgAAAHRvFQIAAAAHAAAAQWNjb3VudAEBAAAACwgAAABDb250cmFjdAECAAAADBYBBAAAAGRhdGEdAQ==");
+ var json = new Utf8Json(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(new
+ {
+ to = new
+ {
+ Account = new string[] {
+ "4tUoKeVapaTwwi2yY3Vwe5auM65VL2vk31R3eVhTW94hnB159F"
+ },
+ },
+ data = ""
+ }));
+
+ // Act
+ var bytes = InteropBinding.SchemaJsonToBytes(wCcdWrapSchemaType, json);
+
+ // Assert
+ await Verifier.Verify(Convert.ToHexString(bytes))
+ .UseFileName("wCCD-wrap-param-hex-from-json")
+ .UseDirectory("__snapshots__");
+ }
+
+
+
[Theory]
[InlineData(ModuleSchemaVersion.V0, (byte)0)]
[InlineData(ModuleSchemaVersion.V2, (byte)2)]
diff --git a/tests/UnitTests/Interop/__snapshots__/receive-params-hex-from-json.verified.txt b/tests/UnitTests/Interop/__snapshots__/receive-params-hex-from-json.verified.txt
new file mode 100644
index 00000000..e6f53346
--- /dev/null
+++ b/tests/UnitTests/Interop/__snapshots__/receive-params-hex-from-json.verified.txt
@@ -0,0 +1 @@
+00fffa722d840687699743e5f1a1ad86113d0404115661ab09a3611bffc1bdaabe0000
diff --git a/tests/UnitTests/Interop/__snapshots__/wCCD-wrap-param-hex-from-json.verified.txt b/tests/UnitTests/Interop/__snapshots__/wCCD-wrap-param-hex-from-json.verified.txt
new file mode 100644
index 00000000..b17a1fab
--- /dev/null
+++ b/tests/UnitTests/Interop/__snapshots__/wCCD-wrap-param-hex-from-json.verified.txt
@@ -0,0 +1 @@
+00FFFA722D840687699743E5F1A1AD86113D0404115661AB09A3611BFFC1BDAABE0000
\ No newline at end of file