use std::time::{SystemTime, UNIX_EPOCH};
use alloy_primitives::U256;
use sha2::{Digest, Sha256};
use crate::constants::{
ERC20_TRANSFER_SELECTOR_BYTES, TRON_CONTRACT_TYPE_TRANSFER, TRON_CONTRACT_TYPE_TRIGGER_SMART,
};
use crate::error::{TxCompilerError, TxCompilerErrorCode};
use crate::tron_address::tron_address_to_bytes;
use crate::tron_proto::{
encode_raw_data, encode_transfer_contract, encode_trigger_smart_contract, RawDataParams,
};
use crate::types::{
Chain, CompilationMetadata, CompilationResult, CompileOptions, FeeMode, PreparedTransaction,
TronBlockHeader, TxType,
};
const EXPIRATION_MS: u64 = 10 * 60 * 60 * 1000;
const TRON_ADDRESS_LEN: usize = 21;
const REF_BLOCK_HASH_START: usize = 8;
const REF_BLOCK_HASH_END: usize = 16;
pub fn compile_tron(
prepared: &PreparedTransaction,
options: CompileOptions,
) -> Result<CompilationResult, TxCompilerError> {
let header = prepared.fee.rp.as_ref().ok_or_else(|| {
TxCompilerError::new(
TxCompilerErrorCode::InvalidBlockHeader,
"Tron transactions require the block header at 'fee.rp'",
)
})?;
let (ref_block_bytes, ref_block_hash) = derive_block_ref(header)?;
let (contract, contract_type) = build_contract(prepared)?;
let fee_limit = parse_fee_limit(prepared.fee.el.as_deref())?;
let timestamp_ms = options.now.unwrap_or_else(current_time_ms);
let expiration = timestamp_ms.checked_add(EXPIRATION_MS).ok_or_else(|| {
TxCompilerError::new(
TxCompilerErrorCode::InvalidPayload,
"timestamp + EXPIRATION_MS overflows u64",
)
})?;
let raw_data = encode_raw_data(&RawDataParams {
ref_block_bytes: &ref_block_bytes,
ref_block_hash: &ref_block_hash,
expiration,
timestamp: timestamp_ms,
contract: &contract,
fee_limit,
});
let mut hasher = Sha256::new();
hasher.update(&raw_data);
let hash = hasher.finalize();
Ok(CompilationResult {
chain: Chain::Tron,
unsigned_tx: format!("0x{}", hex::encode(&raw_data)),
tx_hash: format!("0x{}", hex::encode(hash)),
metadata: CompilationMetadata {
tx_type: prepared.tx_type,
fee_mode: FeeMode::Tron,
evm_tx_type: None,
tron_contract_type: Some(contract_type),
expiration: Some(expiration),
},
})
}
fn derive_block_ref(header: &TronBlockHeader) -> Result<([u8; 2], [u8; 8]), TxCompilerError> {
if header.h.len() != 64 {
return Err(TxCompilerError::new(
TxCompilerErrorCode::InvalidBlockHeader,
"Block ID (h) must be exactly 64 hex characters (32 bytes)",
));
}
let h_bytes = hex::decode(&header.h).map_err(|_| {
TxCompilerError::new(
TxCompilerErrorCode::InvalidBlockHeader,
"Block ID (h) must be valid hex",
)
})?;
debug_assert_eq!(
h_bytes.len(),
32,
"64 hex chars always decode to 32 bytes when hex::decode succeeds"
);
let ref_block_bytes: [u8; 2] = u16::try_from(header.n & 0xffff)
.expect("mask to 16 bits always fits in u16")
.to_be_bytes();
let mut ref_block_hash = [0_u8; 8];
ref_block_hash.copy_from_slice(&h_bytes[REF_BLOCK_HASH_START..REF_BLOCK_HASH_END]);
Ok((ref_block_bytes, ref_block_hash))
}
fn parse_fee_limit(el: Option<&str>) -> Result<Option<u64>, TxCompilerError> {
el.map_or(Ok(None), |s| {
s.parse::<u64>().map(Some).map_err(|_| {
TxCompilerError::with_details(
TxCompilerErrorCode::InvalidAmount,
"Tron fee.el does not fit in u64",
serde_json::json!({ "field": "el", "value": s }),
)
})
})
}
fn build_contract(prepared: &PreparedTransaction) -> Result<(Vec<u8>, u8), TxCompilerError> {
let from = tron_address_to_bytes(&prepared.from)?;
match prepared.tx_type {
TxType::TransferNative => {
let to = tron_address_to_bytes(&prepared.to)?;
let amount = prepared.value_wei.parse::<u64>().map_err(|_| {
TxCompilerError::with_details(
TxCompilerErrorCode::InvalidAmount,
"Tron native amount does not fit in u64",
serde_json::json!({ "field": "valueWei", "value": prepared.value_wei }),
)
})?;
Ok((
encode_transfer_contract(&from, &to, amount),
TRON_CONTRACT_TYPE_TRANSFER,
))
}
TxType::TransferToken => {
let token = prepared.token_contract.as_deref().ok_or_else(|| {
TxCompilerError::new(
TxCompilerErrorCode::InvalidPayload,
"Token transfer requires tokenContract",
)
})?;
let token_bytes = tron_address_to_bytes(token)?;
let data = build_trc20_transfer_data(&prepared.to, &prepared.value_wei)?;
Ok((
encode_trigger_smart_contract(&from, &token_bytes, &data),
TRON_CONTRACT_TYPE_TRIGGER_SMART,
))
}
}
}
fn build_trc20_transfer_data(to: &str, amount: &str) -> Result<Vec<u8>, TxCompilerError> {
let to_bytes = tron_address_to_bytes(to)?;
if to_bytes.len() < TRON_ADDRESS_LEN {
return Err(TxCompilerError::new(
TxCompilerErrorCode::InvalidAddress,
"Tron address decoded to fewer than 21 bytes",
));
}
let address_bytes = &to_bytes[1..TRON_ADDRESS_LEN];
let mut address_word = [0_u8; 32];
address_word[12..32].copy_from_slice(address_bytes);
let amount_u256 = U256::from_str_radix(amount, 10).map_err(|_| {
TxCompilerError::with_details(
TxCompilerErrorCode::InvalidAmount,
"Amount exceeds uint256 range",
serde_json::json!({ "value": amount }),
)
})?;
let amount_word: [u8; 32] = amount_u256.to_be_bytes();
let mut out = Vec::with_capacity(ERC20_TRANSFER_SELECTOR_BYTES.len() + 32 + 32);
out.extend_from_slice(&ERC20_TRANSFER_SELECTOR_BYTES);
out.extend_from_slice(&address_word);
out.extend_from_slice(&amount_word);
Ok(out)
}
fn current_time_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX))
}