Skip to content

Commit

Permalink
[TON]: Add functions to help to generate Jetton user address (#3922)
Browse files Browse the repository at this point in the history
* [TON]: Add functions to help to generate Jetton user address

* [TON]: Rename `TONAddress` module to `TONAddressConverter`

* Add ios, android tests

* [CI] Trigger CI

* [TON]: Fix Android test
  • Loading branch information
satoshiotomakan authored Jul 1, 2024
1 parent 13ff342 commit feafc27
Show file tree
Hide file tree
Showing 18 changed files with 591 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,19 @@ class TestTheOpenNetworkAddress {
val address = AnyAddress(addressString, CoinType.TON)
assertEquals(address.description(), "EQBm--PFwDv1yCeS-QTJ-L8oiUpqo9IT1BwgVptlSq3ts90Q")
}

@Test
fun testGenerateJettonAddress() {
val mainAddress = "UQBjKqthWBE6GEcqb_epTRFrQ1niS6Z1Z1MHMwR-mnAYRoYr"
val mainAddressBoc = TONAddressConverter.toBoc(mainAddress)
assertEquals(mainAddressBoc, "te6ccgICAAEAAQAAACQAAABDgAxlVWwrAidDCOVN/vUpoi1oazxJdM6s6mDmYI/TTgMI0A==")

// curl --location 'https://toncenter.com/api/v2/runGetMethod' --header 'Content-Type: application/json' --data \
// '{"address":"EQAvlWFDxGF2lXm67y4yzC17wYKD9A0guwPkMs1gOsM__NOT","method":"get_wallet_address","method":"get_wallet_address","stack":[["tvm.Slice","te6ccgICAAEAAQAAACQAAABDgAxlVWwrAidDCOVN/vUpoi1oazxJdM6s6mDmYI/TTgMI0A=="]]}'

// Parse the `get_wallet_address` RPC response.
val jettonAddressBocEncoded = "te6cckEBAQEAJAAAQ4AFvT5rqwxcbKfITqnkwL+go4Zi9bulRHAtLt4cjjFdK7B8L+Cq"
val jettonAddress = TONAddressConverter.fromBoc(jettonAddressBocEncoded)
assertEquals(jettonAddress, "UQAt6fNdWGLjZT5CdU8mBf0FHDMXrd0qI4FpdvDkcYrpXV5H")
}
}
34 changes: 34 additions & 0 deletions include/TrustWalletCore/TWTONAddressConverter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

#pragma once

#include "TWBase.h"
#include "TWString.h"

TW_EXTERN_C_BEGIN

/// TON address operations.
TW_EXPORT_CLASS
struct TWTONAddressConverter;

/// Converts a TON user address into a Bag of Cells (BoC) with a single root Cell.
/// The function is mostly used to request a Jetton user address via `get_wallet_address` RPC.
/// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user
///
/// \param address Address to be converted into a Bag Of Cells (BoC).
/// \return Pointer to a base64 encoded Bag Of Cells (BoC). Null if invalid address provided.
TW_EXPORT_STATIC_METHOD
TWString *_Nullable TWTONAddressConverterToBoc(TWString *_Nonnull address);

/// Parses a TON address from a Bag of Cells (BoC) with a single root Cell.
/// The function is mostly used to parse a Jetton user address received on `get_wallet_address` RPC.
/// https://docs.ton.org/develop/dapps/asset-processing/jettons#retrieving-jetton-wallet-addresses-for-a-given-user
///
/// \param boc Base64 encoded Bag Of Cells (BoC).
/// \return Pointer to a Jetton address.
TW_EXPORT_STATIC_METHOD
TWString *_Nullable TWTONAddressConverterFromBoc(TWString *_Nonnull boc);

TW_EXTERN_C_END
10 changes: 10 additions & 0 deletions rust/Cargo.lock

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

7 changes: 7 additions & 0 deletions rust/tw_memory/src/ffi/c_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ pub struct CBoolResult {
pub result: bool,
}

#[repr(C)]
pub struct CUInt8Result {
pub code: i32,
pub result: u8,
}

#[repr(C)]
pub struct CUInt64Result {
pub code: i32,
Expand All @@ -104,4 +110,5 @@ pub struct CUInt64Result {
impl_c_result!(CStrResult, *const c_char, core::ptr::null());
impl_c_result!(CStrMutResult, *mut c_char, core::ptr::null_mut());
impl_c_result!(CBoolResult, bool, false);
impl_c_result!(CUInt8Result, u8, 0);
impl_c_result!(CUInt64Result, u64, 0);
2 changes: 2 additions & 0 deletions rust/wallet_core_rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ utils = [
]

[dependencies]
bitreader = "0.3.8"
tw_any_coin = { path = "../tw_any_coin", optional = true }
tw_bitcoin = { path = "../tw_bitcoin", optional = true }
tw_coin_registry = { path = "../tw_coin_registry", optional = true }
Expand All @@ -49,5 +50,6 @@ uuid = { version = "1.7", features = ["v4"], optional = true }
serde_json = "1.0"
tw_any_coin = { path = "../tw_any_coin", features = ["test-utils"] }
tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] }
tw_encoding = { path = "../tw_encoding" }
tw_memory = { path = "../tw_memory", features = ["test-utils"] }
tw_number = { path = "../tw_number", features = ["helpers"] }
161 changes: 161 additions & 0 deletions rust/wallet_core_rs/src/ffi/utils/bit_reader_ffi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

#![allow(clippy::missing_safety_doc)]

use bitreader::{BitReader, BitReaderError};
use tw_memory::ffi::c_byte_array::{CByteArray, CByteArrayResult};
use tw_memory::ffi::c_result::{CUInt8Result, ErrorCode};
use tw_memory::ffi::tw_data::TWData;
use tw_memory::ffi::RawPtrTrait;
use tw_memory::Data;
use tw_misc::try_or_else;

#[derive(Debug, Eq, PartialEq)]
#[repr(C)]
pub enum CBitReaderCode {
Ok = 0,
/// Requested more bits than there are left in the byte slice at the current position.
NotEnoughData = 1,
/// Requested more bits than the returned variable can hold, for example more than 8 bits when
/// reading into a u8.
TooManyBitsForType = 2,
InalidInput = 3,
}

impl From<BitReaderError> for CBitReaderCode {
fn from(value: BitReaderError) -> Self {
match value {
BitReaderError::NotEnoughData { .. } => CBitReaderCode::NotEnoughData,
BitReaderError::TooManyBitsForType { .. } => CBitReaderCode::TooManyBitsForType,
}
}
}

impl From<CBitReaderCode> for ErrorCode {
fn from(error: CBitReaderCode) -> Self {
error as ErrorCode
}
}

/// BitReader reads data from a big-endian byte slice at the granularity of a single bit.
#[derive(Debug)]
pub struct TWBitReader {
buffer: Data,
bit_position: u64,
bit_len: u64,
}

impl TWBitReader {
pub fn with_relative_bit_len(buffer: Data, bit_len: u64) -> TWBitReader {
TWBitReader {
buffer,
bit_position: 0,
bit_len,
}
}

/// Read at most 8 bits into a u8.
pub fn read_u8(&mut self, bit_count: u8) -> Result<u8, CBitReaderCode> {
let mut reader = self.make_reader()?;
let res = reader.read_u8(bit_count)?;
// Update the bit position in case of success read.
self.bit_position += bit_count as u64;
Ok(res)
}

// Reads an entire slice of `byte_count` bytes. If there aren't enough bits remaining
// after the internal cursor's current position, returns none.
pub fn read_u8_slice(&mut self, byte_count: usize) -> Result<Data, CBitReaderCode> {
let mut reader = self.make_reader()?;

let mut res = vec![0_u8; byte_count];
reader.read_u8_slice(&mut res)?;

// Update the bit position in case of success read.
self.bit_position += byte_count as u64 * 8;
Ok(res)
}

pub fn is_finished(&self) -> bool {
self.bit_len == self.bit_position
}

fn make_reader(&self) -> Result<BitReader<'_>, CBitReaderCode> {
let mut reader = BitReader::new(&self.buffer).relative_reader_atmost(self.bit_len);
reader.skip(self.bit_position)?;
Ok(reader)
}
}

impl RawPtrTrait for TWBitReader {}

/// Constructs a new `TWBitReader` from a big-endian byte slice
/// that will not allow reading more than `bit_len` bits. It must be deleted at the end.
///
/// \param data big-endian byte slice to be read.
/// \param bit_len length this reader is allowed to read from the slice.
/// \return nullable pointer to a `TWBitReader` instance.
#[no_mangle]
pub unsafe extern "C" fn tw_bit_reader_create(
data: *const TWData,
bit_len: u64,
) -> *mut TWBitReader {
let data = try_or_else!(TWData::from_ptr_as_ref(data), std::ptr::null_mut);
TWBitReader::with_relative_bit_len(data.to_vec(), bit_len).into_ptr()
}

/// Deletes a `TWBitReader` and frees the memory.
/// \param reader a `TWBitReader` pointer.
#[no_mangle]
pub unsafe extern "C" fn tw_bit_reader_delete(reader: *mut TWBitReader) {
// Take the ownership back to rust and drop the owner.
let _ = TWBitReader::from_ptr(reader);
}

/// Read at most 8 bits into a u8.
///
/// \param reader a `TWBitReader` pointer.
/// \param bit_count number of bits to read. Expected from 1 to 8.
/// \return u8 or error.
#[no_mangle]
pub unsafe extern "C" fn tw_bit_reader_read_u8(
reader: *mut TWBitReader,
bit_count: u8,
) -> CUInt8Result {
let tw_reader = try_or_else!(
TWBitReader::from_ptr_as_mut(reader),
|| CUInt8Result::error(CBitReaderCode::InalidInput)
);
tw_reader.read_u8(bit_count).into()
}

/// Reads an entire slice of `byteCount` bytes. If there aren't enough bits remaining
/// after the internal cursor's current position, returns null.
///
/// \param reader a `TWBitReader` pointer.
/// \param byte_count number of bytes to read.
/// \return byte array or error.
#[no_mangle]
pub unsafe extern "C" fn tw_bit_reader_read_u8_slice(
reader: *mut TWBitReader,
byte_count: usize,
) -> CByteArrayResult {
let tw_reader = try_or_else!(TWBitReader::from_ptr_as_mut(reader), || {
CByteArrayResult::error(CBitReaderCode::InalidInput)
});
tw_reader
.read_u8_slice(byte_count)
.map(CByteArray::from)
.into()
}

/// Checks whether all bits were read.
///
/// \param reader a `TWBitReader` pointer.
/// \return whether all bits were read.
#[no_mangle]
pub unsafe extern "C" fn tw_bit_reader_finished(reader: *const TWBitReader) -> bool {
try_or_else!(TWBitReader::from_ptr_as_ref(reader), || true).is_finished()
}
1 change: 1 addition & 0 deletions rust/wallet_core_rs/src/ffi/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
//
// Copyright © 2017 Trust Wallet.

pub mod bit_reader_ffi;
pub mod uuid_ffi;
69 changes: 69 additions & 0 deletions rust/wallet_core_rs/tests/bit_reader.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

use tw_encoding::hex::{DecodeHex, ToHex};
use tw_memory::test_utils::tw_data_helper::TWDataHelper;
use wallet_core_rs::ffi::utils::bit_reader_ffi::{
tw_bit_reader_create, tw_bit_reader_finished, tw_bit_reader_read_u8,
tw_bit_reader_read_u8_slice, CBitReaderCode,
};

#[test]
fn test_tw_bit_reader_success() {
let ton_address_cell = "8005bd3e6bab0c5c6ca7c84ea9e4c0bfa0a38662f5bba544702d2ede1c8e315d2ba0"
.decode_hex()
.unwrap();
let ton_address_cell = TWDataHelper::create(ton_address_cell);

let reader = unsafe { tw_bit_reader_create(ton_address_cell.ptr(), 267) };
assert!(!reader.is_null());

let tp = unsafe { tw_bit_reader_read_u8(reader, 2) };
assert_eq!(tp.into_result(), Ok(2));

let res1 = unsafe { tw_bit_reader_read_u8(reader, 1) };
assert_eq!(res1.into_result(), Ok(0));

let wc = unsafe { tw_bit_reader_read_u8(reader, 8) };
assert_eq!(wc.into_result(), Ok(0));

assert!(!unsafe { tw_bit_reader_finished(reader) });

let hash_part = unsafe { tw_bit_reader_read_u8_slice(reader, 32).unwrap().into_vec() };
assert_eq!(
hash_part.to_hex(),
"2de9f35d5862e3653e42754f2605fd051c3317addd2a23816976f0e4718ae95d"
);

assert!(unsafe { tw_bit_reader_finished(reader) });
}

#[test]
fn test_tw_bit_reader_error() {
let bytes_len = 2;
// Less than two bytes.
let bits_len = 15;

let data = TWDataHelper::create(vec![1; bytes_len]);

let reader = unsafe { tw_bit_reader_create(data.ptr(), bits_len as u64) };
assert!(!reader.is_null());

// Cannot read u8 from 9 bits.
let res = unsafe { tw_bit_reader_read_u8(reader, 9) };
assert_eq!(
res.into_result().unwrap_err(),
CBitReaderCode::TooManyBitsForType as i32
);

// Read a dummy u8.
let _ = unsafe { tw_bit_reader_read_u8_slice(reader, 8) };

// Cannot read 8 bits as there are 7 bits left only.
let res = unsafe { tw_bit_reader_read_u8_slice(reader, 8) };
assert_eq!(
res.into_result().unwrap_err(),
CBitReaderCode::NotEnoughData as i32
);
}
39 changes: 39 additions & 0 deletions src/Everscale/CommonTON/BitReader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: Apache-2.0
//
// Copyright © 2017 Trust Wallet.

#include "BitReader.h"

namespace TW::CommonTON {

std::optional<BitReader> BitReader::createExact(const TW::Data &buffer, uint64_t bitLen) {
Rust::TWDataWrapper twData(buffer);
auto *readerPtr = Rust::tw_bit_reader_create(twData.get(), bitLen);
if (!readerPtr) {
return std::nullopt;
}

return BitReader(std::shared_ptr<Rust::TWBitReader>(readerPtr, Rust::tw_bit_reader_delete));
}

std::optional<uint8_t> BitReader::readU8(uint8_t bitCount) {
Rust::CUInt8ResultWrapper res = Rust::tw_bit_reader_read_u8(reader.get(), bitCount);
if (res.isErr()) {
return std::nullopt;
}
return res.unwrap().value;
}

std::optional<Data> BitReader::readU8Slice(uint64_t byteCount) {
Rust::CByteArrayResultWrapper res = Rust::tw_bit_reader_read_u8_slice(reader.get(), byteCount);
if (res.isErr()) {
return std::nullopt;
}
return res.unwrap().data;
}

bool BitReader::finished() const {
return Rust::tw_bit_reader_finished(reader.get());
}

} // namespace TW::CommonTON
Loading

1 comment on commit feafc27

@Endricfr
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uploading VID-20240630-WA0001.mp4…

Please sign in to comment.