Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs Update #307

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions core/wasm/src/wasm_wrappers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,13 @@ macro_rules! impl_wasm_cbor_api {
cml_core::serialization::Serialize::to_cbor_bytes(&self.0)
}

/**
* Serialize this type to CBOR bytes using canonical CBOR encodings
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this to all the types that support preserve-encodings/etc as we expose it in rust and it can't hurt to expose it. I used it in the cbor docs example.

One small possible inconsistency is that it's not added to the byron/cip25 types as we can't guarantee anything implementing cbor_event::Serialize will do it canonically or not. The comment for to_cbor_bytes() on those types does mention that the format may or may not be canonical.

pub fn to_canonical_cbor_bytes(&self) -> Vec<u8> {
cml_core::serialization::Serialize::to_canonical_cbor_bytes(&self.0)
}

/**
* Create this type from CBOR bytes
*/
Expand All @@ -440,6 +447,13 @@ macro_rules! impl_wasm_cbor_api {
hex::encode(self.to_cbor_bytes())
}

/**
* Serialize this type to CBOR bytes using canonical CBOR encodings as hex bytes
*/
pub fn to_canonical_cbor_hex(&self) -> String {
hex::encode(self.to_canonical_cbor_bytes())
}

/**
* Create this type from the CBOR bytes encoded as a hex string.
* This is useful for interfacing with CIP30
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/modules/CIP25.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 5
sidebar_position: 3
---

# CIP25
Expand Down
88 changes: 88 additions & 0 deletions docs/docs/modules/cbor.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
sidebar_position: 1
---

# CBOR

Cardano on-chain types are stored using [CBOR](https://www.rfc-editor.org/rfc/rfc7049), a data format similar to JSON but with many more features and in binary.

## Tool Interoperability

Due to CBOR's flexibility it is possible that one piece of CBOR can be represented in multiple ways in the binary encoding. This causes problems when using CBOR taken on-chain or from another tool and using it with another tool. Notably, one small difference in the binary encoding of CBOR could result in hashes being totally different. e.g. metadatum hashes or transaction hashes calculated in a dApp might be different than in the wallet causing the entire transaction to be rejected by the network.

CML solves this by supporting automatically every single possible CBOR encoding variation. On-chain types created by deserializing from CBOR bytes will remember these details and re-serializing will use them and result in the same CBOR bytes, unlike some other tools.

## Rust

On-chan types in rust can (de)serialize to/from CBOR Via the `Serialize`/`Deserialize` and `ToBytes`/`FromBytes` traits located within the `cml_core::serialize` module.

Most on-chain types implement the `Serialize` and `Deserialize` traits. These traits guarantee that all CBOR encoding details are preserved upon deserialization and upon serialization it is possible to choose between canonical CBOR encoding and arbitrary encodings (the original it was decoded from).

Byron-era types do not implement `Serialize`/`Deserialize` and instead implement `ToBytes`/`FromBytes`. Byron on-chain types are always in canonical CBOR so this was not necessary.

The types in the `cip25` module also do not support `Serialize`/`Deserialize` in favor of `ToBytes`/`FromBytes`. The underlying metadata on-chain does and you should use the types in`cml_core::metadata`

```rust
use cml_core::serialization::{Serialize, Deserialize};
let canonical_cbor_hex = "825820aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa01";
// these all represent the following CBOR:
// [ ; array of 2 elements (transaction input struct)
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, ; bytes (tx hash)
// 1 ; unsigned integer (tx index)
// ]
let non_canonical_cbor = [
canonical_cbor_hex,
"825820aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1a00000001",
"9f5f48aaaaaaaaaaaaaaaa48aaaaaaaaaaaaaaaa48aaaaaaaaaaaaaaaa48aaaaaaaaaaaaaaaaff01ff",
"9900025820aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa190001",
"9b00000000000000025f41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aaff1b0000000000000001",
];
for orig_cbor_hex in non_canonical_cbor {
let tx_in = TransactionInput::from_cbor_bytes(&hex::decode(orig_cbor_hex).unwrap()).unwrap();
// serialize back to cbor bytes using the same cbor encoding details so it will match
// the format where it came from
assert_eq!(hex::encode(tx_in.to_cbor_bytes()), orig_cbor_hex);
// no matter how it was created it will represent the same data and can be encoded to
// canonical cbor bytes which will be the same as all of these are the same transaction input
assert_eq!(hex::encode(tx_in.to_canonical_cbor_bytes()), canonical_cbor_hex);
}
```

## WASM

All on-chain types have the traits directly exposed on each struct as the methods:
* `.to_cbor_bytes()`
* `.to_canonical_cbor_bytes()`
* `.from_cbor_bytes()`
* `.to_cbor_hex()`
* `.to_canonical_cbor_hex()`
* `.from_cbor_hex()`

The hex ones are useful for working with CIP-30 (dApp connector).

On post-Byron on-chain types this delegates to `Serialize`/`Deserialize` (see rust section) and preserve round-trip always. CIP25 and Byron types will always serialize to canonical CBOR. All on-chain data during the Byron era has to be canonical CBOR so this is not a big issue but is worth noting.

```javascript
let canonicalCborHex = "825820aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa01";
// these all represent the following CBOR:
// [ ; array of 2 elements (transaction input struct)
// 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA, ; bytes (tx hash)
// 1 ; unsigned integer (tx index)
// ]
let nonCanonicalCbor = [
canonicalCborHex,
"825820aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1a00000001",
"9f5f48aaaaaaaaaaaaaaaa48aaaaaaaaaaaaaaaa48aaaaaaaaaaaaaaaa48aaaaaaaaaaaaaaaaff01ff",
"9900025820aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa190001",
"9b00000000000000025f41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aa41aaff1b0000000000000001",
];
for (let origCborHex of nonCanonicalCbor) {
let txIn = CML.TransactionInput.from_cbor_hex(orig_cbor_hex);
// serialize back to cbor bytes using the same cbor encoding details so it will match
// the format where it came from
console.assert(txIn.to_cbor_hex() == origCborHex);
// no matter how it was created it will represent the same data and can be encoded to
// canonical cbor bytes which will be the same as all of these are the same transaction input
console.assert(txIn.to_canonical_cbor_hex() == canonicalCborHex);
}
```
2 changes: 1 addition & 1 deletion docs/docs/modules/cip36.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 5
sidebar_position: 4
---
# CIP36

63 changes: 63 additions & 0 deletions docs/docs/modules/json.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
sidebar_position: 2
---

# JSON

## General structs

All on-chain types have to/from JSON support. The vast majority is auto-generated but some have custom logic e.g. `Url`, `Ipv4`, `BigInteger`, etc.

### WASM

In WASM JSON conversions are exposed by `.to_json()` and `.from_json()` methods on all supported wrappers. There is also a `to_js_value()`.

```javascript
let txInJson = "{\"transaction_id\":\"0fba1404ed9b82b41938ba2e8bda7bec8cce813fb7e7cd7692b43caa76fe891c\",\"index\":3}";

let txIn = CML.TransactionInput.from_json(txInJson);

console.log(`txIn JSON: ${txIn.to_json()}`);
```

### Rust

JSON conversions are exposed in rust via the [`serde::Serialize`](https://docs.rs/serde/latest/serde/trait.Serialize.html) and [`serde::Deserialize`](https://docs.rs/serde/latest/serde/trait.Deserialize.html) traits together with `serde_json`.

example:
```rust
let tx_in_json = "{\"transaction_id\":\"0fba1404ed9b82b41938ba2e8bda7bec8cce813fb7e7cd7692b43caa76fe891c\",\"index\":3}";

// from JSON using serde_json::from_str() - note the type annotations
let tx_in: TransactionInput = serde_json::from_str(tx_in_json).unwrap();

// to JSON using serde_json::to_string() - use to_string_pretty() if you want more human-readable formatting
println!("tx_in JSON: {}", serde_json::to_string(&tx_in).unwrap());
```

## Metadata

Metadata, on top of the generic API mentioned above, has specific JSON functionality for compatability with cardano-node.

There are three formats on `MetadataJsonSchema`. `NoConversions` is the stricted, stricter than cardano-node and only converts when there are no implicit conversions at all. `BasicConversions` is the node's `TxMetadataJsonNoSchema` and `DetailedSchema` its `TxMetadataJsonDetailedSchema`. See `MetadataJsonSchema` for more info on the schema.

```javascript
let basic_json = "{\"0x8badf00d\": \"0xdeadbeef\",\"9\": 5,\"obj\": {\"a\":[{\"5\": 2},{}]}}";
let metadatum = CML.encode_json_str_to_metadatum(basic_json, CML.MetadataJsonSchema.BasicConversions);
console.log(`detailed json: ${CML.decode_metadatum_to_json_str(metadatum, CML.MetadataJsonSchema.DetailedSchema)}`);
// OUTPUT:
// detailed json: {"map":[{"k":{"bytes":"8badf00d"},"v":{"bytes":"deadbeef"}},{"k":{"int":9},"v":{"int":5}},{"k":{"string":"obj"},"v":{"map":[{"k":{"string":"a"},"v":{"list":[{"map":[{"k":{"int":5},"v":{"int":2}}]},{"map":[]}]}}]}}]}
```

## Plutus Datums

Plutus datums also have additional cardano-node JSON support. Remember that Plutus has no String datum so the strings there will be converted to utf8 bytes. See `CardanoNodePlutusDatumSchema` for more info on the schema.

```javascript
let basic_json = "{ \"100\": [ { \"x\": \"0\", \"y\": 1 } ], \"foo\": \"0x0000baadf00d0000cafed00d0000deadbeef0000\" }";
let datum = CML.encode_json_str_to_plutus_datum(basic_json, CML.CardanoNodePlutusDatumSchema.BasicConversions);
console.log(`detailed json: ${CML.decode_plutus_datum_to_json_str(datum, CML.CardanoNodePlutusDatumSchema.DetailedSchema,
)}`);
// OUTPUT:
// detailed json: {"map":[{"k":{"int":100},"v":{"list":[{"map":[{"k":{"bytes":"78"},"v":{"bytes":"30"}},{"k":{"bytes":"79"},"v":{"int":1}}]}]}},{"k":{"bytes":"666f6f"},"v":{"bytes":"0000baadf00d0000cafed00d0000deadbeef0000"}}]}
```
Loading