Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a41c190
parse `BoxValue` as u64 or a string;
greenhat Oct 4, 2021
cb79765
fix without-default-features build;
greenhat Oct 4, 2021
084d80d
add JSON parsing TokenAmount as either string or number;
greenhat Oct 4, 2021
e8e16b6
encode box value and token amount as strings in JSON;
greenhat Jul 19, 2021
92585d6
draft Transaction::to_json_dapp() impl in Wasm;
greenhat Oct 4, 2021
9e0712b
switch BoxValue and TokenAmount JSON encoding to numbers in ergo-lib;
greenhat Jul 20, 2021
e07c08d
JSON encode TokenAmountJsonDapp in Wasm;
greenhat Oct 4, 2021
e4015f5
impl UnsignedTransaction::to_json_dapp();
greenhat Jul 20, 2021
5be7e2a
formatting;
greenhat Jul 20, 2021
41e64c3
switch `Transaction::to_json()` and `UnsignedTransaction.to_json()`
greenhat Sep 13, 2021
d092851
fix missing rand impl on wasm; bump RustCrypto crates;
greenhat Aug 22, 2021
bff199e
make `to_json()` return string instead of JsValue;
greenhat Oct 4, 2021
0a86f39
add ErgoBox::to_json() roundtrip test;
greenhat Jul 21, 2021
3e0b72e
rename `to_json_dapp()` to `to_js_eip12`;
greenhat Oct 4, 2021
a90199c
rename `*Dapp` type hierarchy to `*Eip12`;
greenhat Sep 13, 2021
f0a3231
change `ProverResult.to_json()` to return string instead of JsValue;
greenhat Sep 13, 2021
9463e08
fix build after rebase;
greenhat Oct 4, 2021
5dc86dc
update CHANGELOG (added merged PRs);
greenhat Oct 4, 2021
e04e87e
add UnsignedTransaction and ErgoBoxCandidate conversion to EIP-12 types;
greenhat Aug 22, 2021
b189aff
update UnsignedTransactionJsonEip12 and TransactionJsonEip12
greenhat Aug 22, 2021
6339636
fix doc comment for `to_js_eip12` methods;
greenhat Aug 22, 2021
2358552
fix doc comment;
greenhat Aug 23, 2021
219ef4e
fix build after rebase;
greenhat Sep 13, 2021
6d12b89
fix Cargo.toml after rebase;
greenhat Oct 4, 2021
8674eb1
fix build after rebase;
greenhat Oct 6, 2021
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
26 changes: 16 additions & 10 deletions bindings/ergo-lib-wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ default = ["console_error_panic_hook"]
[dependencies]
base16 = "0.2.1"
serde = { version = "1.0", features = ["derive"]}
ergo-lib = { version = "^0.13.0", path = "../../ergo-lib" }
ergo-lib = { version = "^0.13.0", path = "../../ergo-lib"}
serde_json = "1.0"
js-sys = "0.3"

Expand All @@ -30,21 +30,27 @@ console_error_panic_hook = { version = "0.1.6", optional = true }

derive_more = "0.99"

[dependencies.serde_with]
version = "1.9.1"
features = [ "json" ]

[dependencies.wasm-bindgen]
version = "0.2.74"
features = ["serde-serialize"]

[dev-dependencies]
wasm-bindgen-test = "0.3.22"

# [dev-dependencies.proptest]
# # wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
# version = "0.10"
# # The default feature set includes things like process forking which are not
# # supported in Web Assembly.
# default-features = false
# # Enable using the `std` crate.
# features = ["std"]
ergotree-ir = { version = "^0.13.0", path = "../../ergotree-ir", features = ["arbitrary"] }
ergo-lib = { version = "^0.13.0", path = "../../ergo-lib", features = ["arbitrary"] }

[dev-dependencies.proptest]
# wasm support, via https://altsysrq.github.io/proptest-book/proptest/wasm.html
version = "1.0.0"
# The default feature set includes things like process forking which are not
# supported in Web Assembly.
default-features = false
# Enable using the `std` crate.
features = ["std"]

[package.metadata.wasm-pack.profile.release]
# wasm-opt = ["-O", "--enable-mutable-globals"]
Expand Down
18 changes: 14 additions & 4 deletions bindings/ergo-lib-wasm/src/ergo_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use wasm_bindgen::prelude::*;
use crate::ast::Constant;
use crate::ergo_tree::ErgoTree;
use crate::error_conversion::to_js;
use crate::json::ErgoBoxJsonEip12;
use crate::token::Tokens;
use crate::utils::I64;
use crate::{contract::Contract, transaction::TxId};
Expand Down Expand Up @@ -171,12 +172,21 @@ impl ErgoBox {
.map(Constant::from)
}

/// JSON representation
pub fn to_json(&self) -> Result<JsValue, JsValue> {
JsValue::from_serde(&self.0.clone()).map_err(to_js)
/// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
pub fn to_json(&self) -> Result<String, JsValue> {
serde_json::to_string_pretty(&self.0.clone())
.map_err(|e| JsValue::from_str(&format!("{}", e)))
}

/// JSON representation according to EIP-12 https://github.com/ergoplatform/eips/pull/23
/// (similar to [`Self::to_json`], but as JS object with box value and token amounts encoding as strings)
pub fn to_js_eip12(&self) -> Result<JsValue, JsValue> {
let box_dapp: ErgoBoxJsonEip12 = self.0.clone().into();
JsValue::from_serde(&box_dapp).map_err(|e| JsValue::from_str(&format!("{}", e)))
}

/// JSON representation
/// parse from JSON
/// supports Ergo Node/Explorer API and box values and token amount encoded as strings
pub fn from_json(json: &str) -> Result<ErgoBox, JsValue> {
serde_json::from_str(json).map(Self).map_err(to_js)
}
Expand Down
223 changes: 223 additions & 0 deletions bindings/ergo-lib-wasm/src/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
//! JSON serialization according to EIP-12 (using strings for BoxValue and TokenAmount)

use std::convert::TryInto;

use derive_more::FromStr;
use ergo_lib::chain::transaction::unsigned::UnsignedTransaction;
use ergo_lib::chain::transaction::DataInput;
use ergo_lib::chain::transaction::Input;
use ergo_lib::chain::transaction::Transaction;
use ergo_lib::chain::transaction::TxId;
use ergo_lib::chain::transaction::UnsignedInput;
use ergo_lib::ergotree_ir::chain::ergo_box::box_value::BoxValue;
use ergo_lib::ergotree_ir::chain::ergo_box::BoxId;
use ergo_lib::ergotree_ir::chain::ergo_box::ErgoBox;
use ergo_lib::ergotree_ir::chain::ergo_box::ErgoBoxCandidate;
use ergo_lib::ergotree_ir::chain::ergo_box::NonMandatoryRegisters;
use ergo_lib::ergotree_ir::chain::token::Token;
use ergo_lib::ergotree_ir::chain::token::TokenId;
use ergo_lib::ergotree_ir::ergo_tree::ErgoTree;
use serde::Serialize;

#[derive(Serialize, PartialEq, Debug, Clone)]
pub(crate) struct TransactionJsonEip12 {
#[cfg_attr(feature = "json", serde(rename = "id"))]
pub tx_id: TxId,
/// inputs, that will be spent by this transaction.
#[cfg_attr(feature = "json", serde(rename = "inputs"))]
pub inputs: Vec<Input>,
/// inputs, that are not going to be spent by transaction, but will be reachable from inputs
/// scripts. `dataInputs` scripts will not be executed, thus their scripts costs are not
/// included in transaction cost and they do not contain spending proofs.
#[cfg_attr(feature = "json", serde(rename = "dataInputs"))]
pub data_inputs: Vec<DataInput>,
#[cfg_attr(feature = "json", serde(rename = "outputs"))]
pub outputs: Vec<ErgoBoxJsonEip12>,
}

impl From<Transaction> for TransactionJsonEip12 {
fn from(t: Transaction) -> Self {
TransactionJsonEip12 {
tx_id: t.id(),
inputs: t.inputs.try_into().unwrap(),
data_inputs: t
.data_inputs
.map(|di| di.try_into().unwrap())
.unwrap_or_else(Vec::new),
outputs: t.outputs.into_iter().map(|b| b.into()).collect(),
}
}
}

#[derive(Serialize, PartialEq, Debug, Clone)]
pub(crate) struct UnsignedTransactionJsonEip12 {
/// unsigned inputs, that will be spent by this transaction.
#[cfg_attr(feature = "json", serde(rename = "inputs"))]
pub inputs: Vec<UnsignedInput>,
/// inputs, that are not going to be spent by transaction, but will be reachable from inputs
/// scripts. `dataInputs` scripts will not be executed, thus their scripts costs are not
/// included in transaction cost and they do not contain spending proofs.
#[cfg_attr(feature = "json", serde(rename = "dataInputs"))]
pub data_inputs: Vec<DataInput>,
/// box candidates to be created by this transaction
#[cfg_attr(feature = "json", serde(rename = "outputs"))]
pub outputs: Vec<ErgoBoxCandidateJsonEip12>,
}

impl From<UnsignedTransaction> for UnsignedTransactionJsonEip12 {
fn from(t: UnsignedTransaction) -> Self {
UnsignedTransactionJsonEip12 {
inputs: t.inputs.try_into().unwrap(),
data_inputs: t
.data_inputs
.map(|di| di.try_into().unwrap())
.unwrap_or_else(Vec::new),
outputs: t.output_candidates.into_iter().map(|b| b.into()).collect(),
}
}
}

#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
pub(crate) struct ErgoBoxJsonEip12 {
#[serde(rename = "boxId", alias = "id")]
pub box_id: Option<BoxId>,
/// amount of money associated with the box
#[serde(rename = "value")]
pub value: BoxValueJsonEip12,
/// guarding script, which should be evaluated to true in order to open this box
#[serde(
rename = "ergoTree",
with = "ergo_lib::ergotree_ir::chain::json::ergo_tree"
)]
pub ergo_tree: ErgoTree,
/// secondary tokens the box contains
#[serde(rename = "assets")]
pub tokens: Vec<TokenJsonEip12>,
/// additional registers the box can carry over
#[serde(rename = "additionalRegisters")]
pub additional_registers: NonMandatoryRegisters,
/// height when a transaction containing the box was created.
/// This height is declared by user and should not exceed height of the block,
/// containing the transaction with this box.
#[serde(rename = "creationHeight")]
pub creation_height: u32,
/// id of transaction which created the box
#[serde(rename = "transactionId", alias = "txId")]
pub transaction_id: TxId,
/// number of box (from 0 to total number of boxes the transaction with transactionId created - 1)
#[serde(rename = "index")]
pub index: u16,
}

impl From<ErgoBox> for ErgoBoxJsonEip12 {
fn from(b: ErgoBox) -> Self {
ErgoBoxJsonEip12 {
box_id: b.box_id().into(),
value: b.value.into(),
ergo_tree: b.ergo_tree,
tokens: b.tokens.into_iter().map(|t| t.into()).collect(),
additional_registers: b.additional_registers,
creation_height: b.creation_height,
transaction_id: b.transaction_id,
index: b.index,
}
}
}

/// Contains the same fields as `ErgoBox`, except if transaction id and index,
/// that will be calculated after full transaction formation.
/// Use [`box_builder::ErgoBoxCandidateBuilder`] to create an instance.
#[derive(Serialize, PartialEq, Eq, Clone, Debug)]
pub(crate) struct ErgoBoxCandidateJsonEip12 {
/// amount of money associated with the box
#[serde(rename = "value")]
pub value: BoxValueJsonEip12,
/// guarding script, which should be evaluated to true in order to open this box
#[serde(
rename = "ergoTree",
with = "ergo_lib::ergotree_ir::chain::json::ergo_tree"
)]
pub ergo_tree: ErgoTree,
#[serde(rename = "assets")]
pub tokens: Vec<TokenJsonEip12>,
/// additional registers the box can carry over
#[serde(rename = "additionalRegisters")]
pub additional_registers: NonMandatoryRegisters,
/// height when a transaction containing the box was created.
/// This height is declared by user and should not exceed height of the block,
/// containing the transaction with this box.
#[serde(rename = "creationHeight")]
pub creation_height: u32,
}

impl From<ErgoBoxCandidate> for ErgoBoxCandidateJsonEip12 {
fn from(b: ErgoBoxCandidate) -> Self {
ErgoBoxCandidateJsonEip12 {
value: b.value.into(),
ergo_tree: b.ergo_tree,
tokens: b.tokens.into_iter().map(|t| t.into()).collect(),
additional_registers: b.additional_registers,
creation_height: b.creation_height,
}
}
}

#[serde_with::serde_as]
#[derive(
serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, Debug, Clone, Copy, FromStr,
)]
/// Box value in nanoERGs with bound checks
pub(crate) struct BoxValueJsonEip12(#[serde_as(as = "serde_with::DisplayFromStr")] u64);

impl From<BoxValue> for BoxValueJsonEip12 {
fn from(bv: BoxValue) -> Self {
BoxValueJsonEip12(*bv.as_u64())
}
}

/// Token represented with token id paired with it's amount
#[derive(Serialize, PartialEq, Eq, Debug, Clone)]
pub struct TokenJsonEip12 {
/// token id
#[serde(rename = "tokenId")]
pub token_id: TokenId,
/// token amount
#[serde(rename = "amount")]
pub amount: TokenAmountJsonEip12,
}

impl From<Token> for TokenJsonEip12 {
fn from(t: Token) -> Self {
TokenJsonEip12 {
token_id: t.token_id,
amount: TokenAmountJsonEip12(*t.amount.as_u64()),
}
}
}

/// Token amount with bound checks
#[serde_with::serde_as]
#[derive(Serialize, PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
pub struct TokenAmountJsonEip12(
// Encodes as string always
#[serde_as(as = "serde_with::DisplayFromStr")] u64,
);

#[cfg(test)]
mod tests {
use super::*;

use proptest::prelude::*;

proptest! {

#[test]
fn ergo_box_roundtrip_to_json(b in any::<ErgoBox>()) {
let wasm_box: crate::ergo_box::ErgoBox = b.into();
let j = wasm_box.to_json().unwrap();
// eprintln!("{}", j);
let wasm_box_parsed = crate::ergo_box::ErgoBox::from_json(&j).unwrap();
prop_assert_eq![wasm_box, wasm_box_parsed];
}
}
}
1 change: 1 addition & 0 deletions bindings/ergo-lib-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ pub mod utils;
pub mod wallet;

mod error_conversion;
pub(crate) mod json;
6 changes: 3 additions & 3 deletions bindings/ergo-lib-wasm/src/prover_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ impl ProverResult {
self.0.extension.clone().into()
}

/// JSON representation
pub fn to_json(&self) -> Result<JsValue, JsValue> {
JsValue::from_serde(&self.0.clone()).map_err(to_js)
/// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
pub fn to_json(&self) -> Result<String, JsValue> {
serde_json::to_string_pretty(&self.0.clone()).map_err(to_js)
}
}
15 changes: 12 additions & 3 deletions bindings/ergo-lib-wasm/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use wasm_bindgen::prelude::*;

use crate::ergo_box::BoxId;
use crate::error_conversion::to_js;
use crate::json::TokenJsonEip12;
use crate::utils::I64;

/// Token id (32 byte digest)
Expand Down Expand Up @@ -109,9 +110,17 @@ impl Token {
TokenAmount(self.0.amount)
}

/// JSON representation
pub fn to_json(&self) -> Result<JsValue, JsValue> {
JsValue::from_serde(&self.0.clone()).map_err(to_js)
/// JSON representation as text (compatible with Ergo Node/Explorer API, numbers are encoded as numbers)
pub fn to_json(&self) -> Result<String, JsValue> {
serde_json::to_string_pretty(&self.0.clone())
.map_err(|e| JsValue::from_str(&format!("{}", e)))
}

/// JSON representation according to EIP-12 https://github.com/ergoplatform/eips/pull/23
/// (similar to [`Self::to_json`], but as JS object with token amount encoding as string)
pub fn to_js_eip12(&self) -> Result<JsValue, JsValue> {
let t_dapp: TokenJsonEip12 = self.0.clone().into();
JsValue::from_serde(&t_dapp).map_err(|e| JsValue::from_str(&format!("{}", e)))
}
}

Expand Down
Loading