Skip to content
This repository was archived by the owner on Oct 7, 2026. It is now read-only.

Commit d903d7b

Browse files
shamardyclaude
andcommitted
fix(tron): address PR #2714 review comments (batch 2)
- Stricter error assertions in blockID deserialization tests: use unwrap_err() + is_data() + contains() instead of plain is_err() - Add expiration_seconds to WithdrawRequest for customizable tx expiry Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d1508f7 commit d903d7b

5 files changed

Lines changed: 44 additions & 14 deletions

File tree

‎mm2src/coins/eth/eth_withdraw.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ where
364364
resources,
365365
prices,
366366
fee_coin: ticker,
367+
expiration_seconds: req.expiration_seconds,
367368
};
368369
let (raw, tron_fee_details, final_amount) = match &coin.coin_type {
369370
EthCoinType::Eth => {

‎mm2src/coins/eth/tron/api.rs‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1193,16 +1193,28 @@ mod tests {
11931193
/// Non-hex `blockID` must fail deserialization (triggers `BadResponse` → node rotation).
11941194
#[test]
11951195
fn parse_getnowblock_rejects_invalid_block_id_hex() {
1196-
let json = r#"{ "blockID": "not_valid_hex!!", "block_header": { "raw_data": { "number": 1 } } }"#;
1197-
assert!(serde_json::from_str::<GetNowBlockResponse>(json).is_err());
1196+
let json = r#"{ "blockID": "zz00000000000000000000000000000000000000000000000000000000000000", "block_header": { "raw_data": { "number": 1 } } }"#;
1197+
let err = serde_json::from_str::<GetNowBlockResponse>(json).unwrap_err();
1198+
assert!(err.is_data(), "Expected data error for invalid hex, got: {}", err);
1199+
assert!(
1200+
err.to_string().contains("Invalid character"),
1201+
"Expected hex parse error, got: {}",
1202+
err
1203+
);
11981204
}
11991205

12001206
/// `blockID` that isn't exactly 32 bytes must fail deserialization.
12011207
#[test]
12021208
fn parse_getnowblock_rejects_wrong_length_block_id() {
12031209
// 31 bytes (62 hex chars) — too short
12041210
let json = r#"{ "blockID": "00000000033bab42e37d025dc14e9ebc26e8f6cb6b6e26e08d2bf2db29c3b4", "block_header": { "raw_data": { "number": 1 } } }"#;
1205-
assert!(serde_json::from_str::<GetNowBlockResponse>(json).is_err());
1211+
let err = serde_json::from_str::<GetNowBlockResponse>(json).unwrap_err();
1212+
assert!(err.is_data(), "Expected data error for wrong length, got: {}", err);
1213+
assert!(
1214+
err.to_string().contains("blockID must be 32 bytes"),
1215+
"Expected length error, got: {}",
1216+
err
1217+
);
12061218
}
12071219

12081220
#[test]

‎mm2src/coins/eth/tron/tx_builder.rs‎

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use ethereum_types::U256;
1313
use prost::Message;
1414

1515
/// Default transaction expiration window (60 seconds from block timestamp).
16-
const TX_EXPIRATION_MS: i64 = 60_000;
16+
const DEFAULT_TX_EXPIRATION_MS: i64 = 60_000;
1717

1818
/// Extract TAPOS reference fields from a recent block.
1919
///
@@ -48,14 +48,18 @@ pub(super) fn wrap_contract(contract_type: ContractType, type_url: &str, value:
4848
///
4949
/// Timestamp and expiration are derived from `block_data`:
5050
/// - `timestamp` = block timestamp (not validated by java-tron; matches TronWeb)
51-
/// - `expiration` = block timestamp + 60s
51+
/// - `expiration` = block timestamp + `expiration_sec` (converted to ms)
5252
pub fn build_trx_transfer(
5353
from: &TronAddress,
5454
to: &TronAddress,
5555
amount_sun: i64,
5656
block_data: &TaposBlockData,
57+
expiration_seconds: Option<u64>,
5758
) -> TransactionRaw {
5859
let (ref_block_bytes, ref_block_hash) = tapos_from_block(block_data.number, &block_data.block_id);
60+
let expiration_ms = expiration_seconds
61+
.map(|s| (s as i64).saturating_mul(1000))
62+
.unwrap_or(DEFAULT_TX_EXPIRATION_MS);
5963

6064
let transfer = TransferContract {
6165
owner_address: tron_addr_bytes(from),
@@ -71,7 +75,7 @@ pub fn build_trx_transfer(
7175
TransactionRaw {
7276
ref_block_bytes,
7377
ref_block_hash,
74-
expiration: block_data.timestamp.saturating_add(TX_EXPIRATION_MS),
78+
expiration: block_data.timestamp.saturating_add(expiration_ms),
7579
data: Vec::new(),
7680
contract: vec![contract],
7781
timestamp: block_data.timestamp,
@@ -89,8 +93,12 @@ pub fn build_trc20_transfer(
8993
amount: U256,
9094
fee_limit: i64,
9195
block_data: &TaposBlockData,
96+
expiration_seconds: Option<u64>,
9297
) -> Result<TransactionRaw, ethabi::Error> {
9398
let (ref_block_bytes, ref_block_hash) = tapos_from_block(block_data.number, &block_data.block_id);
99+
let expiration_ms = expiration_seconds
100+
.map(|s| (s as i64).saturating_mul(1000))
101+
.unwrap_or(DEFAULT_TX_EXPIRATION_MS);
94102

95103
let trigger = TriggerSmartContract {
96104
owner_address: tron_addr_bytes(from),
@@ -109,7 +117,7 @@ pub fn build_trc20_transfer(
109117
Ok(TransactionRaw {
110118
ref_block_bytes,
111119
ref_block_hash,
112-
expiration: block_data.timestamp.saturating_add(TX_EXPIRATION_MS),
120+
expiration: block_data.timestamp.saturating_add(expiration_ms),
113121
data: Vec::new(),
114122
contract: vec![contract],
115123
timestamp: block_data.timestamp,
@@ -146,10 +154,10 @@ mod tests {
146154
let from = TronAddress::from_hex(TEST_FROM_HEX).unwrap();
147155
let to = TronAddress::from_hex(TEST_TO_HEX).unwrap();
148156

149-
let mut raw = build_trx_transfer(&from, &to, 1000, &block_data);
157+
let mut raw = build_trx_transfer(&from, &to, 1000, &block_data, None);
150158
// Verify timestamp/expiration derived from block_data
151159
assert_eq!(raw.timestamp, block_data.timestamp);
152-
assert_eq!(raw.expiration, block_data.timestamp + TX_EXPIRATION_MS);
160+
assert_eq!(raw.expiration, block_data.timestamp + DEFAULT_TX_EXPIRATION_MS);
153161
// Override to match the real broadcast tx values for golden vector comparison.
154162
raw.timestamp = 1_770_522_424_709;
155163
raw.expiration = 1_770_522_483_000;
@@ -179,10 +187,11 @@ mod tests {
179187
let amount = U256::from(2_380_000u64);
180188
let fee_limit = 2_172_000i64;
181189

182-
let mut raw = build_trc20_transfer(&from, &contract_addr, &recipient, amount, fee_limit, &block_data).unwrap();
190+
let mut raw =
191+
build_trc20_transfer(&from, &contract_addr, &recipient, amount, fee_limit, &block_data, None).unwrap();
183192
// Verify timestamp/expiration derived from block_data
184193
assert_eq!(raw.timestamp, block_data.timestamp);
185-
assert_eq!(raw.expiration, block_data.timestamp + TX_EXPIRATION_MS);
194+
assert_eq!(raw.expiration, block_data.timestamp + DEFAULT_TX_EXPIRATION_MS);
186195
// Override to match the real broadcast tx values for golden vector comparison.
187196
raw.timestamp = 1_770_972_831_784;
188197
raw.expiration = 1_770_972_891_000;

‎mm2src/coins/eth/tron/withdraw.rs‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ pub struct TronWithdrawContext<'a> {
3131
pub resources: TronAccountResources,
3232
pub prices: TronChainPrices,
3333
pub fee_coin: &'a str,
34+
/// Transaction expiration window in seconds. `None` uses the protocol default (60s).
35+
pub expiration_seconds: Option<u64>,
3436
}
3537

3638
/// Reject EVM gas fee policies for TRON. TRON always auto-estimates fees.
@@ -73,7 +75,7 @@ pub fn build_tron_trx_withdraw(
7375
let mut amount_sun_i64: i64 = amount_sun
7476
.try_into()
7577
.map_to_mm(|_| WithdrawError::InternalError(format!("amount {amount_sun} exceeds i64::MAX")))?;
76-
let mut raw = build_trx_transfer(ctx.from, ctx.to, amount_sun_i64, ctx.block_data);
78+
let mut raw = build_trx_transfer(ctx.from, ctx.to, amount_sun_i64, ctx.block_data, ctx.expiration_seconds);
7779

7880
// Iteratively estimate fee and adjust amount until stable.
7981
// Non-max: runs once — amount is fixed, just checks balance sufficiency.
@@ -120,7 +122,7 @@ pub fn build_tron_trx_withdraw(
120122
amount_sun_i64 = amount_sun
121123
.try_into()
122124
.map_to_mm(|_| WithdrawError::InternalError(format!("amount {amount_sun} exceeds i64::MAX")))?;
123-
raw = build_trx_transfer(ctx.from, ctx.to, amount_sun_i64, ctx.block_data);
125+
raw = build_trx_transfer(ctx.from, ctx.to, amount_sun_i64, ctx.block_data, ctx.expiration_seconds);
124126
}
125127
}
126128

@@ -152,6 +154,7 @@ pub async fn build_tron_trc20_withdraw(
152154
amount_base_units,
153155
fee_limit_i64,
154156
ctx.block_data,
157+
ctx.expiration_seconds,
155158
)
156159
.map_to_mm(|e| WithdrawError::InternalError(format!("TRC20 ABI encoding failed: {e}")))?;
157160

@@ -238,7 +241,7 @@ mod tests {
238241
};
239242
let from = TronAddress::from_hex(TEST_FROM_HEX).unwrap();
240243
let to = TronAddress::from_hex(TEST_TO_HEX).unwrap();
241-
let raw = build_trx_transfer(&from, &to, 1_000_000, &block_data);
244+
let raw = build_trx_transfer(&from, &to, 1_000_000, &block_data, None);
242245

243246
// Sign with a deterministic test key
244247
let secret = ethkey::Secret::from_slice(&[1u8; 32]).expect("valid test secret");
@@ -272,6 +275,7 @@ mod tests {
272275
resources,
273276
prices,
274277
fee_coin: "TRX",
278+
expiration_seconds: None,
275279
};
276280
let (raw, fee_details, final_amount) =
277281
build_tron_trx_withdraw(&ctx, balance, balance, &balance_dec, true).unwrap();
@@ -309,6 +313,7 @@ mod tests {
309313
resources,
310314
prices,
311315
fee_coin: "TRX",
316+
expiration_seconds: None,
312317
};
313318
let result = build_tron_trx_withdraw(&ctx, amount, balance, &balance_dec, false);
314319

‎mm2src/coins/lp_coins.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2342,6 +2342,9 @@ pub struct WithdrawRequest {
23422342
/// Currently, this flag is used by ETH/ERC20 coins activated with MetaMask/WalletConnect(Some wallets e.g Metamask) **only**.
23432343
#[serde(default)]
23442344
broadcast: bool,
2345+
/// Transaction expiration window in seconds. Currently only used by TRON (default 60s),
2346+
/// but can be expanded to any protocol that supports transaction expiry.
2347+
pub expiration_seconds: Option<u64>,
23452348
}
23462349

23472350
#[derive(Debug, Deserialize)]

0 commit comments

Comments
 (0)