Skip to content

Tron unstake all bug#920

Merged
gemdev111 merged 8 commits intomainfrom
tron-unstake-all-bug
Feb 4, 2026
Merged

Tron unstake all bug#920
gemdev111 merged 8 commits intomainfrom
tron-unstake-all-bug

Conversation

@gemdev111
Copy link
Contributor

Changed votes: HashMap<String, u64> to TronStakeData enum that handles both:

  • Votes - for stake/redelegate operations
  • Unfreeze - for unstake when all votes are removed

Introduce structured Tron stake data and unfreeze support across the stack. Add TronVote, TronUnfreeze and TronStakeData types (and export Resource) in primitives and propagate them into TransactionLoadMetadata. Update gem_tron preload logic to compute stake data (votes or unfreeze amounts) via a new get_stake_data path and adapt transaction load to carry stake_data instead of a raw votes map. Implement calculate_unfreeze_amounts in preload_mapper to derive unfreeze items from account frozen state (with unit tests), and update gemstone uniffi models and conversion mappings to use the new Tron stake/unfreeze types. These changes enable representing both vote adjustments and automatic unfreeze operations in transaction preload metadata.
@semanticdiff-com
Copy link

semanticdiff-com bot commented Feb 3, 2026

Review changes with  SemanticDiff

Changed Files
File Status
  crates/primitives/src/lib.rs  54% smaller
  crates/gem_tron/src/provider/preload.rs  35% smaller
  gemstone/src/models/transaction.rs  24% smaller
  crates/primitives/src/transaction_load_metadata.rs  22% smaller
  crates/primitives/src/stake_type.rs  1% smaller
  Cargo.lock Unsupported file format
  apps/daemon/src/worker/assets/mod.rs  0% smaller
  crates/gem_tron/Cargo.toml Unsupported file format
  crates/gem_tron/src/provider/preload_mapper.rs  0% smaller

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @gemdev111, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses and resolves a bug related to unstaking all votes on the Tron network. It introduces a more robust and explicit data model for handling staking and unstaking operations by replacing a generic HashMap with a specialized enum. This change allows the system to accurately distinguish between vote modifications and unfreeze actions, particularly when a user intends to remove all their staked assets, thereby improving the reliability of Tron-related transactions.

Highlights

  • Refactored Tron Stake Data Handling: The previous HashMap<String, u64> used for votes has been replaced with a new TronStakeData enum, providing a more structured way to manage staking and unstaking operations.
  • Explicit Unfreeze Operations: The TronStakeData enum now explicitly differentiates between Votes (for stake/redelegate) and Unfreeze operations, specifically addressing scenarios where all votes are removed during an unstake.
  • New Unfreeze Calculation Logic: A new utility function, calculate_unfreeze_amounts, has been introduced to correctly determine and allocate unfreeze amounts across different frozen resources (Energy, Bandwidth) when an unstake operation results in zero remaining votes.
  • API and FFI Updates: The TransactionLoadMetadata structure and its corresponding Foreign Function Interface (FFI) definitions have been updated to incorporate the new TronStakeData enum, ensuring consistency across the system.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • crates/gem_tron/src/provider/preload.rs
    • Updated imports to include TronStakeData, TronVote, and calculate_unfreeze_amounts.
    • Renamed get_votes_for_transaction_input to get_stake_data and changed its return type to TronStakeData.
    • Modified the logic within get_stake_data to handle StakeType::Unstake by potentially returning TronStakeData::Unfreeze if all votes are removed.
    • Adjusted TransactionLoadMetadata population to use the new stake_data field.
  • crates/gem_tron/src/provider/preload_mapper.rs
    • Added imports for TronFrozen, Resource, and TronUnfreeze.
    • Introduced the calculate_unfreeze_amounts function to manage the distribution of unfreeze amounts across resources.
    • Added new unit tests for calculate_unfreeze_amounts to ensure correct functionality.
  • crates/primitives/src/lib.rs
    • Exported new types: Resource, TronStakeData, TronUnfreeze, and TronVote from the stake_type module.
  • crates/primitives/src/stake_type.rs
    • Added PartialEq derive macro to the StakeType enum.
    • Defined new structs TronVote and TronUnfreeze.
    • Introduced the TronStakeData enum with variants Votes(Vec<TronVote>) and Unfreeze(Vec<TronUnfreeze>).
  • crates/primitives/src/transaction_load_metadata.rs
    • Removed std::collections::HashMap import.
    • Updated the Tron variant of TransactionLoadMetadata to use stake_data: TronStakeData instead of votes: HashMap<String, u64>.
  • gemstone/src/models/transaction.rs
    • Added imports for Resource, TronStakeData, TronUnfreeze, and TronVote.
    • Created FFI-compatible type aliases and uniffi::remote definitions for TronVote, TronUnfreeze, and TronStakeData.
    • Modified the Tron variant of GemTransactionLoadMetadata to use stake_data: GemTronStakeData.
    • Updated From trait implementations for TransactionLoadMetadata and GemTransactionLoadMetadata to correctly map the new stake_data field.
Activity
  • The pull request introduces new data structures and logic to correctly handle Tron unstaking operations, particularly when all votes are removed.
  • It involves refactoring existing code to integrate the new TronStakeData enum, ensuring a more robust and explicit representation of staking actions.
  • New utility functions and unit tests have been added to support the enhanced functionality for calculating unfreeze amounts.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses a bug in the Tron unstaking process by introducing a TronStakeData enum. This change correctly handles the scenario where unstaking all votes should trigger an unfreeze operation, distinguishing it from partial unstakes or other staking actions. The new data structures are consistently integrated across the codebase, including the FFI layer. My review includes a suggestion to enhance the clarity and efficiency of the core logic in get_stake_data.

Comment on lines 128 to 150
let account = self.get_account(&input.sender_address).await?;
let mut current_votes: HashMap<String, u64> = account.votes.unwrap_or_default().into_iter().map(|v| (v.vote_address, v.vote_count)).collect();

let vote_amount = input.value.parse::<u64>().unwrap_or(0) / 10_u64.pow(asset.decimals as u32);
let amount = input.value.parse::<u64>().unwrap_or(0) / 10_u64.pow(asset.decimals as u32);
let mut votes: HashMap<String, u64> = account
.votes
.as_ref()
.map(|v| v.iter().map(|v| (v.vote_address.clone(), v.vote_count)).collect())
.unwrap_or_default();

match stake_type {
StakeType::Stake(validator) => {
*current_votes.entry(validator.id.clone()).or_insert(0) += vote_amount;
}
StakeType::Unstake(delegation) => {
if let Some(votes) = current_votes.get_mut(&delegation.base.validator_id) {
*votes = votes.saturating_sub(vote_amount);
StakeType::Stake(v) => *votes.entry(v.id.clone()).or_default() += amount,
StakeType::Unstake(d) => {
votes.entry(d.base.validator_id.clone()).and_modify(|v| *v = v.saturating_sub(amount));
votes.retain(|_, v| *v > 0);
if votes.is_empty() {
return Ok(TronStakeData::Unfreeze(calculate_unfreeze_amounts(account.frozen_v2.as_ref(), input.value.parse().unwrap_or(0))));
}
}
StakeType::Redelegate(redelegate_data) => {
if let Some(votes) = current_votes.get_mut(&redelegate_data.delegation.base.validator_id) {
*votes = votes.saturating_sub(vote_amount);
}
*current_votes.entry(redelegate_data.to_validator.id.clone()).or_insert(0) += vote_amount;
StakeType::Redelegate(r) => {
votes.entry(r.delegation.base.validator_id.clone()).and_modify(|v| *v = v.saturating_sub(amount));
*votes.entry(r.to_validator.id.clone()).or_default() += amount;
}
StakeType::Rewards(_) | StakeType::Withdraw(_) | StakeType::Freeze(_) => {}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To improve code clarity and avoid redundant operations, you can parse input.value once at the beginning of the Stake match arm.

Additionally, in the Unstake arm, you can use votes.values().all(|&count| count == 0) to check if all votes are cleared. This avoids modifying the votes map with retain just for the check, as the final filter operation will handle the removal of zero-count votes.

                let account = self.get_account(&input.sender_address).await?;
                let value_sun = input.value.parse::<u64>().unwrap_or(0);
                let amount = value_sun / 10_u64.pow(asset.decimals as u32);
                let mut votes: HashMap<String, u64> = account
                    .votes
                    .as_ref()
                    .map(|v| v.iter().map(|v| (v.vote_address.clone(), v.vote_count)).collect())
                    .unwrap_or_default();

                match stake_type {
                    StakeType::Stake(v) => *votes.entry(v.id.clone()).or_default() += amount,
                    StakeType::Unstake(d) => {
                        votes.entry(d.base.validator_id.clone()).and_modify(|v| *v = v.saturating_sub(amount));
                        if votes.values().all(|&count| count == 0) {
                            return Ok(TronStakeData::Unfreeze(calculate_unfreeze_amounts(account.frozen_v2.as_ref(), value_sun)));
                        }
                    }
                    StakeType::Redelegate(r) => {
                        votes.entry(r.delegation.base.validator_id.clone()).and_modify(|v| *v = v.saturating_sub(amount));
                        *votes.entry(r.to_validator.id.clone()).or_default() += amount;
                    }
                    StakeType::Rewards(_) | StakeType::Withdraw(_) | StakeType::Freeze(_) => {}
                }

let mut current_votes: HashMap<String, u64> = account.votes.unwrap_or_default().into_iter().map(|v| (v.vote_address, v.vote_count)).collect();

let vote_amount = input.value.parse::<u64>().unwrap_or(0) / 10_u64.pow(asset.decimals as u32);
let amount = input.value.parse::<u64>().unwrap_or(0) / 10_u64.pow(asset.decimals as u32);
Copy link
Contributor

Choose a reason for hiding this comment

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

avoid unwrap_or we have BigNumberFormatter for this. @copilot @gemdev111

Copy link
Contributor

Copilot AI commented Feb 3, 2026

@gemcoder21 I've opened a new pull request, #923, to work on those changes. Once the pull request is ready, I'll request review from you.

frozen
.map(|frozen| {
let mut items: Vec<_> = frozen.iter().filter(|f| f.amount > 0).collect();
items.sort_by_key(|f| std::cmp::Reverse(f.amount));
Copy link
Contributor

Choose a reason for hiding this comment

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

why you need to sort? simplify this code

Copilot AI and others added 6 commits February 3, 2026 22:02
…ng (#923)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gemcoder21 <104884878+gemcoder21@users.noreply.github.com>
Change numeric fields to UInt64 and add typeshare/serde metadata for Swift interop: TronVote.count and TronUnfreeze.amount use UInt64; add PartialEq derives and typeshare swift conformances; tag TronStakeData with serde(tag="type", content="content").

Simplify calculate_unfreeze_amounts in preload_mapper.rs by using an iterator chain with filter and scan to compute unfreeze entries, removing the temporary allocation and explicit sorting (now processes in original order).
@gemdev111 gemdev111 merged commit 01384bd into main Feb 4, 2026
5 of 6 checks passed
@gemdev111 gemdev111 deleted the tron-unstake-all-bug branch February 4, 2026 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants