Skip to content

Commit

Permalink
Add SchemaType and method for constructing parameters using JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
limemloh committed Feb 9, 2024
1 parent 1715518 commit 6606014
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- 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
Expand Down
43 changes: 43 additions & 0 deletions rust-bindings/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::Result;
use concordium_contracts_common::{
from_bytes,
schema::{Type, VersionedModuleSchema, VersionedSchemaError},
schema_json, Cursor,
};
Expand Down Expand Up @@ -239,6 +240,45 @@ pub unsafe extern "C" fn into_init_parameter(
})
}

/// 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.
///
Expand Down Expand Up @@ -298,6 +338,8 @@ enum FFIError {
VersionedSchemaError(#[from] VersionedSchemaError),
#[error(transparent)]
FromJsonError(#[from] schema_json::JsonError),
#[error("error parsing the schema type")]
ParseSchemaType,
}

impl FFIError {
Expand Down Expand Up @@ -326,6 +368,7 @@ impl FFIError {
VersionedSchemaError::EventNotSupported => 18,
},
FFIError::FromJsonError(_) => 19,
FFIError::ParseSchemaType => 20,
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/Client/ConcordiumClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ public async Task<ITransactionStatus> GetBlockItemStatusAsync(TransactionHash tr
///
/// For production it is recommended to provide a <see cref="CancellationToken"/> with a timeout,
/// since a faulty/misbehaving node could otherwise make this wait indefinitely.
///
/// The <see cref="RpcException"/> with <see cref="RpcException.StatusCode"/> 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.
/// </summary>
/// <param name="transactionHash">Transaction Hash which is included in blocks returned.</param>
/// <param name="token">Cancellation token</param>
Expand Down
41 changes: 41 additions & 0 deletions src/Interop/InteropBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ private static extern SchemaJsonResult IntoInitParameter(
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);

/// <summary>
/// Callback to set byte array result of interop call.
/// </summary>
Expand Down Expand Up @@ -236,6 +244,39 @@ Utf8Json json
throw interopException;
}

/// <summary>
/// Contruct a smart contract init parameter from a JSON string and the module schema.
/// </summary>
/// <param name="schemaType">Smart contract schema type</param>
/// <param name="json">JSON representation of the smart contract parameter</param>
/// <returns>Smart contract parameter as bytes</returns>
internal static byte[] SchemaJsonToBytes(
SchemaType schemaType,
Utf8Json json
)
{
var result = Array.Empty<byte>();

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;
}

/// <summary>
/// A C# layout which compiled to a C interpretable structure. This is used as an optional parameter.
/// </summary>
Expand Down
6 changes: 5 additions & 1 deletion src/Interop/SchemaJsonResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ public enum SchemaJsonResult : ushort
/// <summary>
/// Represents errors occurring while converting from the schema JSON format.
/// </summary>
FromJsonError = 19
FromJsonError = 19,
/// <summary>
/// Represents errors occurring parsing a smart contract schema type.
/// </summary>
ParseSchemaType = 20
}

internal static class ErrorExtensions
Expand Down
9 changes: 9 additions & 0 deletions src/Types/Parameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ public static Parameter InitJson(
Utf8Json jsonParameter
) => Interop.InteropBinding.IntoInitParameter(moduleSchema, contractName, jsonParameter);

/// <summary>
/// Create a parameter from JSON representation using the smart contract schema type.
/// </summary>
/// <param name="schemaType">The smart contract schema type for the parameter.</param>
/// <param name="jsonParameter">The UTF8 encoding of the JSON representation of the smart contract parameter.</param>
public static Parameter FromJson(
SchemaType schemaType,
Utf8Json jsonParameter
) => new(Interop.InteropBinding.SchemaJsonToBytes(schemaType, jsonParameter));

/// <summary>
/// Create a parameter from a byte array.
Expand Down
39 changes: 39 additions & 0 deletions src/Types/SchemaType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
namespace Concordium.Sdk.Types;

/// <summary>
/// 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.
/// </summary>
public sealed record SchemaType(byte[] Type) : IEquatable<SchemaType>
{
/// <summary>Construct SchemaType from a HEX encoding.</summary>
public static SchemaType FromHexString(string hexString)
{
var value = Convert.FromHexString(hexString);
return new(value);
}

/// <summary>Construct SchemaType from a base64 encoding.</summary>
public static SchemaType FromBase64String(string base64)
{
var value = Convert.FromBase64String(base64);
return new(value);
}

/// <summary>Check for equality.</summary>
public bool Equals(SchemaType? other) => other != null && this.Type.SequenceEqual(other.Type);

/// <summary>Gets hash code.</summary>
public override int GetHashCode()
{
var paramHash = Helpers.HashCode.GetHashCodeByteArray(this.Type);
return paramHash;
}

/// <summary>
/// Convert schema type to hex string.
/// </summary>
public string ToHexString() => Convert.ToHexString(this.Type).ToLowerInvariant();
}
28 changes: 27 additions & 1 deletion tests/UnitTests/Interop/InteropBindingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public async Task WhenIntoReceiveParamFromJson_ThenReturnParams()
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); // Bad schema
var versionedModuleSchema = new VersionedModuleSchema(schema, ModuleSchemaVersion.Undefined);
var json = new Utf8Json(System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(new
{
to = new
Expand All @@ -286,6 +286,32 @@ await Verifier.Verify(parameter.ToHexString())
.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)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
00FFFA722D840687699743E5F1A1AD86113D0404115661AB09A3611BFFC1BDAABE0000

0 comments on commit 6606014

Please sign in to comment.