Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions 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 crates/gem_hash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ version = { workspace = true }
edition = { workspace = true }

[dependencies]
sha2 = { workspace = true }
sha3 = { version = "0.10.8" }
tiny-keccak = { workspace = true }
1 change: 1 addition & 0 deletions crates/gem_hash/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod keccak;
pub mod sha2;
pub mod sha3;
11 changes: 11 additions & 0 deletions crates/gem_hash/src/sha2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use sha2::{Digest, Sha256};

pub fn sha256(bytes: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(bytes);
let result = hasher.finalize();

let mut hash = [0u8; 32];
hash.copy_from_slice(&result);
hash
}
2 changes: 2 additions & 0 deletions crates/gem_tron/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = { workspace = true }
bs58 = { workspace = true }
hex = { workspace = true }
primitives = { path = "../primitives" }
signer = { path = "../signer" }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }

Expand All @@ -18,6 +19,7 @@ number_formatter = { path = "../number_formatter", optional = true }
gem_evm = { path = "../gem_evm", optional = true }
gem_client = { path = "../gem_client", optional = true }
chain_traits = { path = "../chain_traits", optional = true }
gem_hash = { path = "../gem_hash" }
async-trait = { workspace = true, optional = true }
futures = { workspace = true, optional = true }
alloy-primitives = { workspace = true, optional = true }
Expand Down
3 changes: 3 additions & 0 deletions crates/gem_tron/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
pub mod address;
pub mod signer;

pub use signer::TronChainSigner;

#[cfg(feature = "rpc")]
pub mod rpc;
Expand Down
62 changes: 62 additions & 0 deletions crates/gem_tron/src/models/contract.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
use serde::{Deserialize, Serialize};
use std::error::Error;

use crate::address::TronAddress;
use crate::signer::transaction::TronPayload;

const TRIGGER_SMART_CONTRACT: &str = "TriggerSmartContract";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TronSmartContractCall {
Expand All @@ -23,3 +29,59 @@ pub struct TronSmartContractResultMessage {
pub result: bool,
pub message: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TriggerSmartContractData {
pub contract_address: String,
pub data: String,
pub owner_address: String,
pub fee_limit: Option<u64>,
pub call_value: Option<u64>,
}

impl TriggerSmartContractData {
pub fn from_payload(
data: Option<&[u8]>,
sender_address: &str,
) -> Result<Option<Self>, Box<dyn Error + Send + Sync>> {
let Some(data) = data else {
return Ok(None);
};
let Ok(payload) = serde_json::from_slice::<TronPayload>(data) else {
return Ok(None);
};
let Some(raw_data) = payload.transaction.raw_data.as_ref() else {
return Ok(None);
};
let Some(contract) = raw_data.contract.first() else {
return Ok(None);
};
if contract.contract_type != TRIGGER_SMART_CONTRACT {
return Ok(None);
}

let value = &contract.parameter.value;
let Some(contract_address) = value.contract_address.as_deref().and_then(TronAddress::from_hex) else {
return Err("Invalid Tron contract address".into());
};
let Some(data) = value.data.as_deref() else {
return Ok(None);
};
let owner_address = if payload.address.is_empty() {
match value.owner_address.as_deref() {
Some(address) => TronAddress::from_hex(address).ok_or("Invalid Tron owner address")?,
None => sender_address.to_string(),
}
} else {
payload.address
};

Ok(Some(Self {
contract_address,
data: data.to_string(),
owner_address,
fee_limit: raw_data.fee_limit,
call_value: value.call_value,
}))
}
}
49 changes: 49 additions & 0 deletions crates/gem_tron/src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use std::{error::Error, fmt};

pub mod account;
pub mod block;
Expand Down Expand Up @@ -124,6 +125,18 @@ pub struct TriggerConstantContractRequest {
pub visible: bool,
}

#[derive(Serialize, Debug)]
pub struct TriggerConstantContractDataRequest {
pub owner_address: String,
pub contract_address: String,
pub data: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_limit: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub call_value: Option<u64>,
pub visible: bool,
}

#[derive(Deserialize, Debug)]
pub struct TriggerConstantContractResponse {
#[serde(default)]
Expand All @@ -136,10 +149,46 @@ pub struct TriggerConstantContractResponse {

#[derive(Deserialize, Debug)]
pub struct TriggerContractResult {
pub result: Option<bool>,
pub code: Option<String>,
pub message: Option<String>,
}

impl TriggerConstantContractResponse {
pub fn check_error(&self) -> Option<TronRpcError> {
let result = self.result.as_ref()?;
if result.result.unwrap_or(false) {
return None;
}

let message = result.message.as_deref().map(|message_hex| {
hex::decode(message_hex)
.ok()
.and_then(|bytes| String::from_utf8(bytes).ok())
.unwrap_or_else(|| message_hex.to_string())
});

Some(TronRpcError {
code: result.code.clone(),
message,
})
}
}

#[derive(Debug, Clone)]
pub struct TronRpcError {
pub code: Option<String>,
pub message: Option<String>,
}

impl fmt::Display for TronRpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Tron RPC Error {} {}", self.code.as_deref().unwrap_or(""), self.message.as_deref().unwrap_or(""))
}
}

impl Error for TronRpcError {}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WitnessesList {
pub witnesses: Vec<WitnessAccount>,
Expand Down
43 changes: 42 additions & 1 deletion crates/gem_tron/src/provider/preload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ use gem_client::Client;
use number_formatter::BigNumberFormatter;
use primitives::{
AssetSubtype, FeePriority, FeeRate, GasPriceType, StakeType, TransactionFee, TransactionInputType, TransactionLoadData, TransactionLoadInput, TransactionLoadMetadata,
TransactionPreloadInput, TronStakeData, TronVote,
TransactionPreloadInput, TransferDataOutputAction, TronStakeData, TronVote,
};

use crate::{
models::{ChainParameter, TriggerSmartContractData, account::TronAccountUsage},
provider::{
balances_mapper::format_address_parameter,
preload_mapper::{calculate_stake_fee_rate, calculate_transfer_fee_rate, calculate_transfer_token_fee_rate, calculate_unfreeze_amounts},
Expand Down Expand Up @@ -61,6 +62,16 @@ impl<C: Client> ChainTransactionLoad for TronClient<C> {
.await?
}
},
TransactionInputType::Generic(_, _, extra) => match extra.output_action {
TransferDataOutputAction::Send => match self
.estimate_fee_with_data(&input.sender_address, extra.data.as_deref(), &chain_parameters, &account_usage)
.await?
{
Some(fee) => fee,
None => TransactionFee::new_from_fee(calculate_transfer_fee_rate(&chain_parameters, &account_usage, is_new_account)?),
},
TransferDataOutputAction::Sign => TransactionFee::new_from_fee(calculate_transfer_fee_rate(&chain_parameters, &account_usage, is_new_account)?),
},
TransactionInputType::Stake(_asset, stake_type) => TransactionFee::new_from_fee(calculate_stake_fee_rate(&chain_parameters, &account_usage, stake_type)?),
TransactionInputType::Swap(from_asset, _, swap_data) => match &from_asset.id.token_id {
None => TransactionFee::new_from_fee(calculate_transfer_fee_rate(&chain_parameters, &account_usage, is_new_account)?),
Expand Down Expand Up @@ -110,6 +121,36 @@ impl<C: Client> TronClient<C> {
))
}

async fn estimate_fee_with_data(
&self,
sender_address: &str,
data: Option<&[u8]>,
chain_parameters: &[ChainParameter],
account_usage: &TronAccountUsage,
) -> Result<Option<TransactionFee>, Box<dyn Error + Send + Sync>> {
let Some(parsed) = TriggerSmartContractData::from_payload(data, sender_address)? else {
return Ok(None);
};

let estimated_energy = self
.estimate_energy_with_data(
&parsed.owner_address,
&parsed.contract_address,
&parsed.data,
parsed.fee_limit,
parsed.call_value,
)
.await?;
let token_fee = calculate_transfer_token_fee_rate(chain_parameters, account_usage, estimated_energy)?;

Ok(Some(TransactionFee::new_gas_price_type(
GasPriceType::regular(BigInt::from(token_fee.energy_price)),
BigInt::from(token_fee.fee),
BigInt::from(token_fee.fee_limit),
HashMap::new(),
)))
}

async fn get_is_new_account_for_input_type(&self, address: &str, input_type: TransactionInputType) -> Result<bool, Box<dyn Error + Send + Sync>> {
match input_type {
TransactionInputType::Transfer(asset)
Expand Down
49 changes: 31 additions & 18 deletions crates/gem_tron/src/rpc/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use std::{error::Error, str::FromStr};

use crate::address::TronAddress;
use crate::models::{
Block, BlockTransactions, BlockTransactionsInfo, ChainParameter, ChainParametersResponse, Transaction, TransactionReceiptData, TriggerConstantContractRequest,
TriggerConstantContractResponse, TronTransactionBroadcast, WitnessesList,
Block, BlockTransactions, BlockTransactionsInfo, ChainParameter, ChainParametersResponse, Transaction, TransactionReceiptData, TriggerConstantContractDataRequest,
TriggerConstantContractRequest, TriggerConstantContractResponse, TronTransactionBroadcast, WitnessesList,
};
use crate::models::{TronAccount, TronAccountRequest, TronAccountUsage, TronBlock, TronEmptyAccount, TronReward, TronSmartContractCall, TronSmartContractResult};
use crate::rpc::constants::{DECIMALS_SELECTOR, DEFAULT_OWNER_ADDRESS, NAME_SELECTOR, SYMBOL_SELECTOR};
Expand All @@ -16,7 +16,6 @@ use alloy_primitives::Address as AlloyAddress;
use alloy_sol_types::SolCall;
use gem_client::Client;
use gem_evm::contracts::erc20::{decode_abi_string, decode_abi_uint8};
use serde_json::Value;

#[derive(Clone)]
pub struct TronClient<C: Client> {
Expand Down Expand Up @@ -127,24 +126,38 @@ impl<C: Client> TronClient<C> {
visible: true,
};

let response: Value = self.client.post("/wallet/triggerconstantcontract", &request_payload, None).await?;

if let Some(result_obj) = response.get("result") {
let is_success = result_obj.get("result").and_then(|value| value.as_bool()).unwrap_or(false);
if !is_success {
let code = result_obj.get("code").and_then(|v| v.as_str()).unwrap_or_default();
let message_hex = result_obj.get("message").and_then(|v| v.as_str()).unwrap_or_default();
let message = hex::decode(message_hex)
.ok()
.and_then(|bytes| String::from_utf8(bytes).ok())
.unwrap_or_else(|| message_hex.to_string());
return Err(format!("Estimate energy failed. Code: {}, Message: {}", code, message).into());
}
let response = self.trigger_constant_contract_request(&request_payload).await?;
if let Some(error) = response.check_error() {
return Err(Box::new(error));
}
let energy_used = response.energy_used;
let energy_penalty = response.energy_penalty.unwrap_or_default();
Ok(energy_used + energy_penalty)
}

let energy_used = response.get("energy_used").and_then(|value| value.as_u64()).unwrap_or_default();
let energy_penalty = response.get("energy_penalty").and_then(|value| value.as_u64()).unwrap_or_default();
pub async fn estimate_energy_with_data(
&self,
owner_address: &str,
contract_address: &str,
data: &str,
fee_limit: Option<u64>,
call_value: Option<u64>,
) -> Result<u64, Box<dyn Error + Send + Sync>> {
let request_payload = TriggerConstantContractDataRequest {
owner_address: owner_address.to_string(),
contract_address: contract_address.to_string(),
data: data.to_string(),
fee_limit,
call_value,
visible: true,
};

let response: TriggerConstantContractResponse = self.client.post("/wallet/triggerconstantcontract", &request_payload, None).await?;
if let Some(error) = response.check_error() {
return Err(Box::new(error));
}
let energy_used = response.energy_used;
let energy_penalty = response.energy_penalty.unwrap_or_default();
Ok(energy_used + energy_penalty)
}
}
Expand Down
Loading
Loading