Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Adds ability to take an ABI to handle EntryFunction JSON arguments correctly #4190

Merged
merged 14 commits into from
Jan 8, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,59 @@ class TestAptosSigner {
)
}

@Test
fun AptosTransactionBlindSigningWithABI() {
// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x1ee2aa55382bf6b5a9f7a7f2b2066e16979489c6b2868704a2cf2c482f12b5ca/payload?network=mainnet
val key =
"5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec".toHexBytesInByteString()

val payloadJson = """
{
"function": "0x9770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da3::controller::deposit",
"type_arguments": [
"0x1::aptos_coin::AptosCoin"
],
"arguments": [
"0x4d61696e204163636f756e74",
"10000000",
false
],
"type": "entry_function_payload"
}
""".trimIndent()
val signingInput = Aptos.SigningInput.newBuilder()
.setChainId(1)
.setExpirationTimestampSecs(1735902711)
.setGasUnitPrice(100)
.setMaxGasAmount(50000)
.setSender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30")
.setSequenceNumber(69)
.setAnyEncoded(payloadJson)
.setPrivateKey(key)
.setAbi("""
[
"vector<u8>",
"u64",
"bool"
]
""".trimIndent())
.build()

val result = AnySigner.sign(signingInput, CoinType.APTOS, Aptos.SigningOutput.parser())
assertEquals(
Numeric.cleanHexPrefix(Numeric.toHexString(result.rawTxn.toByteArray())),
"07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c577670000000001"
)
assertEquals(
Numeric.cleanHexPrefix(Numeric.toHexString(result.authenticator.signature.toByteArray())),
"13dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304"
)
assertEquals(
Numeric.cleanHexPrefix(Numeric.toHexString(result.encoded.toByteArray())),
"07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f304500000000000000029770fa9c725cbd97eb50b2be5f7416efdfd1f1554beb0750d4dae4c64e860da30a636f6e74726f6c6c6572076465706f736974010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00030d0c4d61696e204163636f756e74088096980000000000010050c30000000000006400000000000000f7c5776700000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4013dcf1636abd31996729ded4d3bf56e9c7869a7188df4f185cbcce42f0dc74b6e1b54d31703ee3babbea2ef72b3338b8c2866cec68cbd761ccc7f80910124304"
)
}

@Test
fun AptosTransactionSigning() {
// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet
Expand Down
242 changes: 242 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,242 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use move_core_types::{
account_address::AccountAddress,
identifier::Identifier,
language_storage::{StructTag, TypeTag},
parser::parse_type_tag,
};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use tw_encoding::EncodingError;

/// The address of an account
///
/// This is represented in a string as a 64 character hex string, sometimes
/// shortened by stripping leading 0s, and adding a 0x.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Address(AccountAddress);

impl From<AccountAddress> for Address {
fn from(address: AccountAddress) -> Self {
Self(address)
}
}

impl From<Address> for AccountAddress {
fn from(address: Address) -> Self {
address.0
}
}

impl From<&Address> for AccountAddress {
fn from(address: &Address) -> Self {
address.0
}
}
satoshiotomakan marked this conversation as resolved.
Show resolved Hide resolved

/// A wrapper of a Move identifier
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct IdentifierWrapper(pub Identifier);

impl From<IdentifierWrapper> for Identifier {
fn from(value: IdentifierWrapper) -> Identifier {
value.0
}
}

impl From<Identifier> for IdentifierWrapper {
fn from(value: Identifier) -> IdentifierWrapper {
Self(value)
}
}

impl From<&Identifier> for IdentifierWrapper {
fn from(value: &Identifier) -> IdentifierWrapper {
Self(value.clone())
}
}

/// A Move struct tag for referencing an onchain struct type
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MoveStructTag {
pub address: Address,
pub module: IdentifierWrapper,
pub name: IdentifierWrapper,
/// Generic type parameters associated with the struct
pub generic_type_params: Vec<MoveType>,
}

impl From<StructTag> for MoveStructTag {
fn from(tag: StructTag) -> Self {
Self {
address: tag.address.into(),
module: tag.module.into(),
name: tag.name.into(),
generic_type_params: tag.type_params.into_iter().map(MoveType::from).collect(),
}
}
}

impl From<&StructTag> for MoveStructTag {
fn from(tag: &StructTag) -> Self {
Self {
address: tag.address.into(),
module: IdentifierWrapper::from(&tag.module),
name: IdentifierWrapper::from(&tag.name),
generic_type_params: tag.type_params.iter().map(MoveType::from).collect(),
}
}
}

impl TryFrom<MoveStructTag> for StructTag {
type Error = EncodingError;

fn try_from(tag: MoveStructTag) -> Result<Self, Self::Error> {
Ok(Self {
address: tag.address.into(),
module: tag.module.into(),
name: tag.name.into(),
type_params: tag
.generic_type_params
.into_iter()
.map(|p| p.try_into())
.collect::<Result<Vec<TypeTag>, Self::Error>>()?,
})
}
}
satoshiotomakan marked this conversation as resolved.
Show resolved Hide resolved

/// 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,
/// An account signer
Signer,
/// A Vector of [`MoveType`]
Vector { items: Box<MoveType> },
/// A struct of [`MoveStructTag`]
Struct(MoveStructTag),
/// A generic type param with index
GenericTypeParam { index: u16 },
/// 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 = EncodingError;

fn from_str(mut s: &str) -> Result<Self, Self::Err> {
let mut is_ref = false;
gupnik marked this conversation as resolved.
Show resolved Hide resolved
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::Signer => MoveType::Signer,
TypeTag::Vector(v) => MoveType::Vector {
items: Box::new(MoveType::from(*v)),
},
TypeTag::Struct(v) => MoveType::Struct((*v).into()),
}
}
}

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::Signer => MoveType::Signer,
TypeTag::Vector(v) => MoveType::Vector {
items: Box::new(MoveType::from(v.as_ref())),
},
TypeTag::Struct(v) => MoveType::Struct((&**v).into()),
}
}
}
satoshiotomakan marked this conversation as resolved.
Show resolved Hide resolved

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

fn try_from(tag: MoveType) -> Result<Self, Self::Error> {
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::Signer => TypeTag::Signer,
MoveType::Vector { items } => TypeTag::Vector(Box::new((*items).try_into()?)),
MoveType::Struct(v) => TypeTag::Struct(Box::new(v.try_into()?)),
MoveType::GenericTypeParam { index: _ } => TypeTag::Address, // Dummy type, allows for Object<T>
_ => {
return Err(EncodingError::InvalidInput);
},
};
Ok(ret)
}
}
satoshiotomakan marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 5 additions & 0 deletions rust/chains/tw_aptos/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
//
// Copyright © 2017 Trust Wallet.

use move_core_types::{ident_str, identifier::IdentStr};

pub const GAS_UNIT_PRICE: u64 = 100;
pub const MAX_GAS_AMOUNT: u64 = 100_000_000;
pub const APTOS_SALT: &[u8] = b"APTOS::RawTransaction";

pub const OBJECT_MODULE: &IdentStr = ident_str!("object");
pub const OBJECT_STRUCT: &IdentStr = ident_str!("Object");
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::parse_with_abi(v, abi)?;
Ok(factory.payload(TransactionPayload::EntryFunction(entry_function)))
} else {
SigningError::err(SigningErrorType::Error_input_parse)
Expand Down
Loading
Loading