Skip to content

Commit

Permalink
Adds ability to provide abi in signing input
Browse files Browse the repository at this point in the history
  • Loading branch information
gupnik committed Dec 30, 2024
1 parent 7b1bee5 commit 5ca47be
Show file tree
Hide file tree
Showing 9 changed files with 344 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ This list is generated from [./registry.json](../registry.json)
| 10004689 | IoTeX EVM | IOTX | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/iotexevm/info/logo.png" width="32" /> | <https://iotex.io/> |
| 10007000 | NativeZetaChain | ZETA | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/zetachain/info/logo.png" width="32" /> | <https://www.zetachain.com/> |
| 10007700 | NativeCanto | CANTO | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/nativecanto/info/logo.png" width="32" /> | <https://canto.io/> |
| 10008217 | Kaia | KAIA | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/klaytn/info/logo.png" width="32" /> | <https://kaia.io> |
| 10008217 | Kaia | KAIA | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/kaia/info/logo.png" width="32" /> | <https://kaia.io> |
| 10009000 | Avalanche C-Chain | AVAX | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/avalanchec/info/logo.png" width="32" /> | <https://www.avalabs.org/> |
| 10009001 | Evmos | EVMOS | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/evmos/info/logo.png" width="32" /> | <https://evmos.org/> |
| 10042170 | Arbitrum Nova | ETH | <img src="https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/arbitrumnova/info/logo.png" width="32" /> | <https://nova.arbitrum.io> |
Expand Down
1 change: 1 addition & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/chains/tw_aptos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1"
serde_json = "1.0"
tw_coin_entry = { path = "../../tw_coin_entry" }
tw_encoding = { path = "../../tw_encoding" }
Expand Down
200 changes: 200 additions & 0 deletions rust/chains/tw_aptos/src/aptos_move_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use anyhow::format_err;
use move_core_types::{language_storage::TypeTag, parser::parse_type_tag};
use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;
use tw_encoding::hex;

/// Hex encoded bytes to allow for having bytes represented in JSON
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HexEncodedBytes(pub Vec<u8>);

impl HexEncodedBytes {
pub fn json(&self) -> anyhow::Result<serde_json::Value> {
Ok(serde_json::to_value(self)?)
}
}

impl FromStr for HexEncodedBytes {
type Err = anyhow::Error;

fn from_str(s: &str) -> anyhow::Result<Self, anyhow::Error> {
let hex_str = if let Some(hex) = s.strip_prefix("0x") {
hex
} else {
s
};
Ok(Self(hex::decode(hex_str).map_err(|e| {
format_err!(
"decode hex-encoded string({:?}) failed, caused by error: {}",
s,
e
)
})?))
}
}

impl fmt::Display for HexEncodedBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "0x{}", hex::encode(&self.0, false))?;
Ok(())
}
}

impl Serialize for HexEncodedBytes {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.to_string().serialize(serializer)
}
}

impl<'de> Deserialize<'de> for HexEncodedBytes {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
s.parse().map_err(D::Error::custom)
}
}

impl From<Vec<u8>> for HexEncodedBytes {
fn from(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}

impl From<HexEncodedBytes> for Vec<u8> {
fn from(bytes: HexEncodedBytes) -> Self {
bytes.0
}
}

/// An enum of Move's possible types on-chain
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MoveType {
/// A bool type
Bool,
/// An 8-bit unsigned int
U8,
/// A 16-bit unsigned int
U16,
/// A 32-bit unsigned int
U32,
/// A 64-bit unsigned int
U64,
/// A 128-bit unsigned int
U128,
/// A 256-bit unsigned int
U256,
/// A 32-byte account address
Address,
/// A Vector of [`MoveType`]
Vector { items: Box<MoveType> },
/// A reference
Reference { mutable: bool, to: Box<MoveType> },
/// A move type that couldn't be parsed
///
/// This prevents the parser from just throwing an error because one field
/// was unparsable, and gives the value in it.
Unparsable(String),
}

impl FromStr for MoveType {
type Err = anyhow::Error;

fn from_str(mut s: &str) -> Result<Self, Self::Err> {
let mut is_ref = false;
let mut is_mut = false;
if s.starts_with('&') {
s = &s[1..];
is_ref = true;
}
if is_ref && s.starts_with("mut ") {
s = &s[4..];
is_mut = true;
}
// Previously this would just crap out, but this meant the API could
// return a serialized version of an object and not be able to
// deserialize it using that same object.
let inner = match parse_type_tag(s) {
Ok(inner) => inner.into(),
Err(_e) => MoveType::Unparsable(s.to_string()),
};
if is_ref {
Ok(MoveType::Reference {
mutable: is_mut,
to: Box::new(inner),
})
} else {
Ok(inner)
}
}
}

impl From<TypeTag> for MoveType {
fn from(tag: TypeTag) -> Self {
match tag {
TypeTag::Bool => MoveType::Bool,
TypeTag::U8 => MoveType::U8,
TypeTag::U16 => MoveType::U16,
TypeTag::U32 => MoveType::U32,
TypeTag::U64 => MoveType::U64,
TypeTag::U256 => MoveType::U256,
TypeTag::U128 => MoveType::U128,
TypeTag::Address => MoveType::Address,
TypeTag::Vector(v) => MoveType::Vector {
items: Box::new(MoveType::from(*v)),
},
_ => MoveType::Unparsable(tag.to_string()),
}
}
}

impl From<&TypeTag> for MoveType {
fn from(tag: &TypeTag) -> Self {
match tag {
TypeTag::Bool => MoveType::Bool,
TypeTag::U8 => MoveType::U8,
TypeTag::U16 => MoveType::U16,
TypeTag::U32 => MoveType::U32,
TypeTag::U64 => MoveType::U64,
TypeTag::U128 => MoveType::U128,
TypeTag::U256 => MoveType::U256,
TypeTag::Address => MoveType::Address,
TypeTag::Vector(v) => MoveType::Vector {
items: Box::new(MoveType::from(v.as_ref())),
},
_ => MoveType::Unparsable(tag.to_string()),
}
}
}

impl TryFrom<MoveType> for TypeTag {
type Error = anyhow::Error;

fn try_from(tag: MoveType) -> anyhow::Result<Self> {
let ret = match tag {
MoveType::Bool => TypeTag::Bool,
MoveType::U8 => TypeTag::U8,
MoveType::U16 => TypeTag::U16,
MoveType::U32 => TypeTag::U32,
MoveType::U64 => TypeTag::U64,
MoveType::U128 => TypeTag::U128,
MoveType::U256 => TypeTag::U256,
MoveType::Address => TypeTag::Address,
MoveType::Vector { items } => TypeTag::Vector(Box::new((*items).try_into()?)),
_ => {
return Err(anyhow::anyhow!(
"Invalid move type for converting into `TypeTag`: {:?}",
&tag
))
},
};
Ok(ret)
}
}
1 change: 1 addition & 0 deletions rust/chains/tw_aptos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

pub mod address;
pub mod aptos_move_packages;
pub mod aptos_move_types;
pub mod constants;
pub mod entry;
mod serde_helper;
Expand Down
4 changes: 3 additions & 1 deletion rust/chains/tw_aptos/src/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,10 @@ impl TransactionFactory {
let v = serde_json::from_str::<Value>(&input.any_encoded)
.into_tw()
.context("Error decoding 'SigningInput::any_encoded' as JSON")?;
let abi =
serde_json::from_str::<Value>(&input.abi).unwrap_or(serde_json::json!([]));
if is_blind_sign {
let entry_function = EntryFunction::try_from(v)?;
let entry_function = EntryFunction::try_from((v, abi))?;
Ok(factory.payload(TransactionPayload::EntryFunction(entry_function)))
} else {
SigningError::err(SigningErrorType::Error_input_parse)
Expand Down
65 changes: 58 additions & 7 deletions rust/chains/tw_aptos/src/transaction_payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
//
// Copyright © 2017 Trust Wallet.

use crate::aptos_move_types::{HexEncodedBytes, MoveType};
use crate::serde_helper::vec_bytes;
use move_core_types::account_address::AccountAddress;
use move_core_types::identifier::Identifier;
use move_core_types::language_storage::{ModuleId, StructTag, TypeTag};
use move_core_types::parser::parse_transaction_argument;
use move_core_types::transaction_argument::TransactionArgument;
use move_core_types::u256;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::default::Default;
Expand Down Expand Up @@ -53,26 +56,39 @@ pub struct EntryFunction {
json_args: Value,
}

impl TryFrom<Value> for EntryFunction {
impl TryFrom<(Value, Value)> for EntryFunction {
type Error = EntryFunctionError;

fn try_from(value: Value) -> EntryFunctionResult<Self> {
fn try_from((value, abi): (Value, Value)) -> EntryFunctionResult<Self> {
let function_str = value["function"]
.as_str()
.ok_or(EntryFunctionError::MissingFunctionName)?;
let tag = StructTag::from_str(function_str)
.map_err(|_| EntryFunctionError::InvalidFunctionName)?;

let abi = abi
.as_array()
.ok_or(EntryFunctionError::MissingTypeArguments)?;
let get_abi_str =
|index: usize| -> Option<String> { abi.get(index)?.as_str().map(|s| s.to_string()) };

let args = value["arguments"]
.as_array()
.ok_or(EntryFunctionError::MissingArguments)?
.iter()
.map(|element| {
.enumerate()
.map(|(index, element)| {
let arg_str = element.to_string();
let arg = parse_transaction_argument(
arg_str.trim_start_matches('"').trim_end_matches('"'),
)
.map_err(|_| EntryFunctionError::InvalidArguments)?;
let arg_str = arg_str.trim_start_matches('"').trim_end_matches('"');

let arg = if let Some(abi_str) = get_abi_str(index) {
let abi_str = abi_str.trim_start_matches('"').trim_end_matches('"');
parse_argument(arg_str, abi_str)
.map_err(|_| EntryFunctionError::InvalidArguments)?
} else {
parse_transaction_argument(arg_str)
.map_err(|_| EntryFunctionError::InvalidArguments)?
};
serialize_argument(&arg).map_err(EntryFunctionError::from)
})
.collect::<EntryFunctionResult<Vec<Data>>>()?;
Expand All @@ -99,6 +115,41 @@ impl TryFrom<Value> for EntryFunction {
}
}

fn parse_argument(val: &str, abi_str: &str) -> anyhow::Result<TransactionArgument> {
let move_type: MoveType = abi_str.parse::<MoveType>()?;
Ok(match move_type {
MoveType::Bool => TransactionArgument::Bool(val.parse::<bool>()?),
MoveType::U8 => TransactionArgument::U8(val.parse::<u8>()?),
MoveType::U16 => TransactionArgument::U16(val.parse::<u16>()?),
MoveType::U32 => TransactionArgument::U32(val.parse::<u32>()?),
MoveType::U64 => TransactionArgument::U64(val.parse::<u64>()?),
MoveType::U128 => TransactionArgument::U128(val.parse::<u128>()?),
MoveType::U256 => TransactionArgument::U256(val.parse::<u256::U256>()?),
MoveType::Address => TransactionArgument::Address(AccountAddress::from_hex_literal(val)?),
MoveType::Vector { items } => parse_vector_argument(val, items)?,
_ => {
anyhow::bail!("unexpected move type {:?} for value {:?}", move_type, val)
},
})
}

fn parse_vector_argument(val: &str, layout: Box<MoveType>) -> anyhow::Result<TransactionArgument> {
let val = serde_json::to_value(val)?;
if matches!(*layout, MoveType::U8) {
Ok(TransactionArgument::U8Vector(
serde_json::from_value::<HexEncodedBytes>(val)?.into(),
))
} else if let Value::Array(list) = val {
let vals = list
.into_iter()
.map(|v| serde_json::from_value::<u8>(v).map_err(|_| anyhow::anyhow!("expected u8")))
.collect::<anyhow::Result<_>>()?;
Ok(TransactionArgument::U8Vector(vals))
} else {
anyhow::bail!("expected vector<{:?}>, but got: {:?}", layout, val)
}
}

fn serialize_argument(arg: &TransactionArgument) -> EncodingResult<Data> {
match arg {
TransactionArgument::U8(v) => bcs::encode(v),
Expand Down
Loading

0 comments on commit 5ca47be

Please sign in to comment.