diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..e1df1e1
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,61 @@
+name: test
+
+on:
+ push:
+ branches: ["**"]
+ pull_request:
+
+jobs:
+ native-tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - uses: dtolnay/rust-toolchain@stable
+
+ - name: Install libclang for bindgen
+ run: sudo apt-get update && sudo apt-get install -y clang libclang-dev
+
+ - name: Run mocked native tests
+ run: cargo test --features native
+
+ riscv-link-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - uses: dtolnay/rust-toolchain@stable
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Install build dependencies
+ run: sudo apt-get update && sudo apt-get install -y clang libclang-dev build-essential cmake
+
+ - name: Build libcma for riscv64
+ working-directory: third_party/machine-asset-tools
+ run: |
+ make third-party-boost
+ make docker ARGS="build/riscv64/libcma.a"
+
+ - name: Verify riscv64 libcma artifact
+ run: |
+ set -euo pipefail
+ lib=third_party/machine-asset-tools/build/riscv64/libcma.a
+ if [[ ! -f "$lib" ]]; then
+ echo "Missing $lib"
+ ls -laR third_party/machine-asset-tools/build/riscv64 || true
+ exit 1
+ fi
+ member=$(ar t "$lib" | head -1)
+ obj=$(mktemp)
+ ar p "$lib" "$member" > "$obj"
+ file "$obj" | grep -F 'RISC-V'
+ rm -f "$obj"
diff --git a/Cargo.lock b/Cargo.lock
index 413dad8..952dd0d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -603,7 +603,6 @@ dependencies = [
"hex",
"json",
"lazy_static",
- "once_cell",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 2d17267..f6d96cd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,7 +18,6 @@ path = "src/lib.rs"
hex = "0.4"
lazy_static = { version = "1.4", optional = true }
json = "0.12"
-once_cell = "1.18"
ethers-core = "1.0.0"
[build-dependencies]
diff --git a/README.md b/README.md
index 347d19b..ee0caab 100644
--- a/README.md
+++ b/README.md
@@ -6,96 +6,72 @@
Cartesi Rollups LIBCMA Binding for Rust
-
-
-
-
-
-
-
# libcma-binding-rust
-LIBCMA is a Lightweight Rust utilities to parse Cartesi Machine Application (CMA) inputs, build on-chain voucher payloads to be executed on the base layer and handle core asset operations such as deposits, withdrawals, transfers, and balance checks. It acts as a plugin wallet for applicaitons and is particularly useful when you want scalable, predictable, and secure asset management in Cartesi applications that support assets like ETH, ERC20, ERC721, and ERC1155.
+LIBCMA is a lightweight Rust binding for Cartesi Machine Application (CMA) tooling: parse rollup inputs, build on-chain voucher payloads, and manage application assets (deposits, withdrawals, transfers, balances) for ETH, ERC-20, ERC-721, and ERC-1155.
## Clone with submodules
-Headers come from two git submodules under `third_party/`:
+Headers come from git submodules under `third_party/`:
-- **`third_party/machine-asset-tools`** — `libcma` (parser, types, ledger).
-- **`third_party/machine-guest-tools`** — `libcmt` headers used by `libcma` (e.g. `libcmt/abi.h`); required for `bindgen` when generating bindings.
-
-Clone with submodules in one step:
+- **`third_party/machine-asset-tools`** — `libcma` (parser, types, ledger)
+- **`third_party/machine-guest-tools`** — `libcmt` headers used by `libcma`
```bash
git clone --recurse-submodules https://github.com/Mugen-Builders/libcma_binding_rust
-```
-
-If you already cloned without submodules:
-
-```bash
+# or, if already cloned:
git submodule update --init --recursive
```
## Build requirements
-- **Clang / libclang** — `bindgen` needs a working libclang installation (same as any typical `bindgen` setup).
-- **Submodules** — `build.rs` expects both `third_party` trees to exist (after the step above).
-- **Linking the real C library** — With default features disabled, the build links the static library from `third_party/machine-asset-tools/build/riscv64` (see **Feature flags**). For local development on the host, the default `native` feature uses Rust mocks instead of linking that binary.
+- **Clang / libclang** for `bindgen`
+- **Submodules** present before building
+- **Real C library** — disable default features to link the static `libcma` archive from `third_party/machine-asset-tools/build/riscv64`
## Feature flags
-| Feature | Default | Purpose |
-| --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `native` | yes | Compiles `src/mocks.rs` shims for ledger/parser C symbols so you can build and run tests on the host (e.g. macOS) without the RISC-V `libcma` archive. |
-| `riscv64` | no | Reserved for cross-builds that link the vendored static `cma` library (see `build.rs`). |
-
-Build without default features when you have the static library path and target set up:
+| Feature | Default | Purpose |
+| --------- | ------- | ------- |
+| `native` | yes | Compiles `src/mocks.rs` shims so host tests run without the RISC-V `libcma` archive |
+| `riscv64` | no | Cross-build path that links the vendored static `cma` library |
```bash
-cargo build --no-default-features
+cargo build --no-default-features --features riscv64
```
## Ledger wrapper
-The `Ledger` type wraps the C `cma_ledger_*` API. Besides `Ledger::new()` (in-memory basic ledger), you can reinitialize storage using the same functions as the C library:
+`Ledger` wraps `cma_ledger_*` with helpers for file/buffer initialization, asset/account retrieval, deposit/withdraw/transfer, balance, and total supply.
-- `Ledger::init_from_file(path, LedgerFileConfig { ... })` → `cma_ledger_init_file`
-- `Ledger::init_from_buffer(&mut [u8], LedgerBufferConfig { ... })` → `cma_ledger_init_buffer`
-
-Exported helpers: `LedgerMemoryMode`, `LedgerFileConfig`, `LedgerBufferConfig`.
+- `retrieve_ether_assets()` uses `AssetType::Base`
+- `AssetType` also supports `TokenAddress`, `TokenAddressId`, and `TokenAddressIdAmount`
+- `RetrieveOperation::FindAndRemove` is supported
## Parser and vouchers
-This crate is useful for Cartesi dApp developers who need to:
+Pure-Rust parser aligned with `machine-asset-tools` / Cartesi Rollups v2.0:
-- Decode deposit payloads coming from Cartesi portals (ETH, ERC20, ERC721, ERC1155).
-- Decode withdrawal / transfer / ledger inspection requests encoded as JSON and hex.
-- Encode voucher payloads for on-chain token transfers (Ether, ERC20, ERC721 — ERC1155 encoding TODO).
+- Portal deposit decoding (packed + ABI tails for ERC-721/1155)
+- Auto-decode withdrawals/transfers by function selector
+- Inspect decoding for `ledger_getBalance` and `ledger_getTotalSupply`
+- Voucher encoding for Ether, ERC-20, ERC-721 (`safeTransferFrom`), ERC-1155 single/batch (`safeTransferFrom` / `safeBatchTransferFrom`)
### Core public functions
-- `cma_decode_advance(input: JsonValue) -> Result`
- Detects whether an input is a portal deposit (based on the caller address) or a user instruction (withdrawal/transfer). Returns a typed `CmaParserInput` (enum + associated structs) with parsed fields (addresses, amounts, token ids, exec_layer data).
-- `cma_decode_inspect(input: JsonValue) -> Result`
- Parses inspection (ledger) requests like `ledgerGetBalance` and `ledgerGetTotalSupply` encoded as JSON-in-hex and returns structured arguments.
-- `cma_encode_voucher(req_type: CmaParserVoucherType, voucher_request: CmaParserVoucherData) -> Result`
- Builds a `CmaVoucher` (destination, value, payload) for sending to the chain. Implemented for Ether, ERC20 and ERC721 vouchers.
-
-### Important types
+- `cma_decode_advance(req_type, input) -> Result`
+- `cma_decode_inspect(input) -> Result`
+- `cma_encode_voucher(req_type, app_address, voucher_request) -> Result`
-- `CmaParserInputType` — recognized request types (deposits, withdrawals, transfers, ledger queries).
-- `CmaParserInputData` — enum wrapping parsed payload structs, e.g. `EtherDeposit`, `Erc20Transfer`, `EtherWithdrawal`, etc.
-- `CmaParserError` — parsing error variants and helper conversions.
-- `CmaVoucher` — voucher structure (destination, value, payload) ready for submission.
+`CmaVoucher` fields: `destination`, `value` (wei for ether vouchers), `payload`.
-## Dependencies
+Inspect params are flat JSON strings, e.g.:
-- ethers-core = "1.0.0"
-- hex = "0.4"
-- json = "0.12"
-- once_cell = "1.18"
+```json
+{"method":"ledger_getBalance","params":["0x...account...","0x...token...","0x1"]}
+```
## Tests
@@ -103,16 +79,11 @@ This crate is useful for Cartesi dApp developers who need to:
cargo test
```
-Ledger integration tests exercise the `native` mock path by default.
-
-## Notes and limitations
-
-- ERC1155 voucher encoding is not implemented (placeholders return not implemented).
-- The parser expects JSON input as received by the application; it inspects the caller and method to choose a decoding path.
-
-## Contributing
+- `tests/parser_tests.rs` — integration tests against the pure-Rust parser
+- `tests/parser_vectors.rs` — vectors ported from `third_party/machine-asset-tools/tests/parser.c`
+- `tests/ledger_tests.rs` — ledger tests via native mocks
-- Open PRs for improvements, add missing voucher encodings, add more exhaustive parsing tests for edge cases.
+CI runs native tests on every push/PR and attempts an riscv64 link check when `libcma` can be built.
## License
diff --git a/src/error.rs b/src/error.rs
index d80352d..cd4a152 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -8,10 +8,17 @@ pub enum LedgerError {
InsufficientFunds,
AccountNotFound,
AssetNotFound,
+ BalanceNotFound,
SupplyOverflow,
BalanceOverflow,
InvalidAccount,
InsertionError,
+ MaxAssetsReached,
+ MaxAccountsReached,
+ MaxBalancesReached,
+ AssetSupply,
+ AccountBalance,
+ Remove,
Other(i32),
}
@@ -24,10 +31,17 @@ impl LedgerError {
x if x == bindings::CMA_LEDGER_ERROR_INSUFFICIENT_FUNDS as i32 => LedgerError::InsufficientFunds,
x if x == bindings::CMA_LEDGER_ERROR_ACCOUNT_NOT_FOUND as i32 => LedgerError::AccountNotFound,
x if x == bindings::CMA_LEDGER_ERROR_ASSET_NOT_FOUND as i32 => LedgerError::AssetNotFound,
+ x if x == bindings::CMA_LEDGER_ERROR_BALANCE_NOT_FOUND as i32 => LedgerError::BalanceNotFound,
x if x == bindings::CMA_LEDGER_ERROR_SUPPLY_OVERFLOW as i32 => LedgerError::SupplyOverflow,
x if x == bindings::CMA_LEDGER_ERROR_BALANCE_OVERFLOW as i32 => LedgerError::BalanceOverflow,
x if x == bindings::CMA_LEDGER_ERROR_INVALID_ACCOUNT as i32 => LedgerError::InvalidAccount,
x if x == bindings::CMA_LEDGER_ERROR_INSERTION_ERROR as i32 => LedgerError::InsertionError,
+ x if x == bindings::CMA_LEDGER_ERROR_MAX_ASSETS_REACHED as i32 => LedgerError::MaxAssetsReached,
+ x if x == bindings::CMA_LEDGER_ERROR_MAX_ACCOUNTS_REACHED as i32 => LedgerError::MaxAccountsReached,
+ x if x == bindings::CMA_LEDGER_ERROR_MAX_BALANCES_REACHED as i32 => LedgerError::MaxBalancesReached,
+ x if x == bindings::CMA_LEDGER_ERROR_ASSET_SUPPLY as i32 => LedgerError::AssetSupply,
+ x if x == bindings::CMA_LEDGER_ERROR_ACCOUNT_BALANCE as i32 => LedgerError::AccountBalance,
+ x if x == bindings::CMA_LEDGER_ERROR_REMOVE as i32 => LedgerError::Remove,
_ => LedgerError::Other(code),
}
}
diff --git a/src/helpers.rs b/src/helpers.rs
index fc7c77d..0a87a3b 100644
--- a/src/helpers.rs
+++ b/src/helpers.rs
@@ -1,5 +1,4 @@
use hex;
-use once_cell::sync::Lazy;
use ethers_core::types::{Address};
use json::{JsonValue, object};
use crate::parser::CmaVoucher;
@@ -11,68 +10,6 @@ pub fn hex_to_string(hex: &str) -> Result> {
Ok(s)
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Portals {
- ERC1155BatchPortal,
- ERC1155SinglePortal,
- ERC20Portal,
- ERC721Portal,
- EtherPortal,
- None,
-}
-
-pub struct CartesiAddresses {
- pub erc1155_batch_portal: String,
- pub erc1155_single_portal: String,
- pub erc20_portal: String,
- pub erc721_portal: String,
- pub ether_portal: String,
-}
-
-pub static CARTESI_ADDRESSES: Lazy = Lazy::new(|| CartesiAddresses {
- erc1155_batch_portal: "0xc700A2e5531E720a2434433b6ccf4c0eA2400051".to_string(),
- erc1155_single_portal: "0xc700A261279aFC6F755A3a67D86ae43E2eBD0051".to_string(),
- erc20_portal: "0xc700D6aDd016eECd59d989C028214Eaa0fCC0051".to_string(),
- erc721_portal: "0xc700d52F5290e978e9CAe7D1E092935263b60051".to_string(),
- ether_portal: "0xc70076a466789B595b50959cdc261227F0D70051".to_string(),
-});
-
-pub trait PortalMatcher {
- fn match_portal(&self, addr: &str) -> Portals;
- fn get_portal_address(&self, portal: Portals) -> Option<&str>;
-}
-
-impl PortalMatcher for CartesiAddresses {
- fn match_portal(&self, addr: &str) -> Portals {
- let caller_address = addr.trim().to_lowercase();
-
- if caller_address == self.erc1155_batch_portal.to_lowercase() {
- Portals::ERC1155BatchPortal
- } else if caller_address == self.erc1155_single_portal.to_lowercase() {
- Portals::ERC1155SinglePortal
- } else if caller_address == self.erc20_portal.to_lowercase() {
- Portals::ERC20Portal
- } else if caller_address == self.erc721_portal.to_lowercase() {
- Portals::ERC721Portal
- } else if caller_address == self.ether_portal.to_lowercase() {
- Portals::EtherPortal
- } else {
- Portals::None
- }
- }
-
- fn get_portal_address(&self, portal: Portals) -> Option<&str> {
- match portal {
- Portals::ERC1155BatchPortal => Some(&self.erc1155_batch_portal),
- Portals::ERC1155SinglePortal => Some(&self.erc1155_single_portal),
- Portals::ERC20Portal => Some(&self.erc20_portal),
- Portals::ERC721Portal => Some(&self.erc721_portal),
- Portals::EtherPortal => Some(&self.ether_portal),
- Portals::None => None,
- }
- }
-}
-
pub trait ToAddress {
fn to_address(&self) -> Result;
}
@@ -97,10 +34,10 @@ pub trait ToJson {
impl ToJson for CmaVoucher {
fn to_json(&self) -> JsonValue {
- let voucher = object! {
+ object! {
"destination" => format!("{}", self.destination),
+ "value" => format!("{}", self.value),
"payload" => format!("{}", self.payload),
- };
- return voucher;
+ }
}
}
\ No newline at end of file
diff --git a/src/ledger.rs b/src/ledger.rs
index e2e0c25..c37cbca 100644
--- a/src/ledger.rs
+++ b/src/ledger.rs
@@ -201,17 +201,13 @@ impl Ledger {
}
pub fn retrieve_ether_assets(&mut self) -> Result {
- let asset_id = self
- .retrieve_asset(
- None,
- None,
- None,
- AssetType::TokenAddressId,
- RetrieveOperation::FindOrCreate,
- )
- .map_err(|e| e)?;
-
- return Ok(asset_id);
+ self.retrieve_asset(
+ None,
+ None,
+ None,
+ AssetType::Base,
+ RetrieveOperation::FindOrCreate,
+ )
}
/// Retrieve or create an account
diff --git a/src/lib.rs b/src/lib.rs
index 996bc5f..d6b313c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,5 +16,5 @@ pub mod parser;
pub use error::{LedgerError, ParserError};
pub use ledger::{Ledger, LedgerBufferConfig, LedgerFileConfig, LedgerMemoryMode};
-pub use parser::{CmaParserInputType, CmaParserVoucherType, CmaParserError, CmaVoucher, CmaParserUnidentifiedInput, CmaParserEtherDeposit, CmaParserErc20Deposit, CmaParserErc721Deposit, CmaParserErc1155SingleDeposit, CmaParserErc1155BatchDeposit, CmaParserEtherWithdrawal, CmaParserErc20Withdrawal, CmaParserErc721Withdrawal, CmaParserErc1155SingleWithdrawal, CmaParserErc1155BatchWithdrawal, CmaParserEtherTransfer, CmaParserErc20Transfer, CmaParserErc721Transfer, CmaParserErc1155SingleTransfer, CmaParserErc1155BatchTransfer, CmaParserBalance, CmaParserSupply, CmaParserInput, CmaParserInputData};
+pub use parser::{CmaParserInputType, CmaParserVoucherType, CmaParserError, CmaVoucher, CmaParserUnidentifiedInput, CmaParserEtherDeposit, CmaParserErc20Deposit, CmaParserErc721Deposit, CmaParserErc1155SingleDeposit, CmaParserErc1155BatchDeposit, CmaParserEtherWithdrawal, CmaParserErc20Withdrawal, CmaParserErc721Withdrawal, CmaParserErc1155SingleWithdrawal, CmaParserErc1155BatchWithdrawal, CmaParserEtherTransfer, CmaParserErc20Transfer, CmaParserErc721Transfer, CmaParserErc1155SingleTransfer, CmaParserErc1155BatchTransfer, CmaParserBalance, CmaParserSupply, CmaParserInput, CmaParserInputData, cma_parser_get_last_error_message};
pub use types::*;
\ No newline at end of file
diff --git a/src/mocks.rs b/src/mocks.rs
index a9b2dd8..5b20440 100644
--- a/src/mocks.rs
+++ b/src/mocks.rs
@@ -31,7 +31,9 @@ struct AccountInfo {
// Use thread-local storage so each test thread has its own state
thread_local! {
static MOCK_STATE: RefCell = RefCell::new(MockLedgerState {
- next_asset_id: 1,
+ // Asset id 1 is reserved for the Base (Ether) asset (see BASE_ASSET_ID),
+ // so generated token asset ids must start at 2 to avoid colliding with it.
+ next_asset_id: 2,
next_account_id: 1,
assets: HashMap::new(),
accounts: HashMap::new(),
@@ -49,7 +51,7 @@ pub unsafe extern "C" fn cma_ledger_init(ledger: *mut bindings::cma_ledger_t) ->
// Reset the mock state
MOCK_STATE.with(|state| {
let mut s = state.borrow_mut();
- s.next_asset_id = 1;
+ s.next_asset_id = 2; // reserve id 1 for Base (Ether) asset
s.next_account_id = 1;
s.assets.clear();
s.accounts.clear();
@@ -103,7 +105,7 @@ pub unsafe extern "C" fn cma_ledger_fini(_ledger: *mut bindings::cma_ledger_t) -
pub unsafe extern "C" fn cma_ledger_reset(_ledger: *mut bindings::cma_ledger_t) -> i32 {
MOCK_STATE.with(|state| {
let mut s = state.borrow_mut();
- s.next_asset_id = 1;
+ s.next_asset_id = 2; // reserve id 1 for Base (Ether) asset
s.next_account_id = 1;
s.assets.clear();
s.accounts.clear();
@@ -152,32 +154,51 @@ pub unsafe extern "C" fn cma_ledger_retrieve_asset(
t if t == bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_ID => {
let id_val = *asset_id;
if id_val != 0 && s.assets.contains_key(&id_val) {
+ if operation == bindings::cma_ledger_retrieve_operation_t_CMA_LEDGER_OP_FIND_AND_REMOVE
+ {
+ s.balances.retain(|(a_id, _), _| *a_id != id_val);
+ s.assets.remove(&id_val);
+ s.asset_lookup.retain(|_, v| *v != id_val);
+ return bindings::CMA_LEDGER_SUCCESS as i32;
+ }
if !out_total_supply.is_null() {
(*out_total_supply).data = mock_total_supply_for_asset(&s, id_val);
}
return bindings::CMA_LEDGER_SUCCESS as i32;
}
- if !token_address.is_null() && !token_id.is_null() {
- let addr = (*token_address).data;
- let id = (*token_id).data;
- let lookup_key = (addr, id);
-
- if let Some(&found_id) = s.asset_lookup.get(&lookup_key) {
- *asset_id = found_id;
- if !out_total_supply.is_null() {
- (*out_total_supply).data = mock_total_supply_for_asset(&s, found_id);
- }
- return bindings::CMA_LEDGER_SUCCESS as i32;
+ }
+ t if t == bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_BASE => {
+ const BASE_ASSET_ID: u64 = 1;
+ if s.assets.contains_key(&BASE_ASSET_ID) {
+ *asset_id = BASE_ASSET_ID;
+ if !out_total_supply.is_null() {
+ (*out_total_supply).data =
+ mock_total_supply_for_asset(&s, BASE_ASSET_ID);
}
+ return bindings::CMA_LEDGER_SUCCESS as i32;
}
+ if operation == bindings::cma_ledger_retrieve_operation_t_CMA_LEDGER_OP_FIND {
+ return bindings::CMA_LEDGER_ERROR_ASSET_NOT_FOUND as i32;
+ }
+ s.assets.insert(
+ BASE_ASSET_ID,
+ AssetInfo {
+ token_address: None,
+ token_id: None,
+ },
+ );
+ *asset_id = BASE_ASSET_ID;
+ if !out_total_supply.is_null() {
+ (*out_total_supply).data = mock_total_supply_for_asset(&s, BASE_ASSET_ID);
+ }
+ return bindings::CMA_LEDGER_SUCCESS as i32;
}
t if t == bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_TOKEN_ADDRESS => {
if !token_address.is_null() {
let addr = (*token_address).data;
- // Simple lookup by address only
for (&id, info) in &s.assets {
if let Some(ref stored_addr) = info.token_address {
- if stored_addr == &addr {
+ if stored_addr == &addr && info.token_id.is_none() {
*asset_id = id;
if !out_total_supply.is_null() {
let sum = mock_total_supply_for_asset(&s, id);
@@ -189,12 +210,15 @@ pub unsafe extern "C" fn cma_ledger_retrieve_asset(
}
}
}
- t if t == bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_TOKEN_ADDRESS_ID => {
+ t if t == bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_TOKEN_ADDRESS_ID
+ || t
+ == bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_TOKEN_ADDRESS_ID_AMOUNT =>
+ {
if !token_address.is_null() && !token_id.is_null() {
let addr = (*token_address).data;
let id = (*token_id).data;
let lookup_key = (addr, id);
-
+
if let Some(&found_id) = s.asset_lookup.get(&lookup_key) {
*asset_id = found_id;
if !out_total_supply.is_null() {
@@ -209,8 +233,10 @@ pub unsafe extern "C" fn cma_ledger_retrieve_asset(
}
match operation {
- op if op == bindings::cma_ledger_retrieve_operation_t_CMA_LEDGER_OP_FIND => {
- return bindings::CMA_LEDGER_ERROR_ASSET_NOT_FOUND as i32;
+ op if op == bindings::cma_ledger_retrieve_operation_t_CMA_LEDGER_OP_FIND
+ || op == bindings::cma_ledger_retrieve_operation_t_CMA_LEDGER_OP_FIND_AND_REMOVE =>
+ {
+ bindings::CMA_LEDGER_ERROR_ASSET_NOT_FOUND as i32
}
op if op == bindings::cma_ledger_retrieve_operation_t_CMA_LEDGER_OP_CREATE
|| op == bindings::cma_ledger_retrieve_operation_t_CMA_LEDGER_OP_FIND_OR_CREATE =>
diff --git a/src/parser.rs b/src/parser.rs
index 4881141..2352e27 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -1,10 +1,176 @@
-use crate::helpers::{hex_to_string};
+use crate::helpers::hex_to_string;
use ethers_core::abi::{ParamType, Token, decode, encode};
use ethers_core::types::{Address, Bytes, U256};
use ethers_core::utils::{id, to_checksum};
use hex;
-use json::{JsonValue};
+use json::JsonValue;
+use std::cell::RefCell;
+
+thread_local! {
+ static LAST_PARSER_ERROR: RefCell = RefCell::new(String::new());
+}
+
+fn set_last_parser_error(message: impl Into) {
+ LAST_PARSER_ERROR.with(|msg| *msg.borrow_mut() = message.into());
+}
+
+fn payload_bytes(input: &JsonValue) -> Result, CmaParserError> {
+ let payload_hex = input["data"]["payload"]
+ .as_str()
+ .ok_or(CmaParserError::MalformedInput)?;
+ hex::decode(payload_hex.trim_start_matches("0x"))
+ .map_err(|_| CmaParserError::MalformedInput)
+}
+
+fn require_len(bytes: &[u8], min: usize) -> Result<(), CmaParserError> {
+ if bytes.len() < min {
+ Err(CmaParserError::MalformedInput)
+ } else {
+ Ok(())
+ }
+}
+
+fn decode_abi_tail_two_bytes(tail: &[u8]) -> Result<(Bytes, Bytes), CmaParserError> {
+ if tail.is_empty() {
+ return Ok((Bytes::from(vec![]), Bytes::from(vec![])));
+ }
+ let decoded = decode(
+ &[ParamType::Bytes, ParamType::Bytes],
+ tail,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
+ let base = match &decoded[0] {
+ Token::Bytes(b) => Bytes::from(b.clone()),
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let exec = match &decoded[1] {
+ Token::Bytes(b) => Bytes::from(b.clone()),
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ Ok((base, exec))
+}
+
+fn decode_abi_tail_batch(
+ tail: &[u8],
+) -> Result<(Vec, Vec, Bytes, Bytes), CmaParserError> {
+ if tail.is_empty() {
+ return Err(CmaParserError::MalformedInput);
+ }
+ let decoded = decode(
+ &[
+ ParamType::Array(Box::new(ParamType::Uint(256))),
+ ParamType::Array(Box::new(ParamType::Uint(256))),
+ ParamType::Bytes,
+ ParamType::Bytes,
+ ],
+ tail,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
+
+ let token_ids = match &decoded[0] {
+ Token::Array(items) => items
+ .iter()
+ .map(|item| match item {
+ Token::Uint(v) => Ok(*v),
+ _ => Err(CmaParserError::MalformedInput),
+ })
+ .collect::, _>>()?,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let amounts = match &decoded[1] {
+ Token::Array(items) => items
+ .iter()
+ .map(|item| match item {
+ Token::Uint(v) => Ok(*v),
+ _ => Err(CmaParserError::MalformedInput),
+ })
+ .collect::, _>>()?,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let base = match &decoded[2] {
+ Token::Bytes(b) => Bytes::from(b.clone()),
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let exec = match &decoded[3] {
+ Token::Bytes(b) => Bytes::from(b.clone()),
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+
+ if token_ids.is_empty() || amounts.is_empty() || token_ids.len() != amounts.len() {
+ return Err(CmaParserError::MalformedInput);
+ }
+
+ Ok((token_ids, amounts, base, exec))
+}
+
+fn parse_hex_account_id(value: &str) -> Result {
+ let value = value.trim();
+ if !value.starts_with("0x") {
+ return Err(CmaParserError::MalformedInput);
+ }
+ let hex_body = &value[2..];
+ if hex_body.len() % 2 != 0 {
+ return Err(CmaParserError::MalformedInput);
+ }
+
+ let mut bytes = [0u8; 32];
+ let offset = if value.len() == 42 {
+ 12
+ } else if value.len() <= 66 {
+ (64usize.saturating_sub(hex_body.len())) / 2
+ } else {
+ return Err(CmaParserError::MalformedInput);
+ };
+
+ let decoded = hex::decode(hex_body).map_err(|_| CmaParserError::MalformedInput)?;
+ if offset + decoded.len() > 32 {
+ return Err(CmaParserError::MalformedInput);
+ }
+ bytes[offset..offset + decoded.len()].copy_from_slice(&decoded);
+ Ok(U256::from_big_endian(&bytes))
+}
+
+fn parse_hex_token_id(value: &str) -> Result {
+ let value = value.trim();
+ if !value.starts_with("0x") {
+ return Err(CmaParserError::MalformedInput);
+ }
+ let mut hex_body = value[2..].to_string();
+ if hex_body.len() % 2 != 0 {
+ hex_body.insert(0, '0');
+ }
+ if hex_body.len() > 64 {
+ return Err(CmaParserError::MalformedInput);
+ }
+ let mut bytes = [0u8; 32];
+ let offset = (64usize.saturating_sub(hex_body.len())) / 2;
+ let decoded = hex::decode(hex_body).map_err(|_| CmaParserError::MalformedInput)?;
+ if offset + decoded.len() > 32 {
+ return Err(CmaParserError::MalformedInput);
+ }
+ bytes[offset..offset + decoded.len()].copy_from_slice(&decoded);
+ Ok(U256::from_big_endian(&bytes))
+}
+
+fn parse_token_address(value: &str) -> Result {
+ if value.len() != 42 || !value.starts_with("0x") {
+ return Err(CmaParserError::MalformedInput);
+ }
+ value
+ .parse::()
+ .map_err(|_| CmaParserError::MalformedInput)
+}
+
+fn tokenize_u256_list(tokens: &[Token]) -> Result, CmaParserError> {
+ tokens
+ .iter()
+ .map(|item| match item {
+ Token::Uint(v) => Ok(*v),
+ _ => Err(CmaParserError::MalformedInput),
+ })
+ .collect()
+}
#[repr(u32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -20,8 +186,8 @@ pub enum TxHexCodes {
// Bytecode for solidity WithdrawErc1155Batch(address,uint256[],uint256[],bytes) = 50c80019
WithdrawErc1155Batch = 0x50c80019,
- // Bytecode for solidity TransferEther(uint256,bytes32,bytes) = 428c9c4d
- TransferEther = 0x428c9c4d,
+ // Bytecode for solidity TransferEther(bytes32,uint256,bytes) = ff67c903
+ TransferEther = 0xff67c903,
// Bytecode for solidity TransferErc20(address,bytes32,uint256,bytes) = 03d61dcd
TransferErc20 = 0x03d61dcd,
// Bytecode for solidity TransferErc721(address,bytes32,uint256,bytes) = af615a5a
@@ -41,7 +207,7 @@ impl TxHexCodes {
Self::WithdrawErc721 => "0x33acf293",
Self::WithdrawErc1155Single => "0x8bb0a811",
Self::WithdrawErc1155Batch => "0x50c80019",
- Self::TransferEther => "0x428c9c4d",
+ Self::TransferEther => "0xff67c903",
Self::TransferErc20 => "0x03d61dcd",
Self::TransferErc721 => "0xaf615a5a",
Self::TransferErc1155Single => "0xe1c913ed",
@@ -57,7 +223,7 @@ impl TxHexCodes {
"0x33acf293" => Self::WithdrawErc721,
"0x8bb0a811" => Self::WithdrawErc1155Single,
"0x50c80019" => Self::WithdrawErc1155Batch,
- "0x428c9c4d" => Self::TransferEther,
+ "0xff67c903" => Self::TransferEther,
"0x03d61dcd" => Self::TransferErc20,
"0xaf615a5a" => Self::TransferErc721,
"0xe1c913ed" => Self::TransferErc1155Single,
@@ -92,12 +258,6 @@ pub enum CmaVoucherFieldType {
Erc1155BatchVoucherFields(CmaParserErc1155BatchVoucherFields),
}
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-enum TokenType {
- Erc20,
- Erc721,
-}
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CmaParserInputType {
CmaParserInputTypeNone,
@@ -144,8 +304,10 @@ impl CmaParserInputType {
"Erc721Transfer" => CmaParserInputType::CmaParserInputTypeErc721Transfer,
"Erc1155SingleTransfer" => CmaParserInputType::CmaParserInputTypeErc1155SingleTransfer,
"Erc1155BatchTransfer" => CmaParserInputType::CmaParserInputTypeErc1155BatchTransfer,
- "ledgerGetBalance" => CmaParserInputType::CmaParserInputTypeBalance,
- "ledgerGetTotalSupply" => CmaParserInputType::CmaParserInputTypeSupply,
+ "ledger_getBalance" | "ledgerGetBalance" => CmaParserInputType::CmaParserInputTypeBalance,
+ "ledger_getTotalSupply" | "ledgerGetTotalSupply" => {
+ CmaParserInputType::CmaParserInputTypeSupply
+ }
_ => CmaParserInputType::CmaParserInputTypeUnidentified,
}
}
@@ -164,9 +326,11 @@ pub enum CmaParserVoucherType {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CmaParserError {
Success,
+ Unknown,
+ Exception,
IncompatibleInput,
MalformedInput,
- Unknown,
+ InvalidAmount,
Message(String),
}
@@ -174,10 +338,11 @@ impl CmaParserError {
pub fn from_code(code: i32) -> Self {
match code {
0 => Self::Success,
- -2001 => Self::IncompatibleInput,
- -2002 => Self::MalformedInput,
- -2003 => Self::Unknown,
- -2004 => Self::Message("Unknown error".to_string()),
+ -2001 => Self::Unknown,
+ -2002 => Self::Exception,
+ -2003 => Self::IncompatibleInput,
+ -2004 => Self::MalformedInput,
+ -2005 => Self::InvalidAmount,
_ => Self::Unknown,
}
}
@@ -185,18 +350,38 @@ impl CmaParserError {
pub fn to_code(self) -> i32 {
match self {
Self::Success => 0,
- Self::IncompatibleInput => -2001,
- Self::MalformedInput => -2002,
- Self::Unknown => -2003,
- Self::Message(_) => -2004,
+ Self::Unknown => -2001,
+ Self::Exception => -2002,
+ Self::IncompatibleInput => -2003,
+ Self::MalformedInput => -2004,
+ Self::InvalidAmount => -2005,
+ Self::Message(_) => -2002,
}
}
+
+ pub fn message(&self) -> String {
+ match self {
+ Self::Message(msg) => msg.clone(),
+ other => format!("{:?}", other),
+ }
+ }
+}
+
+pub fn cma_parser_get_last_error_message() -> Option {
+ LAST_PARSER_ERROR.with(|msg| {
+ let value = msg.borrow().clone();
+ if value.is_empty() {
+ None
+ } else {
+ Some(value)
+ }
+ })
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CmaVoucher {
pub destination: String,
- // pub value: String,
+ pub value: String,
pub payload: String,
}
@@ -232,20 +417,19 @@ pub struct CmaParserErc721VoucherFields {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CmaParserErc1155SingleVoucherFields {
pub token: Address,
- pub token_id: U256,
pub receiver: Address,
- pub value: U256,
+ pub token_id: U256,
pub amount: U256,
+ pub exec_layer_data: Bytes,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CmaParserErc1155BatchVoucherFields {
pub token: Address,
pub receiver: Address,
- pub count: usize,
pub token_ids: Vec,
- pub value: U256,
pub amounts: Vec,
+ pub exec_layer_data: Bytes,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -268,6 +452,7 @@ pub struct CmaParserErc721Deposit {
pub sender: Address,
pub token: Address,
pub token_id: U256,
+ pub base_layer_data: Bytes,
pub exec_layer_data: Bytes,
}
@@ -277,6 +462,7 @@ pub struct CmaParserErc1155SingleDeposit {
pub token: Address,
pub token_id: U256,
pub amount: U256,
+ pub base_layer_data: Bytes,
pub exec_layer_data: Bytes,
}
@@ -330,7 +516,6 @@ pub struct CmaParserErc1155BatchWithdrawal {
pub count: usize,
pub token_ids: Vec,
pub amounts: Vec,
- pub base_layer_data: String,
pub exec_layer_data: String,
}
@@ -359,8 +544,7 @@ pub struct CmaParserErc721Transfer {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CmaParserErc1155SingleTransfer {
- pub sender: Address,
- pub receiver: Address,
+ pub receiver: U256,
pub token: Address,
pub token_id: U256,
pub amount: U256,
@@ -369,27 +553,27 @@ pub struct CmaParserErc1155SingleTransfer {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CmaParserErc1155BatchTransfer {
- pub sender: Address,
- pub receiver: Address,
+ pub receiver: U256,
pub token: Address,
pub count: usize,
pub token_ids: Vec,
pub amounts: Vec,
- pub base_layer_data: String,
pub exec_layer_data: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CmaParserBalance {
- pub account: Address,
+ pub account: U256,
pub token: Address,
- pub token_ids: Option>,
+ pub token_id: U256,
+ pub exec_layer_data: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CmaParserSupply {
pub token: Address,
- pub token_ids: Vec,
+ pub token_id: U256,
+ pub exec_layer_data: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -421,49 +605,27 @@ pub struct CmaParserInput {
}
fn handle_unidentified_method(input: JsonValue) -> Result {
- let payload_hex = input["data"]["payload"]
+ let bytes = payload_bytes(&input)?;
+ let msg_sender = input["data"]["metadata"]["msg_sender"]
.as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
- let msg_sender =
- input["data"]["metadata"]["msg_sender"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from(
- "Invalid msg_sender address",
- )))?;
+ .ok_or(CmaParserError::MalformedInput)?;
let sender = Address::from_slice(
&hex::decode(msg_sender.trim_start_matches("0x"))
- .map_err(|e| CmaParserError::Message(format!("hex decode error: {}", e)))?,
+ .map_err(|_| CmaParserError::MalformedInput)?,
);
- let bytes = hex::decode(payload_hex.trim_start_matches("0x")).expect("invalid hex");
-
- return Ok(CmaParserInputData::Unidentified(CmaParserUnidentifiedInput {
+ Ok(CmaParserInputData::Unidentified(CmaParserUnidentifiedInput {
abi_encoded_bytes: bytes,
msg_sender: sender,
- }));
+ }))
}
fn handle_parse_ether_deposit(input: JsonValue) -> Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
- let payload = payload_hex.trim_start_matches("0x");
-
- let bytes = hex::decode(payload)
- .map_err(|e| CmaParserError::Message(format!("hex decode error: {}", e)))?;
-
- if bytes.len() < 20 + 32 {
- return Err(CmaParserError::Message(
- "Invalid payload length".to_string(),
- ));
- }
-
- let sender_bytes = &bytes[0..20];
- let sender = Address::from_slice(sender_bytes);
-
- let value_bytes = &bytes[20..52];
- let value = U256::from_big_endian(value_bytes);
+ let bytes = payload_bytes(&input)?;
+ require_len(&bytes, 52)?;
+ let sender = Address::from_slice(&bytes[0..20]);
+ let value = U256::from_big_endian(&bytes[20..52]);
let exec_layer_data = Bytes::from(bytes[52..].to_vec());
Ok(CmaParserInputData::EtherDeposit(CmaParserEtherDeposit {
@@ -473,119 +635,61 @@ fn handle_parse_ether_deposit(input: JsonValue) -> Result Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
- let payload = payload_hex.trim_start_matches("0x");
-
- let bytes = hex::decode(payload)
- .map_err(|e| CmaParserError::Message(format!("hex decode error: {}", e)))?;
+fn handle_parse_erc20_deposit(input: JsonValue) -> Result {
+ let bytes = payload_bytes(&input)?;
+ require_len(&bytes, 72)?;
- if bytes.len() < 20 + 20 + 32 {
- return Err(CmaParserError::Message(
- "Invalid payload length".to_string(),
- ));
- }
-
- let token = &bytes[1..21];
- let sender = &bytes[21..41];
- let amount_bytes = &bytes[41..73];
- let amount = U256::from_big_endian(amount_bytes);
- let exec_layer_data = Bytes::from(bytes[73..].to_vec());
+ let token = Address::from_slice(&bytes[0..20]);
+ let sender = Address::from_slice(&bytes[20..40]);
+ let amount = U256::from_big_endian(&bytes[40..72]);
+ let exec_layer_data = Bytes::from(bytes[72..].to_vec());
- match t_type {
- TokenType::Erc20 => Ok(CmaParserInputData::Erc20Deposit(CmaParserErc20Deposit {
- sender: Address::from_slice(sender),
- token: Address::from_slice(token),
- amount,
- exec_layer_data,
- })),
- TokenType::Erc721 => Ok(CmaParserInputData::Erc721Deposit(CmaParserErc721Deposit {
- sender: Address::from_slice(sender),
- token: Address::from_slice(token),
- token_id: amount,
- exec_layer_data,
- })),
- }
+ Ok(CmaParserInputData::Erc20Deposit(CmaParserErc20Deposit {
+ sender,
+ token,
+ amount,
+ exec_layer_data,
+ }))
}
-fn handle_parse_erc721_deposit(
- input: JsonValue,
- t_type: TokenType,
-) -> Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
- let payload = payload_hex.trim_start_matches("0x");
+fn handle_parse_erc721_deposit(input: JsonValue) -> Result {
+ let bytes = payload_bytes(&input)?;
+ require_len(&bytes, 72)?;
- let bytes = hex::decode(payload)
- .map_err(|e| CmaParserError::Message(format!("hex decode error: {}", e)))?;
+ let token = Address::from_slice(&bytes[0..20]);
+ let sender = Address::from_slice(&bytes[20..40]);
+ let token_id = U256::from_big_endian(&bytes[40..72]);
+ let (base_layer_data, exec_layer_data) = decode_abi_tail_two_bytes(&bytes[72..])?;
- if bytes.len() < 20 + 20 + 32 {
- return Err(CmaParserError::Message(
- "Invalid payload length".to_string(),
- ));
- }
-
- let token = &bytes[0..20];
- let sender = &bytes[20..40];
- let amount_bytes = &bytes[40..72];
- let amount = U256::from_big_endian(amount_bytes);
- let exec_layer_data = Bytes::from(bytes[72..].to_vec());
-
- match t_type {
- TokenType::Erc20 => Ok(CmaParserInputData::Erc20Deposit(CmaParserErc20Deposit {
- sender: Address::from_slice(sender),
- token: Address::from_slice(token),
- amount,
- exec_layer_data,
- })),
- TokenType::Erc721 => Ok(CmaParserInputData::Erc721Deposit(CmaParserErc721Deposit {
- sender: Address::from_slice(sender),
- token: Address::from_slice(token),
- token_id: amount,
- exec_layer_data,
- })),
- }
+ Ok(CmaParserInputData::Erc721Deposit(CmaParserErc721Deposit {
+ sender,
+ token,
+ token_id,
+ base_layer_data,
+ exec_layer_data,
+ }))
}
fn handle_parse_erc1155_single_deposit(
input: JsonValue,
) -> Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
- let payload = payload_hex.trim_start_matches("0x");
-
- let bytes = hex::decode(payload)
- .map_err(|e| CmaParserError::Message(format!("hex decode error: {}", e)))?;
+ let bytes = payload_bytes(&input)?;
+ require_len(&bytes, 104)?;
- if bytes.len() < 20 + 20 + 32 + 32 {
- return Err(CmaParserError::Message(
- "Invalid payload length".to_string(),
- ));
- }
-
- let token = &bytes[0..20];
- let sender = &bytes[20..40];
- let token_id_bytes = &bytes[40..72];
- let token_id = U256::from_big_endian(token_id_bytes);
- let amount_bytes = &bytes[72..104];
- let amount = U256::from_big_endian(amount_bytes);
- let _base_layer_data = Bytes::from(bytes[104..136].to_vec());
- let exec_layer_data = Bytes::from(bytes[136..].to_vec());
+ let token = Address::from_slice(&bytes[0..20]);
+ let sender = Address::from_slice(&bytes[20..40]);
+ let token_id = U256::from_big_endian(&bytes[40..72]);
+ let amount = U256::from_big_endian(&bytes[72..104]);
+ let (base_layer_data, exec_layer_data) = decode_abi_tail_two_bytes(&bytes[104..])?;
Ok(CmaParserInputData::Erc1155SingleDeposit(
CmaParserErc1155SingleDeposit {
- sender: Address::from_slice(sender),
- token: Address::from_slice(token),
+ sender,
+ token,
token_id,
amount,
+ base_layer_data,
exec_layer_data,
},
))
@@ -594,387 +698,463 @@ fn handle_parse_erc1155_single_deposit(
fn handle_parse_erc1155_batch_deposit(
input: JsonValue,
) -> Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
- let payload = payload_hex.trim_start_matches("0x");
-
- let bytes = hex::decode(payload)
- .map_err(|e| CmaParserError::Message(format!("hex decode error: {}", e)))?;
-
- if bytes.len() < 20 + 20 + 32 + 32 + 32 {
- return Err(CmaParserError::Message(
- "Invalid payload length".to_string(),
- ));
- }
-
- let u256_from = |b: &[u8]| U256::from_big_endian(b);
- let as_addr = |b: &[u8]| Address::from_slice(&b[12..32]);
-
- let token = as_addr(&bytes[0..32]);
- let sender = as_addr(&bytes[32..64]);
-
- let token_ids_offset = u256_from(&bytes[64..96]).as_usize();
- let values_offset = u256_from(&bytes[96..128]).as_usize();
- let base_offset = u256_from(&bytes[128..160]).as_usize();
- let exec_offset = u256_from(&bytes[160..192]).as_usize();
-
- let token_ids_len = u256_from(&bytes[token_ids_offset..token_ids_offset + 32]).as_usize();
- let mut token_ids = Vec::with_capacity(token_ids_len);
-
- let mut cursor = token_ids_offset + 32;
- for _ in 0..token_ids_len {
- token_ids.push(u256_from(&bytes[cursor..cursor + 32]));
- cursor += 32;
- }
-
- let values_len = u256_from(&bytes[values_offset..values_offset + 32]).as_usize();
- let mut values = Vec::with_capacity(values_len);
-
- let mut cursor2 = values_offset + 32;
- for _ in 0..values_len {
- values.push(u256_from(&bytes[cursor2..cursor2 + 32]));
- cursor2 += 32;
- }
-
- let base_len = u256_from(&bytes[base_offset..base_offset + 32]).as_usize();
- let base_start = base_offset + 32;
- let base_end = base_start + base_len;
- let base_layer_data = Bytes::from(bytes[base_start..base_end].to_vec());
-
- let exec_len = u256_from(&bytes[exec_offset..exec_offset + 32]).as_usize();
- let exec_start = exec_offset + 32;
- let exec_end = exec_start + exec_len;
- let exec_layer_data = Bytes::from(bytes[exec_start..exec_end].to_vec());
+ let bytes = payload_bytes(&input)?;
+ require_len(&bytes, 40)?;
- if token_ids_len == 0 || values_len == 0 || token_ids_len != values_len {
- return Err(CmaParserError::Message("Invalid payload data".to_string()));
- }
+ let token = Address::from_slice(&bytes[0..20]);
+ let sender = Address::from_slice(&bytes[20..40]);
+ let (token_ids, amounts, base_layer_data, exec_layer_data) =
+ decode_abi_tail_batch(&bytes[40..])?;
Ok(CmaParserInputData::Erc1155BatchDeposit(
CmaParserErc1155BatchDeposit {
sender,
token,
- count: token_ids_len,
+ count: token_ids.len(),
token_ids,
- amounts: values,
+ amounts,
base_layer_data,
exec_layer_data,
},
))
}
-fn handle_ether_withdrawal(input: JsonValue) -> Result {
- let payload_hex = input["data"]["payload"]
+fn withdrawal_receiver(input: &JsonValue) -> Result {
+ let msg_sender = input["data"]["metadata"]["msg_sender"]
.as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
- let msg_sender =
- input["data"]["metadata"]["msg_sender"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from(
- "Invalid msg_sender address",
- )))?;
-
- let bytes = hex::decode(payload_hex.trim_start_matches("0x")).expect("invalid hex");
- let (_first_4_bytes, encoded_args) = bytes.split_at(4);
-
- let param_types = vec![
- ParamType::Uint(256),
- ParamType::Bytes,
- ];
-
- let receiver = msg_sender
+ .ok_or(CmaParserError::MalformedInput)?;
+ msg_sender
.parse::()
- .map_err(|e| CmaParserError::Message(format!("msg_sender address error: {}", e)))?;
-
+ .map_err(|_| CmaParserError::MalformedInput)
+}
- let decoded = decode(¶m_types, encoded_args).map_err(|e| CmaParserError::Message(format!("ABI decode failed: {}", e)))?;
+fn decode_after_selector(bytes: &[u8]) -> Result<&[u8], CmaParserError> {
+ require_len(bytes, 4)?;
+ Ok(&bytes[4..])
+}
+
+fn exec_layer_hex(exec_layer_byte: &[u8]) -> String {
+ format!("0x{}", hex::encode(exec_layer_byte))
+}
+
+fn handle_ether_withdrawal(input: JsonValue) -> Result {
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
+ let receiver = withdrawal_receiver(&input)?;
+
+ let decoded = decode(
+ &[ParamType::Uint(256), ParamType::Bytes],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
let amount = match &decoded[0] {
- Token::Uint(v) => v,
- _ => return Err(CmaParserError::Message(format!("Error decoding uint256 amount"))),
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
};
-
let exec_layer_byte = match &decoded[1] {
- Token::Bytes(b) => b,
- _ => return Err(CmaParserError::Message(format!("Error decoding bytes execution layer data"))),
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
};
- let exec_layer_data = format!("0x{}", hex::encode(exec_layer_byte));
-
- return Ok(CmaParserInputData::EtherWithdrawal(
- CmaParserEtherWithdrawal {
- receiver,
- amount: amount.clone(),
- exec_layer_data,
- },
- ));
-
+ Ok(CmaParserInputData::EtherWithdrawal(CmaParserEtherWithdrawal {
+ receiver,
+ amount,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
+ }))
}
fn handle_erc20_withdrawal(input: JsonValue) -> Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
- let msg_sender =
- input["data"]["metadata"]["msg_sender"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from(
- "Invalid msg_sender address",
- )))?;
-
- let bytes = hex::decode(payload_hex.trim_start_matches("0x")).expect("invalid hex");
- let (_first_4_bytes, encoded_args) = bytes.split_at(4);
-
- let param_types = vec![
- ParamType::Address,
- ParamType::Uint(256),
- ParamType::Bytes,
- ];
-
- let receiver = msg_sender
- .parse::()
- .map_err(|e| CmaParserError::Message(format!("msg_sender address error: {}", e)))?;
-
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
+ let receiver = withdrawal_receiver(&input)?;
- let decoded = decode(¶m_types, encoded_args).map_err(|e| CmaParserError::Message(format!("ABI decode failed: {}", e)))?;
+ let decoded = decode(
+ &[ParamType::Address, ParamType::Uint(256), ParamType::Bytes],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
- let token = match &decoded[0] {
+ let token = match &decoded[0] {
Token::Address(v) => *v,
- _ => return Err(CmaParserError::Message(format!("Error decoding receiver address"))),
+ _ => return Err(CmaParserError::MalformedInput),
};
-
let amount = match &decoded[1] {
Token::Uint(v) => *v,
- _ => return Err(CmaParserError::Message(format!("Error decoding uint256 amount"))),
+ _ => return Err(CmaParserError::MalformedInput),
};
-
let exec_layer_byte = match &decoded[2] {
- Token::Bytes(b) => b,
- _ => return Err(CmaParserError::Message(format!("Error decoding bytes execution layer data"))),
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
};
- let exec_layer_data = format!("0x{}", hex::encode(exec_layer_byte));
-
- return Ok(CmaParserInputData::Erc20Withdrawal(
- CmaParserErc20Withdrawal {
- receiver,
- token,
- amount,
- exec_layer_data,
- },
- ))
+ Ok(CmaParserInputData::Erc20Withdrawal(CmaParserErc20Withdrawal {
+ receiver,
+ token,
+ amount,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
+ }))
}
fn handle_erc721_withdrawal(input: JsonValue) -> Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
- let msg_sender =
- input["data"]["metadata"]["msg_sender"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from(
- "Invalid msg_sender address",
- )))?;
-
- let bytes = hex::decode(payload_hex.trim_start_matches("0x")).expect("invalid hex");
- let (_first_4_bytes, encoded_args) = bytes.split_at(4);
-
- let param_types = vec![
- ParamType::Address,
- ParamType::Uint(256),
- ParamType::Bytes,
- ];
-
- let receiver = msg_sender
- .parse::()
- .map_err(|e| CmaParserError::Message(format!("msg_sender address error: {}", e)))?;
-
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
+ let receiver = withdrawal_receiver(&input)?;
- let decoded = decode(¶m_types, encoded_args).map_err(|e| CmaParserError::Message(format!("ABI decode failed: {}", e)))?;
+ let decoded = decode(
+ &[ParamType::Address, ParamType::Uint(256), ParamType::Bytes],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
- let token = match &decoded[0] {
+ let token = match &decoded[0] {
Token::Address(v) => *v,
- _ => return Err(CmaParserError::Message(format!("Error decoding receiver address"))),
+ _ => return Err(CmaParserError::MalformedInput),
};
-
let token_id = match &decoded[1] {
Token::Uint(v) => *v,
- _ => return Err(CmaParserError::Message(format!("Error decoding uint256 token id"))),
+ _ => return Err(CmaParserError::MalformedInput),
};
-
let exec_layer_byte = match &decoded[2] {
- Token::Bytes(b) => b,
- _ => return Err(CmaParserError::Message(format!("Error decoding bytes execution layer data"))),
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
};
- let exec_layer_data = format!("0x{}", hex::encode(exec_layer_byte));
+ Ok(CmaParserInputData::Erc721Withdrawal(CmaParserErc721Withdrawal {
+ receiver,
+ token,
+ token_id,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
+ }))
+}
+
+fn handle_erc1155_single_withdrawal(
+ input: JsonValue,
+) -> Result {
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
+ let receiver = withdrawal_receiver(&input)?;
+
+ let decoded = decode(
+ &[
+ ParamType::Address,
+ ParamType::Uint(256),
+ ParamType::Uint(256),
+ ParamType::Bytes,
+ ],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
+
+ let token = match &decoded[0] {
+ Token::Address(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let token_id = match &decoded[1] {
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let amount = match &decoded[2] {
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let exec_layer_byte = match &decoded[3] {
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
+ };
- return Ok(CmaParserInputData::Erc721Withdrawal(
- CmaParserErc721Withdrawal {
+ Ok(CmaParserInputData::Erc1155SingleWithdrawal(
+ CmaParserErc1155SingleWithdrawal {
receiver,
token,
token_id,
- exec_layer_data,
+ amount,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
},
))
}
-fn handle_ether_transfer(input: JsonValue) -> Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
+fn handle_erc1155_batch_withdrawal(
+ input: JsonValue,
+) -> Result {
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
+ let receiver = withdrawal_receiver(&input)?;
+
+ let decoded = decode(
+ &[
+ ParamType::Address,
+ ParamType::Array(Box::new(ParamType::Uint(256))),
+ ParamType::Array(Box::new(ParamType::Uint(256))),
+ ParamType::Bytes,
+ ],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
+
+ let token = match &decoded[0] {
+ Token::Address(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let token_ids = match &decoded[1] {
+ Token::Array(items) => tokenize_u256_list(items)?,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let amounts = match &decoded[2] {
+ Token::Array(items) => tokenize_u256_list(items)?,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let exec_layer_byte = match &decoded[3] {
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+
+ if token_ids.len() != amounts.len() || token_ids.is_empty() {
+ return Err(CmaParserError::MalformedInput);
+ }
- let bytes = hex::decode(payload_hex.trim_start_matches("0x")).expect("invalid hex");
- let (_first_4_bytes, encoded_args) = bytes.split_at(4);
+ Ok(CmaParserInputData::Erc1155BatchWithdrawal(
+ CmaParserErc1155BatchWithdrawal {
+ receiver,
+ token,
+ count: token_ids.len(),
+ token_ids,
+ amounts,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
+ },
+ ))
+}
- let param_types = vec![
- ParamType::Uint(256),
- ParamType::FixedBytes(32),
- ParamType::Bytes
- ];
+fn handle_ether_transfer(input: JsonValue) -> Result {
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
- let decoded = decode(¶m_types, encoded_args).map_err(|e| CmaParserError::Message(format!("ABI decode failed: {}", e)))?;
+ let decoded = decode(
+ &[ParamType::Uint(256), ParamType::Uint(256), ParamType::Bytes],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
- let amount = match &decoded[0] {
+ let receiver = match &decoded[0] {
Token::Uint(v) => *v,
- _ => return Err(CmaParserError::Message(format!("Error decoding u256 amount"))),
+ _ => return Err(CmaParserError::MalformedInput),
};
-
- let receiver = match &decoded[1] {
- Token::FixedBytes(v) if v.len() == 32 => {
- U256::from_big_endian(v)
- }
- _ => {
- return Err(CmaParserError::Message(
- "Error decodeing bytes32 receiver id".to_string(),
- ))
- }
+ let amount = match &decoded[1] {
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
};
-
let exec_layer_byte = match &decoded[2] {
- Token::Bytes(b) => b,
- _ => return Err(CmaParserError::Message(format!("Error decoding bytes execution layer data"))),
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
};
- let exec_layer_data = format!("0x{}", hex::encode(exec_layer_byte));
-
Ok(CmaParserInputData::EtherTransfer(CmaParserEtherTransfer {
receiver,
amount,
- exec_layer_data,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
}))
}
fn handle_erc20_transfer(input: JsonValue) -> Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
-
- let bytes = hex::decode(payload_hex.trim_start_matches("0x")).expect("invalid hex");
- let (_first_4_bytes, encoded_args) = bytes.split_at(4);
-
- let param_types = vec![
- ParamType::Address,
- ParamType::FixedBytes(32),
- ParamType::Uint(256),
- ParamType::Bytes
- ];
-
- let decoded = decode(¶m_types, encoded_args).map_err(|e| CmaParserError::Message(format!("ABI decode failed: {}", e)))?;
- let token = match &decoded[0] {
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
+
+ let decoded = decode(
+ &[
+ ParamType::Address,
+ ParamType::Uint(256),
+ ParamType::Uint(256),
+ ParamType::Bytes,
+ ],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
+
+ let token = match &decoded[0] {
Token::Address(v) => *v,
- _ => return Err(CmaParserError::Message(format!("Error decoding token address"))),
+ _ => return Err(CmaParserError::MalformedInput),
};
-
let receiver = match &decoded[1] {
- Token::FixedBytes(v) if v.len() == 32 => {
- U256::from_big_endian(v)
- }
- _ => {
- return Err(CmaParserError::Message(
- "Error decodeing bytes32 receiver id".to_string(),
- ))
- }
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
};
-
- let amount = match &decoded[2] {
+ let amount = match &decoded[2] {
Token::Uint(v) => *v,
- _ => return Err(CmaParserError::Message(format!("Error decoding u256 amount"))),
+ _ => return Err(CmaParserError::MalformedInput),
};
-
-
let exec_layer_byte = match &decoded[3] {
- Token::Bytes(b) => b,
- _ => return Err(CmaParserError::Message(format!("Error decoding bytes execution layer data"))),
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
};
- let exec_layer_data = format!("0x{}", hex::encode(exec_layer_byte));
-
- return Ok(CmaParserInputData::Erc20Transfer(CmaParserErc20Transfer {
+ Ok(CmaParserInputData::Erc20Transfer(CmaParserErc20Transfer {
receiver,
token,
amount,
- exec_layer_data,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
}))
}
fn handle_erc721_transfer(input: JsonValue) -> Result {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
-
- let bytes = hex::decode(payload_hex.trim_start_matches("0x")).expect("invalid hex");
- let (_first_4_bytes, encoded_args) = bytes.split_at(4);
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
+
+ let decoded = decode(
+ &[
+ ParamType::Address,
+ ParamType::Uint(256),
+ ParamType::Uint(256),
+ ParamType::Bytes,
+ ],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
+
+ let token = match &decoded[0] {
+ Token::Address(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let receiver = match &decoded[1] {
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let token_id = match &decoded[2] {
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let exec_layer_byte = match &decoded[3] {
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
+ };
- let param_types = vec![
- ParamType::Address,
- ParamType::FixedBytes(32),
- ParamType::Uint(256),
- ParamType::Bytes
- ];
+ Ok(CmaParserInputData::Erc721Transfer(CmaParserErc721Transfer {
+ receiver,
+ token,
+ token_id,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
+ }))
+}
- let decoded = decode(¶m_types, encoded_args).map_err(|e| CmaParserError::Message(format!("ABI decode failed: {}", e)))?;
- let token = match &decoded[0] {
+fn handle_erc1155_single_transfer(
+ input: JsonValue,
+) -> Result {
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
+
+ let decoded = decode(
+ &[
+ ParamType::Address,
+ ParamType::Uint(256),
+ ParamType::Uint(256),
+ ParamType::Uint(256),
+ ParamType::Bytes,
+ ],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
+
+ let token = match &decoded[0] {
Token::Address(v) => *v,
- _ => return Err(CmaParserError::Message(format!("Error decoding token address"))),
+ _ => return Err(CmaParserError::MalformedInput),
};
-
let receiver = match &decoded[1] {
- Token::FixedBytes(v) if v.len() == 32 => {
- U256::from_big_endian(v)
- }
- _ => {
- return Err(CmaParserError::Message(
- "Error decodeing bytes32 receiver id".to_string(),
- ))
- }
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
};
-
- let token_id = match &decoded[2] {
+ let token_id = match &decoded[2] {
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let amount = match &decoded[3] {
Token::Uint(v) => *v,
- _ => return Err(CmaParserError::Message(format!("Error decoding u256 amount"))),
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let exec_layer_byte = match &decoded[4] {
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
};
+ Ok(CmaParserInputData::Erc1155SingleTransfer(
+ CmaParserErc1155SingleTransfer {
+ receiver,
+ token,
+ token_id,
+ amount,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
+ },
+ ))
+}
- let exec_layer_byte = match &decoded[3] {
- Token::Bytes(b) => b,
- _ => return Err(CmaParserError::Message(format!("Error decoding bytes execution layer data"))),
+fn handle_erc1155_batch_transfer(
+ input: JsonValue,
+) -> Result {
+ let bytes = payload_bytes(&input)?;
+ let encoded_args = decode_after_selector(&bytes)?;
+
+ let decoded = decode(
+ &[
+ ParamType::Address,
+ ParamType::Uint(256),
+ ParamType::Array(Box::new(ParamType::Uint(256))),
+ ParamType::Array(Box::new(ParamType::Uint(256))),
+ ParamType::Bytes,
+ ],
+ encoded_args,
+ )
+ .map_err(|_| CmaParserError::MalformedInput)?;
+
+ let token = match &decoded[0] {
+ Token::Address(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let receiver = match &decoded[1] {
+ Token::Uint(v) => *v,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let token_ids = match &decoded[2] {
+ Token::Array(items) => tokenize_u256_list(items)?,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let amounts = match &decoded[3] {
+ Token::Array(items) => tokenize_u256_list(items)?,
+ _ => return Err(CmaParserError::MalformedInput),
+ };
+ let exec_layer_byte = match &decoded[4] {
+ Token::Bytes(b) => b.as_slice(),
+ _ => return Err(CmaParserError::MalformedInput),
};
- let exec_layer_data = format!("0x{}", hex::encode(exec_layer_byte));
+ if token_ids.len() != amounts.len() || token_ids.is_empty() {
+ return Err(CmaParserError::MalformedInput);
+ }
- return Ok(CmaParserInputData::Erc721Transfer(
- CmaParserErc721Transfer {
+ Ok(CmaParserInputData::Erc1155BatchTransfer(
+ CmaParserErc1155BatchTransfer {
receiver,
token,
- token_id,
- exec_layer_data,
+ count: token_ids.len(),
+ token_ids,
+ amounts,
+ exec_layer_data: exec_layer_hex(exec_layer_byte),
},
))
}
-pub fn cma_decode_advance(req_type: CmaParserInputType, input: JsonValue) -> Result {
+pub fn cma_decode_advance(
+ req_type: CmaParserInputType,
+ input: JsonValue,
+) -> Result {
+ let result = cma_decode_advance_inner(req_type, input);
+ if let Err(ref err) = result {
+ set_last_parser_error(err.message());
+ }
+ result
+}
+
+fn cma_decode_advance_inner(
+ req_type: CmaParserInputType,
+ input: JsonValue,
+) -> Result {
// Determine the portal type based on msg_sender
match req_type {
CmaParserInputType::CmaParserInputTypeErc1155BatchDeposit => {
@@ -990,19 +1170,15 @@ pub fn cma_decode_advance(req_type: CmaParserInputType, input: JsonValue) -> Re
});
}
CmaParserInputType::CmaParserInputTypeErc721Deposit => {
- return handle_parse_erc721_deposit(input, TokenType::Erc721).map(|data| {
- CmaParserInput {
- req_type,
- input: data,
- }
+ return handle_parse_erc721_deposit(input).map(|data| CmaParserInput {
+ req_type,
+ input: data,
});
}
CmaParserInputType::CmaParserInputTypeErc20Deposit => {
- return handle_parse_erc20_deposit(input, TokenType::Erc20).map(|data| {
- CmaParserInput {
- req_type,
- input: data,
- }
+ return handle_parse_erc20_deposit(input).map(|data| CmaParserInput {
+ req_type,
+ input: data,
});
}
CmaParserInputType::CmaParserInputTypeEtherDeposit => {
@@ -1013,352 +1189,329 @@ pub fn cma_decode_advance(req_type: CmaParserInputType, input: JsonValue) -> Re
}
// IF CALLER IS NOT ANY OF THE ABOVE PORTALS, WE TRY TO PARSE THE PAYLOAD FOR WITHDRAWALS/TRANSFERS
CmaParserInputType::CmaParserInputTypeAuto => {
- let payload_hex = input["data"]["payload"]
- .as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
-
- let bytes = hex::decode(payload_hex.trim_start_matches("0x")).expect("invalid hex");
-
- let (first_4_bytes,_encoded_args) = bytes.split_at(4);
- let selector_str = format!("0x{}", hex::encode(first_4_bytes));
+ let bytes = payload_bytes(&input)?;
+ require_len(&bytes, 4)?;
+ let selector_str = format!("0x{}", hex::encode(&bytes[..4]));
let req_type = TxHexCodes::from_string(&selector_str).to_input_type();
- // Handle the request based on the determined type
- match req_type {
+ let result = match req_type {
CmaParserInputType::CmaParserInputTypeEtherWithdrawal => {
- return handle_ether_withdrawal(input).map(|data| CmaParserInput {
- req_type,
- input: data,
- });
+ handle_ether_withdrawal(input)
}
CmaParserInputType::CmaParserInputTypeErc20Withdrawal => {
- return handle_erc20_withdrawal(input).map(|data| CmaParserInput {
- req_type,
- input: data,
- });
+ handle_erc20_withdrawal(input)
}
CmaParserInputType::CmaParserInputTypeErc721Withdrawal => {
- return handle_erc721_withdrawal(input).map(|data| CmaParserInput {
- req_type,
- input: data,
- });
+ handle_erc721_withdrawal(input)
+ }
+ CmaParserInputType::CmaParserInputTypeErc1155SingleWithdrawal => {
+ handle_erc1155_single_withdrawal(input)
+ }
+ CmaParserInputType::CmaParserInputTypeErc1155BatchWithdrawal => {
+ handle_erc1155_batch_withdrawal(input)
}
CmaParserInputType::CmaParserInputTypeEtherTransfer => {
- return handle_ether_transfer(input).map(|data| CmaParserInput {
- req_type,
- input: data,
- });
+ handle_ether_transfer(input)
}
CmaParserInputType::CmaParserInputTypeErc20Transfer => {
- return handle_erc20_transfer(input).map(|data| CmaParserInput {
- req_type,
- input: data,
- });
+ handle_erc20_transfer(input)
}
CmaParserInputType::CmaParserInputTypeErc721Transfer => {
- return handle_erc721_transfer(input).map(|data| CmaParserInput {
- req_type,
- input: data,
- });
+ handle_erc721_transfer(input)
+ }
+ CmaParserInputType::CmaParserInputTypeErc1155SingleTransfer => {
+ handle_erc1155_single_transfer(input)
+ }
+ CmaParserInputType::CmaParserInputTypeErc1155BatchTransfer => {
+ handle_erc1155_batch_transfer(input)
}
CmaParserInputType::CmaParserInputTypeUnidentified => {
- return handle_unidentified_method(input).map(|data| CmaParserInput {
- req_type,
- input: data,
- });
+ handle_unidentified_method(input)
}
_ => Err(CmaParserError::IncompatibleInput),
- }
- }
- _ => {
- return Err(CmaParserError::Unknown)
+ };
+
+ result.map(|data| CmaParserInput { req_type, input: data })
}
+ _ => Err(CmaParserError::Unknown),
}
}
-fn handle_ledger_get_balance(parsed_json: JsonValue) -> Result {
- // Extract params array
+fn inspect_param_strings(parsed_json: &JsonValue) -> Result, CmaParserError> {
let params_val = &parsed_json["params"];
if !params_val.is_array() {
- return Err(CmaParserError::Message(
- "Invalid params: not an array".into(),
- ));
+ return Err(CmaParserError::MalformedInput);
}
+ params_val
+ .members()
+ .map(|member| {
+ member
+ .as_str()
+ .map(str::to_string)
+ .ok_or(CmaParserError::MalformedInput)
+ })
+ .collect()
+}
- // params[0] = account
- let account_str = params_val[0]
- .as_str()
- .ok_or_else(|| CmaParserError::Message("Invalid account param".into()))?;
-
- let account = account_str
- .parse::()
- .map_err(|e| CmaParserError::Message(format!("account address error: {}", e)))?;
+fn handle_ledger_get_balance(parsed_json: JsonValue) -> Result {
+ let params = inspect_param_strings(&parsed_json)?;
+ if params.is_empty() || params.len() > 4 {
+ return Err(CmaParserError::MalformedInput);
+ }
- // params[1] = token
- let token_str = params_val[1]
- .as_str()
- .ok_or_else(|| CmaParserError::Message("Invalid token param".into()))?;
+ let account = parse_hex_account_id(¶ms[0])?;
+ let mut balance = CmaParserBalance {
+ account,
+ token: Address::zero(),
+ token_id: U256::zero(),
+ exec_layer_data: String::new(),
+ };
- let token = token_str
- .parse::()
- .map_err(|e| CmaParserError::Message(format!("token address error: {}", e)))?;
-
- // params[2] = optional array containing token id's
- let mut token_id: Vec = Vec::new();
- if params_val.len() > 2 && params_val[2].is_array() {
- // params_val[2][0] might itself be an array of ids or params_val[2] might be the array
- if params_val[2][0].is_array() {
- for v in params_val[2][0].members() {
- let s = v
- .as_str()
- .ok_or_else(|| CmaParserError::Message("Invalid token id".into()))?;
- let id = U256::from_dec_str(s)
- .map_err(|e| CmaParserError::Message(format!("token id parse error: {}", e)))?;
- token_id.push(id);
- }
- } else {
- // handle array of strings/numbers or comma-separated string(s)
- for v in params_val[2].members() {
- if let Some(s) = v.as_str() {
- if s.contains(',') {
- for part in s.split(',').map(str::trim).filter(|p| !p.is_empty()) {
- let id = U256::from_dec_str(part).map_err(|e| {
- CmaParserError::Message(format!("token id parse error: {}", e))
- })?;
- token_id.push(id);
- }
- } else {
- let id = U256::from_dec_str(s).map_err(|e| {
- CmaParserError::Message(format!("token id parse error: {}", e))
- })?;
- token_id.push(id);
- }
- } else if v.is_number() {
- let id = U256::from_dec_str(&v.to_string()).map_err(|e| {
- CmaParserError::Message(format!("token id parse error: {}", e))
- })?;
- token_id.push(id);
- } else {
- return Err(CmaParserError::Message("Invalid token id format".into()));
- }
- }
- }
+ if params.len() >= 2 {
+ balance.token = parse_token_address(¶ms[1])?;
+ }
+ if params.len() >= 3 {
+ balance.token_id = parse_hex_token_id(¶ms[2])?;
+ }
+ if params.len() == 4 {
+ balance.exec_layer_data = params[3].clone();
}
- Ok(CmaParserBalance {
- account,
- token,
- token_ids: Some(token_id),
- })
+ Ok(balance)
}
fn handle_ledger_get_supply(parsed_json: JsonValue) -> Result {
- // Ensure params is an array
- let params_val = &parsed_json["params"];
- if !params_val.is_array() {
- return Err(CmaParserError::Message(
- "Invalid params: not an array".into(),
- ));
+ let params = if parsed_json["params"].is_array() {
+ inspect_param_strings(&parsed_json)?
+ } else {
+ Vec::new()
+ };
+
+ if params.len() > 3 {
+ return Err(CmaParserError::MalformedInput);
}
- // params[0] = token address string
- let token_str = params_val[0]
- .as_str()
- .ok_or_else(|| CmaParserError::Message("Invalid token param".into()))?;
+ let mut supply = CmaParserSupply {
+ token: Address::zero(),
+ token_id: U256::zero(),
+ exec_layer_data: String::new(),
+ };
- let token = token_str
- .parse::()
- .map_err(|e| CmaParserError::Message(format!("token address error: {}", e)))?;
-
- // params[1] = optional array containing token id's
- let mut token_id: Vec = Vec::new();
- if params_val.len() > 1 && params_val[1].is_array() {
- // params_val[1][0] might itself be an array of ids or params_val[1] might be the array
- if params_val[1][0].is_array() {
- for v in params_val[1][0].members() {
- let s = v
- .as_str()
- .ok_or_else(|| CmaParserError::Message("Invalid token id".into()))?;
- let id = U256::from_dec_str(s)
- .map_err(|e| CmaParserError::Message(format!("token id parse error: {}", e)))?;
- token_id.push(id);
- }
- } else {
- // handle array of strings/numbers or comma-separated string(s)
- for v in params_val[1].members() {
- if let Some(s) = v.as_str() {
- if s.contains(',') {
- for part in s.split(',').map(str::trim).filter(|p| !p.is_empty()) {
- let id = U256::from_dec_str(part).map_err(|e| {
- CmaParserError::Message(format!("token id parse error: {}", e))
- })?;
- token_id.push(id);
- }
- } else {
- let id = U256::from_dec_str(s).map_err(|e| {
- CmaParserError::Message(format!("token id parse error: {}", e))
- })?;
- token_id.push(id);
- }
- } else if v.is_number() {
- let id = U256::from_dec_str(&v.to_string()).map_err(|e| {
- CmaParserError::Message(format!("token id parse error: {}", e))
- })?;
- token_id.push(id);
- } else {
- return Err(CmaParserError::Message("Invalid token id format".into()));
- }
- }
- }
+ if !params.is_empty() {
+ supply.token = parse_token_address(¶ms[0])?;
+ }
+ if params.len() >= 2 {
+ supply.token_id = parse_hex_token_id(¶ms[1])?;
+ }
+ if params.len() == 3 {
+ supply.exec_layer_data = params[2].clone();
}
- Ok(CmaParserInputData::Supply(CmaParserSupply {
- token,
- token_ids: token_id,
- }))
+ Ok(CmaParserInputData::Supply(supply))
}
pub fn cma_decode_inspect(input: JsonValue) -> Result {
+ let result = cma_decode_inspect_inner(input);
+ if let Err(ref err) = result {
+ set_last_parser_error(err.message());
+ }
+ result
+}
+
+fn cma_decode_inspect_inner(input: JsonValue) -> Result {
let payload_hex = input["data"]["payload"]
.as_str()
- .ok_or(CmaParserError::Message(String::from("Invalid payload hex")))?;
+ .ok_or(CmaParserError::MalformedInput)?;
let payload_str = hex_to_string(payload_hex)
- .map_err(|e| CmaParserError::Message(format!("hex to string conversion error: {}", e)))?;
+ .map_err(|_| CmaParserError::MalformedInput)?;
let payload_json = json::parse(&payload_str)
- .map_err(|e| CmaParserError::Message(format!("Error parsing string to JSON: {}", e)))?;
+ .map_err(|_| CmaParserError::MalformedInput)?;
- let req_type: CmaParserInputType =
- CmaParserInputType::from_string(payload_json["method"].as_str().ok_or(
- CmaParserError::Message(String::from("Invalid inspection type")),
- )?);
+ let method = payload_json["method"]
+ .as_str()
+ .ok_or(CmaParserError::MalformedInput)?;
+ let req_type = CmaParserInputType::from_string(method);
+ if req_type == CmaParserInputType::CmaParserInputTypeUnidentified {
+ return Err(CmaParserError::IncompatibleInput);
+ }
match req_type {
CmaParserInputType::CmaParserInputTypeBalance => {
- return handle_ledger_get_balance(payload_json).map(|data| CmaParserInput {
+ handle_ledger_get_balance(payload_json).map(|data| CmaParserInput {
req_type,
input: CmaParserInputData::Balance(data),
- });
+ })
}
CmaParserInputType::CmaParserInputTypeSupply => {
- return handle_ledger_get_supply(payload_json).map(|data| CmaParserInput {
+ handle_ledger_get_supply(payload_json).map(|data| CmaParserInput {
req_type,
input: data,
- });
- }
- _ => {
- // Unsupported inspection type
- return Err(CmaParserError::IncompatibleInput);
+ })
}
+ _ => Err(CmaParserError::IncompatibleInput),
}
}
fn handle_ether_voucher_encoding(
voucher_request: &CmaVoucherFieldType,
) -> Result {
- if let CmaVoucherFieldType::EtherVoucherFields(fields) = &voucher_request {
- let payload = "0x".to_string();
-
+ if let CmaVoucherFieldType::EtherVoucherFields(fields) = voucher_request {
let mut value_bytes = [0u8; 32];
fields.amount.to_big_endian(&mut value_bytes);
- let voucher = CmaVoucher {
+ Ok(CmaVoucher {
destination: to_checksum(&fields.receiver, None),
- payload,
- };
-
- Ok(voucher)
+ value: format!("0x{}", hex::encode(value_bytes)),
+ payload: "0x".to_string(),
+ })
} else {
- Err(CmaParserError::Message(String::from(
- "Invalid voucher fields for Ether",
- )))
+ Err(CmaParserError::IncompatibleInput)
}
}
fn handle_erc20_voucher_encoding(
voucher_request: &CmaVoucherFieldType,
) -> Result {
- if let CmaVoucherFieldType::Erc20VoucherFields(fields) = &voucher_request {
- let token = fields.token;
+ if let CmaVoucherFieldType::Erc20VoucherFields(fields) = voucher_request {
+ let args = vec![Token::Address(fields.receiver), Token::Uint(fields.amount)];
+ let selector = &id("transfer(address,uint256)")[..4];
+ let mut payload_bytes = Vec::new();
+ payload_bytes.extend_from_slice(selector);
+ payload_bytes.extend_from_slice(&encode(&args));
- let args: Vec = vec![Token::Address(fields.receiver), Token::Uint(fields.amount)];
+ Ok(CmaVoucher {
+ destination: to_checksum(&fields.token, None),
+ value: "0x".to_string(),
+ payload: format!("0x{}", hex::encode(payload_bytes)),
+ })
+ } else {
+ Err(CmaParserError::IncompatibleInput)
+ }
+}
+
+fn handle_erc721_voucher_encoding(
+ app_address: Address,
+ voucher_request: &CmaVoucherFieldType,
+) -> Result {
+ if let CmaVoucherFieldType::Erc721VoucherFields(fields) = voucher_request {
+ let args = vec![
+ Token::Address(app_address),
+ Token::Address(fields.receiver),
+ Token::Uint(fields.token_id),
+ ];
+ let selector = &id("safeTransferFrom(address,address,uint256)")[..4];
+ let mut payload_bytes = Vec::new();
+ payload_bytes.extend_from_slice(selector);
+ payload_bytes.extend_from_slice(&encode(&args));
- let function_sig = "transfer(address,uint256)";
- let selector = &id(function_sig)[..4];
+ Ok(CmaVoucher {
+ destination: to_checksum(&fields.token, None),
+ value: "0x".to_string(),
+ payload: format!("0x{}", hex::encode(payload_bytes)),
+ })
+ } else {
+ Err(CmaParserError::IncompatibleInput)
+ }
+}
- let encoded_args = encode(&args);
+fn handle_erc1155_single_voucher_encoding(
+ app_address: Address,
+ voucher_request: &CmaVoucherFieldType,
+) -> Result {
+ if let CmaVoucherFieldType::Erc1155SingleVoucherFields(fields) = voucher_request {
+ let args = vec![
+ Token::Address(app_address),
+ Token::Address(fields.receiver),
+ Token::Uint(fields.token_id),
+ Token::Uint(fields.amount),
+ Token::Bytes(fields.exec_layer_data.to_vec()),
+ ];
+ let selector = &id("safeTransferFrom(address,address,uint256,uint256,bytes)")[..4];
let mut payload_bytes = Vec::new();
payload_bytes.extend_from_slice(selector);
- payload_bytes.extend_from_slice(&encoded_args);
- let payload = format!("0x{}", hex::encode(payload_bytes));
-
- let voucher = CmaVoucher {
- destination: format!("{:?}", token),
- payload: format!("{}", payload),
- };
- return Ok(voucher);
+ payload_bytes.extend_from_slice(&encode(&args));
+
+ Ok(CmaVoucher {
+ destination: to_checksum(&fields.token, None),
+ value: "0x".to_string(),
+ payload: format!("0x{}", hex::encode(payload_bytes)),
+ })
} else {
- Err(CmaParserError::Message(String::from(
- "Invalid voucher fields for ERC20",
- )))
+ Err(CmaParserError::IncompatibleInput)
}
}
-fn handle_erc721_voucher_encoding(
+fn handle_erc1155_batch_voucher_encoding(
+ app_address: Address,
voucher_request: &CmaVoucherFieldType,
) -> Result {
- if let CmaVoucherFieldType::Erc721VoucherFields(fields) = &voucher_request {
- let token = fields.token;
+ if let CmaVoucherFieldType::Erc1155BatchVoucherFields(fields) = voucher_request {
+ if fields.token_ids.len() != fields.amounts.len() || fields.token_ids.is_empty() {
+ return Err(CmaParserError::MalformedInput);
+ }
- let args: Vec = vec![
- Token::Address(fields.application_address),
+ let token_id_tokens: Vec = fields
+ .token_ids
+ .iter()
+ .map(|id| Token::Uint(*id))
+ .collect();
+ let amount_tokens: Vec = fields
+ .amounts
+ .iter()
+ .map(|amount| Token::Uint(*amount))
+ .collect();
+
+ let args = vec![
+ Token::Address(app_address),
Token::Address(fields.receiver),
- Token::Uint(fields.token_id.into()),
+ Token::Array(token_id_tokens),
+ Token::Array(amount_tokens),
+ Token::Bytes(fields.exec_layer_data.to_vec()),
];
- let function_sig = "transferFrom(address,address,uint256)";
- let selector = &id(function_sig)[..4];
- let encoded_args = encode(&args);
+ let selector = &id("safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)")[..4];
let mut payload_bytes = Vec::new();
payload_bytes.extend_from_slice(selector);
- payload_bytes.extend_from_slice(&encoded_args);
- let payload = format!("0x{}", hex::encode(payload_bytes));
-
- let voucher = CmaVoucher {
- destination: format!("{:?}", token),
- payload: format!("{}", payload),
- };
+ payload_bytes.extend_from_slice(&encode(&args));
- return Ok(voucher);
+ Ok(CmaVoucher {
+ destination: to_checksum(&fields.token, None),
+ value: "0x".to_string(),
+ payload: format!("0x{}", hex::encode(payload_bytes)),
+ })
} else {
- Err(CmaParserError::Message(String::from(
- "Invalid voucher fields for ERC721",
- )))
+ Err(CmaParserError::IncompatibleInput)
}
}
pub fn cma_encode_voucher(
req_type: CmaParserVoucherType,
+ app_address: Option,
voucher_request: CmaVoucherFieldType,
) -> Result {
- match req_type {
+ let result = match req_type {
CmaParserVoucherType::CmaParserVoucherTypeEther => {
- return handle_ether_voucher_encoding(&voucher_request);
+ handle_ether_voucher_encoding(&voucher_request)
}
CmaParserVoucherType::CmaParserVoucherTypeErc20 => {
- return handle_erc20_voucher_encoding(&voucher_request);
+ handle_erc20_voucher_encoding(&voucher_request)
}
CmaParserVoucherType::CmaParserVoucherTypeErc721 => {
- return handle_erc721_voucher_encoding(&voucher_request);
+ let app_address = app_address.ok_or(CmaParserError::MalformedInput)?;
+ handle_erc721_voucher_encoding(app_address, &voucher_request)
}
CmaParserVoucherType::CmaParserVoucherTypeErc1155Single => {
- // TODO Implement encoding logic for ERC1155 Single voucher
- return Err(CmaParserError::Message(String::from("Not Implemented yet")));
+ let app_address = app_address.ok_or(CmaParserError::MalformedInput)?;
+ handle_erc1155_single_voucher_encoding(app_address, &voucher_request)
}
CmaParserVoucherType::CmaParserVoucherTypeErc1155Batch => {
- // TODO Implement encoding logic for ERC1155 Batch voucher
- return Err(CmaParserError::Message(String::from("Not Implemented yet")));
- }
- CmaParserVoucherType::CmaParserVoucherTypeNone => {
- return Err(CmaParserError::Message(String::from("Not Implemented yet")))
+ let app_address = app_address.ok_or(CmaParserError::MalformedInput)?;
+ handle_erc1155_batch_voucher_encoding(app_address, &voucher_request)
}
+ CmaParserVoucherType::CmaParserVoucherTypeNone => Err(CmaParserError::IncompatibleInput),
+ };
+
+ if let Err(ref err) = result {
+ set_last_parser_error(err.message());
}
+ result
}
diff --git a/src/types.rs b/src/types.rs
index aadde74..3fa995a 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -126,114 +126,6 @@ impl U256CBindingsExt for U256 {
}
}
-// /// Ethereum address (20 bytes)
-// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-// pub struct Address([u8; 20]);
-
-// impl Address {
-// pub const LENGTH: usize = 20;
-
-// pub fn from_str(s: &str) -> Result {
-// let s = s.strip_prefix("0x").unwrap_or(s);
-// if s.len() != 40 {
-// return Err(format!("Address hex string must be 40 characters, got {}", s.len()));
-// }
-// let bytes = hex::decode(s).map_err(|e| format!("Invalid hex string: {}", e))?;
-// Self::from_slice(&bytes)
-// }
-
-// pub fn new(bytes: [u8; 20]) -> Self {
-// Address(bytes)
-// }
-
-// pub fn as_bytes(&self) -> &[u8; 20] {
-// &self.0
-// }
-
-// pub fn from_slice(slice: &[u8]) -> Result {
-// if slice.len() != 20 {
-// return Err(format!("Address must be 20 bytes, got {}", slice.len()));
-// }
-// let mut bytes = [0u8; 20];
-// bytes.copy_from_slice(slice);
-// Ok(Address(bytes))
-// }
-
-// pub fn to_c(&self) -> bindings::cmt_abi_address_t {
-// bindings::cmt_abi_address_t { data: self.0 }
-// }
-
-// pub fn from_c(c_addr: &bindings::cmt_abi_address_t) -> Self {
-// Address(c_addr.data)
-// }
-// }
-
-// impl fmt::Display for Address {
-// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-// write!(f, "0x{}", hex::encode(self.0))
-// }
-// }
-
-// /// U256 value (32 bytes, big-endian)
-// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-// pub struct U256([u8; 32]);
-
-// impl U256 {
-// pub const LENGTH: usize = 32;
-
-// pub fn new(bytes: [u8; 32]) -> Self {
-// U256(bytes)
-// }
-
-// pub fn zero() -> Self {
-// U256([0u8; 32])
-// }
-
-// pub fn from_u64(value: u64) -> Self {
-// let mut bytes = [0u8; 32];
-// // Big-endian encoding
-// bytes[24..32].copy_from_slice(&value.to_be_bytes());
-// U256(bytes)
-// }
-
-// pub fn to_u128(&self) -> Option {
-// // Check if the value fits in u128 (lower 16 bytes must be zero)
-// if self.0[..16].iter().any(|&b| b != 0) {
-// return None;
-// }
-// let mut bytes = [0u8; 16];
-// bytes.copy_from_slice(&self.0[16..32]);
-// Some(u128::from_be_bytes(bytes))
-// }
-
-// pub fn as_bytes(&self) -> &[u8; 32] {
-// &self.0
-// }
-
-// pub fn from_slice(slice: &[u8]) -> Result {
-// if slice.len() != 32 {
-// return Err(format!("U256 must be 32 bytes, got {}", slice.len()));
-// }
-// let mut bytes = [0u8; 32];
-// bytes.copy_from_slice(slice);
-// Ok(U256(bytes))
-// }
-
-// pub fn to_c(&self) -> bindings::cmt_abi_u256_t {
-// bindings::cmt_abi_u256_t { data: self.0 }
-// }
-
-// pub fn from_c(c_u256: &bindings::cmt_abi_u256_t) -> Self {
-// U256(c_u256.data)
-// }
-// }
-
-// impl fmt::Display for U256 {
-// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-// write!(f, "0x{}", hex::encode(self.0))
-// }
-// }
-
/// Account ID (32 bytes)
pub type AccountId = U256;
@@ -257,6 +149,7 @@ pub enum RetrieveOperation {
Find,
Create,
FindOrCreate,
+ FindAndRemove,
}
impl RetrieveOperation {
@@ -269,6 +162,9 @@ impl RetrieveOperation {
RetrieveOperation::FindOrCreate => {
bindings::cma_ledger_retrieve_operation_t_CMA_LEDGER_OP_FIND_OR_CREATE
}
+ RetrieveOperation::FindAndRemove => {
+ bindings::cma_ledger_retrieve_operation_t_CMA_LEDGER_OP_FIND_AND_REMOVE
+ }
}
}
}
@@ -277,20 +173,26 @@ impl RetrieveOperation {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssetType {
Id,
+ Base,
TokenAddress,
TokenAddressId,
+ TokenAddressIdAmount,
}
impl AssetType {
pub fn to_c(&self) -> bindings::cma_ledger_asset_type_t {
match self {
AssetType::Id => bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_ID,
+ AssetType::Base => bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_BASE,
AssetType::TokenAddress => {
bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_TOKEN_ADDRESS
}
AssetType::TokenAddressId => {
bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_TOKEN_ADDRESS_ID
}
+ AssetType::TokenAddressIdAmount => {
+ bindings::cma_ledger_asset_type_t_CMA_LEDGER_ASSET_TYPE_TOKEN_ADDRESS_ID_AMOUNT
+ }
}
}
}
@@ -317,12 +219,34 @@ impl AccountType {
}
}
-/// Helper to convert bytes to C bytes struct
-pub fn bytes_to_c_bytes(data: &[u8]) -> bindings::cmt_abi_bytes_t {
- bindings::cmt_abi_bytes_t {
- data: data.as_ptr() as *mut std::ffi::c_void,
- length: data.len(),
+/// Owned byte buffer that can be safely passed to C as `cmt_abi_bytes_t`.
+#[derive(Debug, Clone)]
+pub struct OwnedCBytes {
+ inner: Vec,
+}
+
+impl OwnedCBytes {
+ pub fn new(data: impl Into>) -> Self {
+ Self {
+ inner: data.into(),
+ }
}
+
+ pub fn as_c_bytes(&self) -> bindings::cmt_abi_bytes_t {
+ bindings::cmt_abi_bytes_t {
+ data: self.inner.as_ptr() as *mut std::ffi::c_void,
+ length: self.inner.len(),
+ }
+ }
+
+ pub fn as_slice(&self) -> &[u8] {
+ &self.inner
+ }
+}
+
+/// Helper to convert bytes into an owned C-compatible buffer wrapper.
+pub fn bytes_to_c_bytes(data: &[u8]) -> OwnedCBytes {
+ OwnedCBytes::new(data.to_vec())
}
/// Helper to safely extract bytes from C bytes struct
diff --git a/tests/ledger_tests.rs b/tests/ledger_tests.rs
index 43a8ac1..c68c4c4 100644
--- a/tests/ledger_tests.rs
+++ b/tests/ledger_tests.rs
@@ -645,6 +645,15 @@ fn test_find_nonexistent_account() {
}
}
+#[test]
+fn test_retrieve_ether_asset() {
+ let mut ledger = Ledger::new().expect("Failed to initialize ledger");
+ let asset_id = ledger
+ .retrieve_ether_assets()
+ .expect("Should create base ether asset");
+ assert!(asset_id.0 > 0, "Ether asset ID should be non-zero");
+}
+
#[test]
fn test_large_amounts() {
let mut ledger = Ledger::new().expect("Failed to initialize ledger");
diff --git a/tests/parser_tests.rs b/tests/parser_tests.rs
index f58913e..cc9279f 100644
--- a/tests/parser_tests.rs
+++ b/tests/parser_tests.rs
@@ -2,13 +2,20 @@ use ethers_core::abi::{encode, AbiParser, FixedBytes, Token};
use ethers_core::types::{Address, U256};
use ethers_core::utils::id;
use json::JsonValue;
-use libcma_binding_rust::helpers::{PortalMatcher, Portals, CARTESI_ADDRESSES};
use libcma_binding_rust::parser::{
cma_decode_advance, cma_decode_inspect, cma_encode_voucher, CmaParserErc20VoucherFields,
CmaParserErc721VoucherFields, CmaParserEtherVoucherFields, CmaParserInputData,
CmaParserInputType, CmaParserVoucherType, CmaVoucherFieldType,
};
+// Portal addresses used purely as the `msg_sender` of test inputs. Portal
+// matching is a consumer concern (the library no longer ships addresses);
+// `cma_decode_advance` selects the decoder from the explicit `req_type`, so the
+// exact value here does not affect decoding.
+const ETHER_PORTAL: &str = "0xc70076a466789B595b50959cdc261227F0D70051";
+const ERC20_PORTAL: &str = "0xc700D6aDd016eECd59d989C028214Eaa0fCC0051";
+const ERC721_PORTAL: &str = "0xc700d52F5290e978e9CAe7D1E092935263b60051";
+
// Helper to create a basic JsonValue input structure
fn create_test_input(msg_sender: &str, payload: &str) -> JsonValue {
let mut input = JsonValue::new_object();
@@ -34,12 +41,7 @@ fn test_ether_deposit_success() {
let amount = U256::from_dec_str("2000000000000000000").unwrap(); // 2 Ether in wei
let payload = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000001bc16d674ec80000".to_string(); // Sample payload from the ether portal
- let input = create_test_input(
- CARTESI_ADDRESSES
- .get_portal_address(Portals::EtherPortal)
- .unwrap(),
- &payload,
- );
+ let input = create_test_input(ETHER_PORTAL, &payload);
match cma_decode_advance(CmaParserInputType::CmaParserInputTypeEtherDeposit, input) {
Ok(result) => {
@@ -72,47 +74,70 @@ fn test_ether_deposit_success() {
// let sender: Address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
// .parse()
// .unwrap();
-// let amount = U256::from_dec_str("300000000000000000000").unwrap(); // 300 ERC20 tokens in wei
+// let amount = U256::from_dec_str("300000000000000000000").unwrap();
// let token_address: Address = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C"
// .parse()
-// .unwrap(); // TEST token address
-// let payload = "0xfbdb734ef6a23ad76863cba6f10d0c5cbbd8342cf39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000001043561a8829300000".to_string(); // Sample payload from the ERC20 portal
-
+// .unwrap();
+// let payload = "0xfbdb734ef6a23ad76863cba6f10d0c5cbbd8342cf39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000001043561a8829300000".to_string();
+//
// let input = create_test_input(
// CARTESI_ADDRESSES
// .get_portal_address(Portals::ERC20Portal)
// .unwrap(),
// &payload,
// );
-
+//
// match cma_decode_advance(CmaParserInputType::CmaParserInputTypeErc20Deposit, input) {
// Ok(result) => {
// let is_correct_method =
// result.req_type == CmaParserInputType::CmaParserInputTypeErc20Deposit;
// let is_correct_sender = if let CmaParserInputData::Erc20Deposit(deposit) = result.input
// {
-// if deposit.amount == amount
+// deposit.amount == amount
// && deposit.sender == sender
// && deposit.token == token_address
-// {
-// true
-// } else {
-// false
-// }
// } else {
// false
// };
-
-// assert_eq!(true, is_correct_method, "Expected ERC20 Deposit method");
-// assert_eq!(
-// true, is_correct_sender,
-// "Expected correct sender, token address and amount"
-// );
+//
+// assert!(is_correct_method, "Expected ERC20 Deposit method");
+// assert!(is_correct_sender, "Expected correct sender, token address and amount");
// }
// Err(e) => panic!("Expected success, got error: {:?}", e),
// }
// }
+#[test]
+fn test_erc20_deposit_success() {
+ let sender: Address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+ .parse()
+ .unwrap();
+ let amount = U256::from_dec_str("300000000000000000000").unwrap();
+ let token_address: Address = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C"
+ .parse()
+ .unwrap();
+ let payload = "0xfbdb734ef6a23ad76863cba6f10d0c5cbbd8342cf39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000001043561a8829300000".to_string();
+
+ let input = create_test_input(ERC20_PORTAL, &payload);
+
+ match cma_decode_advance(CmaParserInputType::CmaParserInputTypeErc20Deposit, input) {
+ Ok(result) => {
+ assert_eq!(
+ result.req_type,
+ CmaParserInputType::CmaParserInputTypeErc20Deposit
+ );
+ if let CmaParserInputData::Erc20Deposit(deposit) = result.input {
+ assert_eq!(deposit.amount, amount);
+ assert_eq!(deposit.sender, sender);
+ assert_eq!(deposit.token, token_address);
+ } else {
+ panic!("Expected ERC20 deposit payload");
+ }
+ }
+ Err(e) => panic!("Expected success, got error: {:?}", e),
+ }
+}
+
#[test]
fn test_erc721_deposit_success() {
let sender: Address = "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
@@ -124,12 +149,7 @@ fn test_erc721_deposit_success() {
.unwrap(); // Sample ERC721 token address
let payload = "0xba46623ad94ab45850c4ecba9555d26328917c3bf39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".to_string(); // Sample payload from the ERC721 portal
- let input = create_test_input(
- CARTESI_ADDRESSES
- .get_portal_address(Portals::ERC721Portal)
- .unwrap(),
- &payload,
- );
+ let input = create_test_input(ERC721_PORTAL, &payload);
match cma_decode_advance(CmaParserInputType::CmaParserInputTypeErc721Deposit, input) {
Ok(result) => {
let is_correct_method =
@@ -312,10 +332,10 @@ fn test_ether_transfer_success() {
let amount = U256::from_dec_str("1500000000000000000").unwrap(); // 1.5 Ether in wei
let abi_encoded_input = abi_encode_call(
- "TransferEther(uint256,bytes32,bytes)",
+ "TransferEther(bytes32,uint256,bytes)",
vec![
- Token::Uint(amount),
Token::FixedBytes(recipient),
+ Token::Uint(amount),
Token::Bytes(vec![0x0]),
],
)
@@ -472,13 +492,17 @@ fn test_ether_voucher_encoding_success() {
amount,
});
- match cma_encode_voucher(CmaParserVoucherType::CmaParserVoucherTypeEther, request) {
+ match cma_encode_voucher(CmaParserVoucherType::CmaParserVoucherTypeEther, None, request) {
Ok(voucher) => {
- // Basic checks on the voucher structure
assert!(
voucher.destination.to_lowercase() == receipient_string,
"Incorrect destination address in voucher"
);
+ assert_eq!(
+ voucher.value,
+ format!("0x{}", hex::encode(expected_value_bytes)),
+ "Incorrect value in voucher"
+ );
assert!(
voucher.payload == "0x",
"Payload should be empty for Ether transfer"
@@ -516,7 +540,7 @@ fn test_erc20_voucher_encoding_success() {
amount,
receiver: recipient,
});
- match cma_encode_voucher(CmaParserVoucherType::CmaParserVoucherTypeErc20, request) {
+ match cma_encode_voucher(CmaParserVoucherType::CmaParserVoucherTypeErc20, None, request) {
Ok(voucher) => {
// Basic checks on the voucher structure
assert!(
@@ -553,7 +577,7 @@ fn test_erc721_voucher_encoding_success() {
Token::Uint(token_id),
];
- let function_sig = "transferFrom(address,address,uint256)";
+ let function_sig = "safeTransferFrom(address,address,uint256)";
let selector = &id(function_sig)[..4];
let encoded_args = encode(&args);
@@ -568,7 +592,11 @@ fn test_erc721_voucher_encoding_success() {
receiver: recipient,
application_address,
});
- match cma_encode_voucher(CmaParserVoucherType::CmaParserVoucherTypeErc721, request) {
+ match cma_encode_voucher(
+ CmaParserVoucherType::CmaParserVoucherTypeErc721,
+ Some(application_address),
+ request,
+ ) {
Ok(voucher) => {
// Basic checks on the voucher structure
assert!(
@@ -592,7 +620,7 @@ fn test_ledger_get_balance_success() {
let erc20_token: Address = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C"
.parse()
.unwrap(); // TEST token address
- let payload = r#"{"method": "ledgerGetBalance", "params": ["0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C", [1,2]]}"#.to_string();
+ let payload = r#"{"method": "ledger_getBalance", "params": ["0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C", "0x1"]}"#.to_string();
// let payload = r#"{"method": "ledgerGetBalance", "params": ["0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C", []]}"#.to_string();
let payload_hex = hex::encode(payload.as_bytes());
@@ -603,14 +631,11 @@ fn test_ledger_get_balance_success() {
let is_correct_method =
result.req_type == CmaParserInputType::CmaParserInputTypeBalance;
let is_correct_address = if let CmaParserInputData::Balance(data) = result.input {
- if data.account == address
+ let mut expected_account = [0u8; 32];
+ expected_account[12..].copy_from_slice(address.as_bytes());
+ data.account == U256::from_big_endian(&expected_account)
&& data.token == erc20_token
- && data.token_ids == Some(vec![U256::from(1), U256::from(2)])
- {
- true
- } else {
- false
- }
+ && data.token_id == U256::from(1)
} else {
false
};
@@ -633,7 +658,7 @@ fn test_ledger_get_total_supply_success() {
let erc20_token: Address = "0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C"
.parse()
.unwrap(); // TEST token address
- let payload = r#"{"method": "ledgerGetTotalSupply", "params": ["0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C", [1,2]]}"#.to_string();
+ let payload = r#"{"method": "ledger_getTotalSupply", "params": ["0xFBdB734EF6a23aD76863CbA6f10d0C5CBBD8342C", "0x1"]}"#.to_string();
let payload_hex = hex::encode(payload.as_bytes());
let input = create_test_input(&address.to_string(), &payload_hex);
@@ -641,12 +666,7 @@ fn test_ledger_get_total_supply_success() {
Ok(result) => {
let is_correct_method = result.req_type == CmaParserInputType::CmaParserInputTypeSupply;
let is_correct_address = if let CmaParserInputData::Supply(data) = result.input {
- if data.token == erc20_token && data.token_ids == vec![U256::from(1), U256::from(2)]
- {
- true
- } else {
- false
- }
+ data.token == erc20_token && data.token_id == U256::from(1)
} else {
false
};
diff --git a/tests/parser_vectors.rs b/tests/parser_vectors.rs
new file mode 100644
index 0000000..4e33948
--- /dev/null
+++ b/tests/parser_vectors.rs
@@ -0,0 +1,164 @@
+//! Canonical parser vectors ported from `third_party/machine-asset-tools/tests/parser.c`.
+
+use ethers_core::types::{Address, U256};
+use json::JsonValue;
+use libcma_binding_rust::parser::{
+ cma_decode_advance, cma_decode_inspect, cma_encode_voucher, CmaParserErc721VoucherFields,
+ CmaParserEtherVoucherFields, CmaParserInputData, CmaParserInputType, CmaParserVoucherType,
+ CmaVoucherFieldType,
+};
+
+fn advance_input(payload_hex: &str) -> JsonValue {
+ let mut input = JsonValue::new_object();
+ input["data"]["metadata"]["msg_sender"] = "0x0000000000000000000000000000000000000001".into();
+ input["data"]["payload"] = payload_hex.into();
+ input
+}
+
+fn inspect_input(payload_json: &str) -> JsonValue {
+ let mut input = JsonValue::new_object();
+ input["data"]["metadata"]["msg_sender"] = "0x0000000000000000000000000000000000000001".into();
+ input["data"]["payload"] = hex::encode(payload_json.as_bytes()).into();
+ input
+}
+
+#[test]
+fn vector_ether_deposit_from_parser_c() {
+ let payload = concat!(
+ "0x",
+ "0000000000000000000000000000000000000001",
+ "0000000000000000000000000000000000000000000000000000000000000004"
+ );
+ let result = cma_decode_advance(
+ CmaParserInputType::CmaParserInputTypeEtherDeposit,
+ advance_input(payload),
+ )
+ .expect("ether deposit should decode");
+
+ if let CmaParserInputData::EtherDeposit(deposit) = result.input {
+ let mut expected_sender = [0u8; 20];
+ expected_sender[19] = 1;
+ assert_eq!(deposit.sender, Address::from(expected_sender));
+ assert_eq!(deposit.amount, U256::from(4u64));
+ assert!(deposit.exec_layer_data.is_empty());
+ } else {
+ panic!("expected ether deposit");
+ }
+}
+
+#[test]
+fn vector_erc20_deposit_from_parser_c() {
+ let payload = concat!(
+ "0x",
+ "00000000000000000000000000000000000000ff",
+ "0000000000000000000000000000000000000001",
+ "0000000000000000000000000000000000000000000000000000000000000004"
+ );
+ let result = cma_decode_advance(
+ CmaParserInputType::CmaParserInputTypeErc20Deposit,
+ advance_input(payload),
+ )
+ .expect("erc20 deposit should decode");
+
+ if let CmaParserInputData::Erc20Deposit(deposit) = result.input {
+ let mut expected_token = [0u8; 20];
+ expected_token[19] = 0xff;
+ let mut expected_sender = [0u8; 20];
+ expected_sender[19] = 1;
+ assert_eq!(deposit.token, Address::from(expected_token));
+ assert_eq!(deposit.sender, Address::from(expected_sender));
+ assert_eq!(deposit.amount, U256::from(4u64));
+ } else {
+ panic!("expected erc20 deposit");
+ }
+}
+
+#[test]
+fn vector_ether_transfer_from_parser_c() {
+ let payload = concat!(
+ "0x",
+ "ff67c903",
+ "0000000000000000000000000000000000000000000000000000000000000002",
+ "0000000000000000000000000000000000000000000000000000000000000011",
+ "0000000000000000000000000000000000000000000000000000000000000060",
+ "0000000000000000000000000000000000000000000000000000000000000000"
+ );
+ let result = cma_decode_advance(
+ CmaParserInputType::CmaParserInputTypeAuto,
+ advance_input(payload),
+ )
+ .expect("ether transfer should decode");
+
+ if let CmaParserInputData::EtherTransfer(transfer) = result.input {
+ assert_eq!(transfer.receiver, U256::from(2u64));
+ assert_eq!(transfer.amount, U256::from(17u64));
+ } else {
+ panic!("expected ether transfer");
+ }
+}
+
+#[test]
+fn vector_ledger_get_balance_from_parser_c() {
+ let payload = r#"{"method":"ledger_getBalance","params":["0x0000000000000000000000000000000000000001"]}"#;
+ let result =
+ cma_decode_inspect(inspect_input(payload)).expect("ledger_getBalance should decode");
+
+ if let CmaParserInputData::Balance(balance) = result.input {
+ assert_eq!(balance.account, U256::from(1u64));
+ } else {
+ panic!("expected balance inspect");
+ }
+}
+
+#[test]
+fn vector_erc721_voucher_from_parser_c() {
+ let app_address = Address::from_slice(&[
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xbe, 0xef,
+ ]);
+ let receiver = Address::from_slice(&[
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ ]);
+ let token = Address::from_slice(&[
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff,
+ ]);
+
+ let request = CmaVoucherFieldType::Erc721VoucherFields(CmaParserErc721VoucherFields {
+ token,
+ token_id: U256::from(1u64),
+ receiver,
+ application_address: app_address,
+ });
+
+ let voucher = cma_encode_voucher(
+ CmaParserVoucherType::CmaParserVoucherTypeErc721,
+ Some(app_address),
+ request,
+ )
+ .expect("erc721 voucher should encode");
+
+ assert!(voucher.payload.starts_with("0x42842e0e"));
+ assert_eq!(voucher.value, "0x");
+}
+
+#[test]
+fn vector_ether_voucher_value_from_parser_c() {
+ let receiver: Address = "0x3e157927fb178490941bb18adcdc4144e442e32a"
+ .parse()
+ .unwrap();
+ let amount = U256::from_dec_str("1500000000000000000").unwrap();
+ let request = CmaVoucherFieldType::EtherVoucherFields(CmaParserEtherVoucherFields {
+ receiver,
+ amount,
+ });
+
+ let voucher = cma_encode_voucher(CmaParserVoucherType::CmaParserVoucherTypeEther, None, request)
+ .expect("ether voucher should encode");
+
+ let mut expected_value = [0u8; 32];
+ amount.to_big_endian(&mut expected_value);
+ assert_eq!(
+ voucher.value,
+ format!("0x{}", hex::encode(expected_value))
+ );
+ assert_eq!(voucher.payload, "0x");
+}