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

Add jetton wallet #18

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"nekoton-abi",
"nekoton-contracts",
"nekoton-derive",
"nekoton-jetton",
"nekoton-proto",
"nekoton-transport",
"nekoton-utils",
Expand All @@ -40,7 +41,7 @@ once_cell = "1.12.0"
parking_lot = "0.12.0"
pbkdf2 = { version = "0.12.2", optional = true }
quick_cache = "0.4.1"
rand = { version = "0.8", features = ["getrandom"] , optional = true }
rand = { version = "0.8", features = ["getrandom"], optional = true }
secstr = { version = "0.5.0", features = ["serde"], optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion nekoton-abi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ ton_abi = { git = "https://github.com/broxus/ton-labs-abi" }
ton_block = { git = "https://github.com/broxus/ton-labs-block.git" }
ton_executor = { git = "https://github.com/broxus/ton-labs-executor.git" }
ton_types = { git = "https://github.com/broxus/ton-labs-types.git" }
ton_vm = { git = "https://github.com/broxus/ton-labs-vm.git" }
ton_vm = { git = "https://github.com/broxus/ton-labs-vm.git", branch = "feature/jetton" }

nekoton-derive = { path = "../nekoton-derive", optional = true }
nekoton-utils = { path = "../nekoton-utils" }
Expand Down
4 changes: 4 additions & 0 deletions nekoton-contracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ ton_types = { git = "https://github.com/broxus/ton-labs-types.git" }
ton_abi = { git = "https://github.com/broxus/ton-labs-abi" }

nekoton-abi = { path = "../nekoton-abi", features = ["derive"] }
nekoton-jetton = { path = "../nekoton-jetton" }
nekoton-utils = { path = "../nekoton-utils" }

[dev-dependencies]
base64 = "0.13"

[features]
web = ["ton_abi/web"]
325 changes: 325 additions & 0 deletions nekoton-contracts/src/jetton/mod.rs

Large diffs are not rendered by default.

99 changes: 99 additions & 0 deletions nekoton-contracts/src/jetton/root_token_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use anyhow::Result;
use nekoton_abi::num_bigint::BigUint;
use nekoton_abi::VmGetterOutput;
use nekoton_jetton::{JettonMetaData, MetaDataContent};
use thiserror::Error;
use ton_block::{Deserializable, MsgAddressInt};
use ton_types::Cell;

#[derive(PartialEq, Eq, Debug, Clone)]
pub struct JettonRootData {
pub total_supply: BigUint,
pub mintable: bool,
pub admin_address: MsgAddressInt,
pub content: JettonMetaData,
pub wallet_code: Cell,
}

pub fn get_jetton_data(res: VmGetterOutput) -> Result<JettonRootData> {
if !res.is_ok {
return Err(RootContractError::ExecutionFailed {
exit_code: res.exit_code,
}
.into());
}

const JETTON_DATA_STACK_ELEMENTS: usize = 5;

let stack = res.stack;
if stack.len() != JETTON_DATA_STACK_ELEMENTS {
return Err(RootContractError::InvalidMethodResultStackSize {
actual: stack.len(),
expected: JETTON_DATA_STACK_ELEMENTS,
}
.into());
}

let total_supply = stack[0].as_integer()?.into(0..=u128::MAX)?;

let mintable = stack[1].as_bool()?;

let mut address_data = stack[2].as_slice()?.clone();

let admin_address = MsgAddressInt::construct_from(&mut address_data).unwrap_or_default();

let content = stack[3].as_cell()?;
let content = MetaDataContent::parse(content)?;

let wallet_code = stack[4].as_cell()?.clone();

let dict = match content {
MetaDataContent::Internal { dict } => dict,
MetaDataContent::Unsupported => {
return Err(RootContractError::UnsupportedContentType.into())
}
};

Ok(JettonRootData {
total_supply: BigUint::from(total_supply),
mintable,
admin_address,
wallet_code,
content: (&dict).into(),
})
}

pub fn get_wallet_address(res: VmGetterOutput) -> Result<MsgAddressInt> {
if !res.is_ok {
return Err(RootContractError::ExecutionFailed {
exit_code: res.exit_code,
}
.into());
}

const WALLET_ADDRESS_STACK_ELEMENTS: usize = 1;

let stack = res.stack;
if stack.len() != WALLET_ADDRESS_STACK_ELEMENTS {
return Err(RootContractError::InvalidMethodResultStackSize {
actual: stack.len(),
expected: WALLET_ADDRESS_STACK_ELEMENTS,
}
.into());
}

let mut address_data = stack[0].as_slice()?.clone();
let address = MsgAddressInt::construct_from(&mut address_data)?;

Ok(address)
}

#[derive(Error, Debug)]
pub enum RootContractError {
#[error("ExecutionFailed (exit_code: {exit_code})")]
ExecutionFailed { exit_code: i32 },
#[error("Invalid method result stack size (actual: {actual}, expected {expected})")]
InvalidMethodResultStackSize { actual: usize, expected: usize },
#[error("Unsupported metadata content type")]
UnsupportedContentType,
}
58 changes: 58 additions & 0 deletions nekoton-contracts/src/jetton/token_wallet_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use nekoton_abi::num_bigint::BigUint;
use nekoton_abi::VmGetterOutput;
use thiserror::Error;
use ton_block::{Deserializable, MsgAddressInt};
use ton_types::Cell;

#[derive(Debug, Clone)]
pub struct JettonWalletData {
pub balance: BigUint,
pub owner_address: MsgAddressInt,
pub root_address: MsgAddressInt,
pub wallet_code: Cell,
}

pub fn get_wallet_data(res: VmGetterOutput) -> anyhow::Result<JettonWalletData> {
if !res.is_ok {
return Err(WalletContractError::ExecutionFailed {
exit_code: res.exit_code,
}
.into());
}

const WALLET_DATA_STACK_ELEMENTS: usize = 4;

let stack = res.stack;
if stack.len() == WALLET_DATA_STACK_ELEMENTS {
let balance = stack[0].as_integer()?.into(0..=u128::MAX)?;

let mut address_data = stack[1].as_slice()?.clone();
let owner_address = MsgAddressInt::construct_from(&mut address_data)?;

let mut data = stack[2].as_slice()?.clone();
let root_address = MsgAddressInt::construct_from(&mut data)?;

let wallet_code = stack[3].as_cell()?.clone();

Ok(JettonWalletData {
balance: BigUint::from(balance),
owner_address,
root_address,
wallet_code,
})
} else {
Err(WalletContractError::InvalidMethodResultStackSize {
actual: stack.len(),
expected: WALLET_DATA_STACK_ELEMENTS,
}
.into())
}
}

#[derive(Error, Debug)]
pub enum WalletContractError {
#[error("ExecutionFailed (exit_code: {exit_code})")]
ExecutionFailed { exit_code: i32 },
#[error("Invalid method result stack size (actual: {actual}, expected {expected})")]
InvalidMethodResultStackSize { actual: usize, expected: usize },
}
1 change: 1 addition & 0 deletions nekoton-contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ use anyhow::Result;
use nekoton_abi::{ExecutionContext, ExecutionOutput};

pub mod dens;
pub mod jetton;
pub mod old_tip3;
pub mod tip1155;
pub mod tip3;
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
30 changes: 29 additions & 1 deletion nekoton-contracts/src/wallets/code/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use ton_types::Cell;
use std::collections::HashMap;
use ton_types::{Cell, UInt256};

macro_rules! declare_tvc {
($($contract:ident => $source:literal ($const_bytes:ident)),*$(,)?) => {$(
Expand All @@ -20,10 +21,37 @@ declare_tvc! {
multisig2_1 => "./Multisig2_1.tvc" (MULTISIG2_1_CODE),
surf_wallet => "./Surf.tvc" (SURF_WALLET_CODE),
wallet_v3 => "./wallet_v3_code.boc" (WALLET_V3_CODE),
wallet_v4r1 => "./wallet_v4r1_code.boc" (WALLET_V4R1_CODE),
wallet_v4r2 => "./wallet_v4r2_code.boc" (WALLET_V4R2_CODE),
wallet_v5r1 => "./wallet_v5r1_code.boc" (WALLET_V5R1_CODE),
highload_wallet_v2 => "./highload_wallet_v2_code.boc" (HIGHLOAD_WALLET_V2_CODE),
ever_wallet => "./ever_wallet_code.boc" (EVER_WALLET_CODE),
jetton_wallet => "./jetton_wallet.boc" (JETTON_WALLET),
jetton_wallet_v2 => "./jetton_wallet_v2.boc" (JETTON_WALLET_V2),
jetton_wallet_governed => "./jetton_wallet_governed.boc" (JETTON_WALLET_GOVERNED),
}

fn load(mut data: &[u8]) -> Cell {
ton_types::deserialize_tree_of_cells(&mut data).expect("Trust me")
}

static JETTON_LIBRARY_CELLS: once_cell::sync::Lazy<HashMap<UInt256, Cell>> =
once_cell::sync::Lazy::new(|| {
let mut m = HashMap::new();

let codes = [
jetton_wallet(),
jetton_wallet_v2(),
jetton_wallet_governed(),
];

for code in codes {
m.insert(code.repr_hash(), code);
}

m
});

pub fn get_jetton_library_cell(hash: &UInt256) -> Option<&'static Cell> {
JETTON_LIBRARY_CELLS.get(hash)
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
27 changes: 27 additions & 0 deletions nekoton-jetton/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "nekoton-jetton"
version = "0.13.0"
authors = [
"Alexey Pashinov <[email protected]>",
"Vladimir Petrzhikovskiy <[email protected]>",
"Ivan Kalinin <[email protected]>"
]
rust-version = "1.62.0"
edition = "2021"

[dependencies]
anyhow = "1.0"
lazy_static = "1.4"
serde = { version = "1.0.183", features = ["derive"] }
sha2 = "0.10.8"

ton_block = { git = "https://github.com/broxus/ton-labs-block.git" }
ton_types = { git = "https://github.com/broxus/ton-labs-types.git" }
ton_abi = { git = "https://github.com/broxus/ton-labs-abi" }

nekoton-utils = { path = "../nekoton-utils" }
num-bigint = "0.4"
num-traits = "0.2"

[features]
web = ["ton_abi/web"]
52 changes: 52 additions & 0 deletions nekoton-jetton/src/dict.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::collections::HashMap;

use ton_types::{HashmapType, SliceData, UInt256};

pub type SnakeFormatDict = HashMap<UInt256, Vec<u8>>;

pub fn load_dict_snake_format(slice: &mut SliceData) -> anyhow::Result<SnakeFormatDict> {
let content_dict = slice.get_dictionary()?.reference_opt(0);
let content_map = ton_types::HashmapE::with_hashmap(32 * 8, content_dict);

let mut dict = HashMap::with_capacity(content_map.len().unwrap());
for item in content_map.iter() {
let (k, v) = item.unwrap();

// Load value
if let Some(cell) = v.reference_opt(0) {
let buffer = parse_snake_data(cell).unwrap();
dict.insert(UInt256::from_slice(k.data()), buffer);
}
}

Ok(dict)
}

fn parse_snake_data(cell: ton_types::Cell) -> anyhow::Result<Vec<u8>> {
let mut buffer = vec![];

let mut cell = cell;
let mut first_cell = true;
loop {
let mut slice_data = SliceData::load_cell_ref(&cell)?;
if first_cell {
let first_byte = slice_data.get_next_byte()?;

if first_byte != 0 {
anyhow::bail!("Invalid snake format")
}
}

buffer.extend(slice_data.remaining_data().data());
match slice_data.remaining_references() {
0 => return Ok(buffer),
1 => {
cell = cell.reference(0)?;
first_cell = false;
}
n => {
anyhow::bail!("Invalid snake format string: found cell with {n} references")
}
}
}
}
5 changes: 5 additions & 0 deletions nekoton-jetton/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub use self::dict::*;
pub use self::meta::*;

mod dict;
mod meta;
Loading
Loading