Skip to main content

dusk_transfer_contract_dd/
lib.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7//! This crate provides a data driver implementation for dusk transfer contract.
8
9#![no_std]
10#![deny(missing_docs)]
11#![deny(rustdoc::broken_intra_doc_links)]
12#![deny(clippy::pedantic)]
13#![allow(clippy::module_name_repetitions)]
14#![deny(unused_crate_dependencies)]
15#![deny(unused_extern_crates)]
16
17extern crate alloc;
18
19use alloc::format;
20use alloc::string::{String, ToString};
21use alloc::vec::Vec;
22
23use dusk_core::BlsScalar;
24use dusk_core::abi::ContractId;
25use dusk_core::signatures::bls::PublicKey as AccountPublicKey;
26use dusk_core::transfer::moonlight::AccountData;
27use dusk_core::transfer::withdraw::Withdraw;
28use dusk_core::transfer::{
29    CONTRACT_TO_ACCOUNT_TOPIC, CONTRACT_TO_CONTRACT_TOPIC, CONVERT_TOPIC,
30    ContractToAccount, ContractToAccountEvent, ContractToContract,
31    ContractToContractEvent, ConvertEvent, DEPOSIT_TOPIC, DepositEvent,
32    MINT_CONTRACT_TOPIC, MINT_TOPIC, MOONLIGHT_TOPIC,
33    MoonlightTransactionEvent, PHOENIX_TOPIC, PhoenixTransactionEvent,
34    WITHDRAW_TOPIC, WithdrawEvent,
35};
36use dusk_data_driver::{
37    ConvertibleContract, Error, JsonValue, from_rkyv, json_to_rkyv,
38    json_to_rkyv_pair_u64, json_to_rkyv_u64, rkyv_to_json,
39    rkyv_to_json_pair_u64, rkyv_to_json_u64, to_json,
40};
41
42/// The contract driver for encoding and decoding transactions.
43#[derive(Default)]
44pub struct ContractDriver;
45
46const TRANSFER_SCHEMA_FUNCTIONS: &str = r#"[{"name":"convert","doc":"","input":"Withdraw","output":"()","custom":false},{"name":"deposit","doc":"","input":"u64","output":"()","custom":false},{"name":"mint","doc":"","input":"Withdraw","output":"()","custom":false},{"name":"withdraw","doc":"","input":"Withdraw","output":"()","custom":false},{"name":"mint_to_contract","doc":"","input":"ContractToContract","output":"()","custom":false},{"name":"contract_to_contract","doc":"","input":"ContractToContract","output":"()","custom":false},{"name":"contract_to_account","doc":"","input":"ContractToAccount","output":"()","custom":false},{"name":"root","doc":"","input":"()","output":"BlsScalar","custom":false},{"name":"num_notes","doc":"","input":"()","output":"u64","custom":false},{"name":"chain_id","doc":"","input":"()","output":"u8","custom":false},{"name":"account","doc":"","input":"AccountPublicKey","output":"AccountData","custom":false},{"name":"contract_balance","doc":"","input":"ContractId","output":"u64","custom":false},{"name":"opening","doc":"","input":"u64","output":"unsupported","custom":false},{"name":"existing_nullifiers","doc":"","input":"Vec<BlsScalar>","output":"Vec<BlsScalar>","custom":false},{"name":"leaves_from_height","doc":"","input":"u64","output":"unsupported","custom":false},{"name":"leaves_from_pos","doc":"","input":"u64","output":"unsupported","custom":false},{"name":"sync","doc":"","input":"(u64,u64)","output":"unsupported","custom":false},{"name":"sync_nullifiers","doc":"","input":"(u64,u64)","output":"BlsScalar","custom":false},{"name":"sync_contract_balances","doc":"","input":"(u64,u64)","output":"(ContractId,u64)","custom":false},{"name":"sync_accounts","doc":"","input":"(u64,u64)","output":"(AccountData,AccountPublicKey)","custom":false}]"#;
47
48#[allow(clippy::match_same_arms)]
49impl ConvertibleContract for ContractDriver {
50    fn encode_input_fn(
51        &self,
52        fn_name: &str,
53        json: &str,
54    ) -> Result<Vec<u8>, Error> {
55        match fn_name {
56            // Transactions
57            "convert" => json_to_rkyv::<Withdraw>(json),
58            // Protocol calls
59            "deposit" => json_to_rkyv_u64(json),
60            "mint" | "withdraw" => json_to_rkyv::<Withdraw>(json),
61            "mint_to_contract" | "contract_to_contract" => {
62                json_to_rkyv::<ContractToContract>(json)
63            }
64            "contract_to_account" => json_to_rkyv::<ContractToAccount>(json),
65            // Queries
66            "root" | "num_notes" | "chain_id" => json_to_rkyv::<()>(json),
67            "account" => json_to_rkyv::<AccountPublicKey>(json),
68            "contract_balance" => json_to_rkyv::<ContractId>(json),
69            "opening" => json_to_rkyv_u64(json),
70            "existing_nullifiers" => json_to_rkyv::<Vec<BlsScalar>>(json),
71            // Feeder Queries
72            "leaves_from_height" | "leaves_from_pos" => json_to_rkyv_u64(json),
73            "sync"
74            | "sync_nullifiers"
75            | "sync_contract_balances"
76            | "sync_accounts" => json_to_rkyv_pair_u64(json),
77
78            // Unsupported
79            name => Err(Error::Unsupported(format!("fn_name {name}"))),
80        }
81    }
82
83    fn decode_input_fn(
84        &self,
85        fn_name: &str,
86        rkyv: &[u8],
87    ) -> Result<JsonValue, Error> {
88        match fn_name {
89            // Transactions and internal calls
90            "convert" => rkyv_to_json::<Withdraw>(rkyv),
91            // Protocol calls
92            "deposit" => rkyv_to_json_u64(rkyv),
93            "mint" | "withdraw" => rkyv_to_json::<Withdraw>(rkyv),
94            "mint_to_contract" | "contract_to_contract" => {
95                rkyv_to_json::<ContractToContract>(rkyv)
96            }
97            "contract_to_account" => rkyv_to_json::<ContractToAccount>(rkyv),
98            // Queries
99            "root" | "num_notes" | "chain_id" => rkyv_to_json::<()>(rkyv),
100            "account" => rkyv_to_json::<AccountPublicKey>(rkyv),
101            "contract_balance" => rkyv_to_json::<ContractId>(rkyv),
102            "opening" => rkyv_to_json_u64(rkyv),
103            "existing_nullifiers" => rkyv_to_json::<Vec<BlsScalar>>(rkyv),
104            // Feeder Queries
105            "leaves_from_height" | "leaves_from_pos" => rkyv_to_json_u64(rkyv),
106            "sync"
107            | "sync_nullifiers"
108            | "sync_contract_balances"
109            | "sync_accounts" => rkyv_to_json_pair_u64(rkyv),
110
111            // Unsupported
112            name => Err(Error::Unsupported(format!("fn_name {name}"))),
113        }
114    }
115
116    fn decode_output_fn(
117        &self,
118        fn_name: &str,
119        rkyv: &[u8],
120    ) -> Result<JsonValue, Error> {
121        match fn_name {
122            // Transactions and internal calls
123            "convert" => Ok(JsonValue::Null),
124            // Protocol calls
125            "deposit" => Ok(JsonValue::Null),
126            "mint" | "withdraw" => Ok(JsonValue::Null),
127            "mint_to_contract" | "contract_to_contract" => Ok(JsonValue::Null),
128            "contract_to_account" => Ok(JsonValue::Null),
129            // Queries
130            "root" => rkyv_to_json::<BlsScalar>(rkyv),
131            "num_notes" => rkyv_to_json_u64(rkyv),
132            "chain_id" => rkyv_to_json::<u8>(rkyv),
133            "account" => rkyv_to_json::<AccountData>(rkyv),
134            "contract_balance" => rkyv_to_json_u64(rkyv),
135            "opening" => {
136                Err(Error::Unsupported("opening not supported".into()))
137            }
138            "existing_nullifiers" => rkyv_to_json::<Vec<BlsScalar>>(rkyv),
139            // Feeder Queries
140            "sync_accounts" => from_rkyv::<(AccountData, [u8; 193])>(rkyv)
141                .and_then(|(data, key)| {
142                    // Keys are stored in raw 193-byte format; the BLS API does
143                    // not currently expose a checked constructor for this
144                    // representation, so validate immediately after decoding.
145                    let key =
146                        unsafe { AccountPublicKey::from_slice_unchecked(&key) };
147                    if !key.is_valid() {
148                        return Err(Error::Other(
149                            "invalid account public key".into(),
150                        ));
151                    }
152
153                    to_json((data, key)).map_err(Error::from)
154                }),
155            "sync_nullifiers" => rkyv_to_json::<BlsScalar>(rkyv),
156            "sync_contract_balances" => from_rkyv::<(ContractId, u64)>(rkyv)
157                .and_then(|(contract, balance)| {
158                    to_json((contract, JsonValue::String(balance.to_string())))
159                        .map_err(Error::from)
160                }),
161
162            "leaves_from_height" | "leaves_from_pos" | "sync" => {
163                Err(Error::Unsupported("unsupported".into()))
164            }
165
166            // Unsupported
167            name => Err(Error::Unsupported(format!("fn_name {name}"))),
168        }
169    }
170
171    fn decode_event(
172        &self,
173        event_name: &str,
174        rkyv: &[u8],
175    ) -> Result<JsonValue, Error> {
176        match event_name {
177            MOONLIGHT_TOPIC => rkyv_to_json::<MoonlightTransactionEvent>(rkyv),
178            PHOENIX_TOPIC => rkyv_to_json::<PhoenixTransactionEvent>(rkyv),
179            CONTRACT_TO_CONTRACT_TOPIC | MINT_CONTRACT_TOPIC => {
180                rkyv_to_json::<ContractToContractEvent>(rkyv)
181            }
182            CONTRACT_TO_ACCOUNT_TOPIC => {
183                rkyv_to_json::<ContractToAccountEvent>(rkyv)
184            }
185            WITHDRAW_TOPIC | MINT_TOPIC => rkyv_to_json::<WithdrawEvent>(rkyv),
186            DEPOSIT_TOPIC => rkyv_to_json::<DepositEvent>(rkyv),
187            CONVERT_TOPIC => rkyv_to_json::<ConvertEvent>(rkyv),
188
189            event => Err(Error::Unsupported(format!("event {event}"))),
190        }
191    }
192
193    fn get_schema(&self) -> String {
194        let moonlight = MOONLIGHT_TOPIC;
195        let phoenix = PHOENIX_TOPIC;
196        let contract_to_contract = CONTRACT_TO_CONTRACT_TOPIC;
197        let mint_contract = MINT_CONTRACT_TOPIC;
198        let contract_to_account = CONTRACT_TO_ACCOUNT_TOPIC;
199        let withdraw = WITHDRAW_TOPIC;
200        let mint = MINT_TOPIC;
201        let deposit = DEPOSIT_TOPIC;
202        let convert = CONVERT_TOPIC;
203
204        let events = format!(
205            r#"[{{"topic":"{moonlight}","data":"MoonlightTransactionEvent"}},{{"topic":"{phoenix}","data":"PhoenixTransactionEvent"}},{{"topic":"{contract_to_contract}","data":"ContractToContractEvent"}},{{"topic":"{mint_contract}","data":"ContractToContractEvent"}},{{"topic":"{contract_to_account}","data":"ContractToAccountEvent"}},{{"topic":"{withdraw}","data":"WithdrawEvent"}},{{"topic":"{mint}","data":"WithdrawEvent"}},{{"topic":"{deposit}","data":"DepositEvent"}},{{"topic":"{convert}","data":"ConvertEvent"}}]"#
206        );
207        let functions = TRANSFER_SCHEMA_FUNCTIONS;
208
209        format!(
210            r#"{{"name":"TransferContract","imports":[],"functions":{functions},"events":{events}}}"#
211        )
212    }
213}
214
215#[cfg(all(target_family = "wasm", feature = "ffi"))]
216dusk_data_driver::generate_wasm_entrypoint!(ContractDriver);