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

feat(oracle): support update for eth-native-restaking balance change #170

Closed

Conversation

leonz789
Copy link
Contributor

@leonz789 leonz789 commented Aug 26, 2024

Description

This PR add support for feed effective balance change of staked ETH on beaconChain onto exocoreChain.
When staked ETH on beaconChain deposit to exocoreChain, we treat it as a virtual LST and set it with a virtual price of its original amount of ETH on beaconChain.
eg.
validator_A has 32 staked ETH on beaconChain, and then deposit these 32 staked ETH to eoxcoreChain. He will get 32 NRETH(virtualLST) on exocoreChain, with price as 1ETH/NRETH. When validator_A got slashed on beaconChain which lead to his effective balance became to 16 ETH, then the price of his 32NRETH on exocoreChain will change to 16/32=0.5ETH/NRETH correspondingly.
the symbol NRETH is used for clarify

TODO:
- [x] test


Closes #XXX

Summary by CodeRabbit

  • New Features

    • Enhanced application functionality with the integration of the OracleKeeper for managing oracle data.
    • Introduced a new Protocol Buffers schema for token pricing and staking, improving data communication within the Exocore ecosystem.
    • Added methods for managing native tokens, including deposit, withdrawal, and delegation processes.
    • Implemented new functionality for retrieving asset IDs based on token IDs.
    • Introduced a comprehensive test suite for native token lifecycle management.
  • Bug Fixes

    • Improved handling of native token operations to ensure accurate state management during deposits and withdrawals.
  • Documentation

    • Updated comments and documentation to reflect new functionalities and enhancements related to native tokens and oracles.

Copy link
Contributor

coderabbitai bot commented Aug 26, 2024

Walkthrough

The changes introduce enhancements to the Exocore application, focusing on the integration of native token functionalities and oracle management. Key modifications include the addition of new parameters and methods for handling deposits, withdrawals, and delegations related to native tokens. New Protocol Buffers messages are defined for token pricing and staking, while existing functions are updated to accommodate these changes, improving the overall structure and capabilities of the application.

Changes

Files Change Summary
app/app.go Modified NewExocoreApp to include &app.OracleKeeper as a parameter, enhancing oracle data management.
proto/exocore/oracle/v1/native_token.proto Introduced a new file defining Protocol Buffers messages for token pricing and staking, including PriceInfo, OperatorInfo, and StakerInfo.
x/assets/keeper/bank.go Updated PerformDepositOrWithdraw to handle native tokens with new logic for state updates.
x/oracle/keeper/native_token.go Implemented methods for managing native token operations, including deposit, withdrawal, and price retrieval.
x/oracle/types/key_native_token.go Defined constants and functions for key management related to native tokens.
x/oracle/types/native_token.go Introduced NewStakerInfo and NewOperatorInfo functions for creating structured instances of staker and operator information.
x/oracle/types/params.go Added GetAssetIDsFromTokenID method to the Params struct for retrieving asset IDs based on token IDs.
x/delegation/keeper/abci.go Modified error handling logic in EndBlock for improved conciseness.
x/oracle/keeper/prices.go Enhanced GetSpecifiedAssetsPrice and AppendPriceTR methods to include handling for native tokens.
x/oracle/types/errors.go Introduced a new error constant for failures related to updating the native token's virtual price.
x/oracle/keeper/native_token_test.go Added a test suite for native token lifecycle, validating deposit, delegation, and withdrawal processes.
go.mod Added a new indirect dependency on github.com/imroc/biu.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App
    participant OracleKeeper
    participant BankKeeper
    participant DelegationKeeper

    User->>App: Initiate Deposit/Withdraw
    App->>BankKeeper: PerformDepositOrWithdraw(assetID, amount)
    BankKeeper->>OracleKeeper: UpdateNativeTokenByDepositOrWithdraw(assetID, stakerAddr, amount)
    OracleKeeper-->>BankKeeper: Return updated balance
    BankKeeper-->>App: Confirm Deposit/Withdraw
    App-->>User: Notify Success
Loading

Poem

🐇 In the fields of code we play,
New tokens dance, come what may.
With oracles guiding the way,
We hop along, bright and gay!
Each change a leap, a joyful cheer,
For in this world, we hold so dear! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Outside diff range, codebase verification and nitpick comments (2)
x/delegation/keeper/delegation.go (1)

124-126: Add Test Cases for Native Token Handling in UndelegateFrom

The current codebase lacks specific test cases to verify the handling of native tokens in the UndelegateFrom function. It is recommended to add test scenarios that cover this condition to ensure the new logic is correctly implemented and tested.

  • Consider adding test cases in files where UndelegateFrom is already tested, such as delegation_op_test.go or similar test files.
  • Ensure that the tests cover both positive and negative scenarios for native tokens.
Analysis chain

LGTM! But verify the function usage.

The function correctly includes new logic to handle native tokens. Ensure that all function calls to UndelegateFrom handle this new condition correctly.

The code changes are approved.

Run the following script to verify the function usage:

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify all function calls to `UndelegateFrom` handle the new condition correctly.

# Test: Search for the function usage. Expect: Only occurrences of the new condition.
rg --type go -A 5 $'UndelegateFrom'

Length of output: 10762


Script:

#!/bin/bash
# Description: Search for test cases related to `UndelegateFrom` that handle native tokens.

# Test: Search for occurrences of native token handling in test files.
rg --type go 'IsNativeToken' -A 5

Length of output: 3616

x/oracle/keeper/native_token.go (1)

199-241: Fix typo in comment.

There is a typo in the comment on line 212. Replace "effect balance f stakerAddr[index] has changed" with "effective balance of stakerAddr[index] has changed".

Apply this diff to fix the typo:

-                // effect balance  f stakerAddr[index] has changed
+                // effective balance of stakerAddr[index] has changed
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 60c3f17 and a5433e5.

Files ignored due to path filters (1)
  • x/oracle/types/native_token.pb.go is excluded by !**/*.pb.go
Files selected for processing (18)
  • app/app.go (1 hunks)
  • proto/exocore/oracle/v1/native_token.proto (1 hunks)
  • x/assets/keeper/bank.go (2 hunks)
  • x/assets/types/expected_keepers.go (1 hunks)
  • x/assets/types/general.go (2 hunks)
  • x/delegation/keeper/abci.go (1 hunks)
  • x/delegation/keeper/delegation.go (3 hunks)
  • x/delegation/keeper/keeper.go (2 hunks)
  • x/delegation/types/expected_keepers.go (2 hunks)
  • x/delegation/types/hooks.go (1 hunks)
  • x/operator/keeper/abci.go (2 hunks)
  • x/operator/keeper/usd_value.go (2 hunks)
  • x/oracle/keeper/native_token.go (1 hunks)
  • x/oracle/keeper/prices.go (4 hunks)
  • x/oracle/types/key_native_token.go (1 hunks)
  • x/oracle/types/keys.go (1 hunks)
  • x/oracle/types/native_token.go (1 hunks)
  • x/oracle/types/params.go (1 hunks)
Files skipped from review due to trivial changes (2)
  • x/delegation/types/hooks.go
  • x/oracle/types/keys.go
Additional context used
Learnings (1)
x/operator/keeper/usd_value.go (1)
Learnt from: TimmyExogenous
PR: ExocoreNetwork/exocore#129
File: x/operator/keeper/consensus_keys.go:394-395
Timestamp: 2024-07-25T06:38:34.825Z
Learning: The decimals and prices retrieval in the `GetOrCalculateOperatorUSDValues` function cannot be combined because they call different modules.
GitHub Check: Run golangci-lint
x/oracle/types/key_native_token.go

[warning] 15-15:
unused-parameter: parameter 'stakerAddr' seems to be unused, consider removing or renaming it as _ (revive)

x/assets/keeper/bank.go

[failure] 9-9:
ST1019: package "github.com/ExocoreNetwork/exocore/x/assets/types" is being imported more than once (stylecheck)


[failure] 10-10:
ST1019(related information): other import of "github.com/ExocoreNetwork/exocore/x/assets/types" (stylecheck)

x/delegation/keeper/abci.go

[failure] 49-49:
File is not gofumpt-ed (gofumpt)

x/oracle/keeper/prices.go

[failure] 210-210:
Error return value of k.UpdateNativeTokenByBalanceChange is not checked (errcheck)

x/oracle/keeper/native_token.go

[warning] 134-134:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)

GitHub Check: CodeQL
x/oracle/keeper/native_token.go

[warning] 171-188: Iteration over map
Iteration over map may be a possible source of non-determinism

Additional comments not posted (33)
x/assets/types/expected_keepers.go (1)

12-12: LGTM! But verify the function implementation.

The function signature looks correct. However, ensure that the function is implemented correctly in the corresponding struct that implements this interface.

The code changes are approved.

Run the following script to verify the function implementation:

Verification successful

Verification successful: Function implementation found.

The function UpdateNativeTokenByDepositOrWithdraw is correctly implemented in the Keeper struct within the x/oracle/keeper/native_token.go file, matching the interface addition.

  • Implementation found in: x/oracle/keeper/native_token.go
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of `UpdateNativeTokenByDepositOrWithdraw`.

# Test: Search for the function implementation. Expect: The function should be implemented in the corresponding struct.
rg --type go 'func (.*) UpdateNativeTokenByDepositOrWithdraw'

Length of output: 212

x/oracle/types/native_token.go (1)

24-37: LGTM!

The function is correctly implemented.

The code changes are approved.

proto/exocore/oracle/v1/native_token.proto (6)

9-13: LGTM!

The message is correctly defined.

The code changes are approved.


15-19: LGTM!

The message is correctly defined.

The code changes are approved.


21-27: LGTM!

The message is correctly defined.

The code changes are approved.


34-36: LGTM!

The message is correctly defined.

The code changes are approved.


38-41: LGTM!

The message is correctly defined.

The code changes are approved.


42-44: LGTM!

The message is correctly defined.

The code changes are approved.

x/oracle/types/key_native_token.go (4)

5-13: LGTM!

The constants are well-defined and follow a consistent naming convention.

The code changes are approved.


19-21: LGTM!

The function is correctly implemented.

The code changes are approved.


23-26: LGTM!

The function is correctly implemented.

The code changes are approved.


28-30: LGTM!

The function is correctly implemented.

The code changes are approved.

x/delegation/keeper/keeper.go (2)

20-20: LGTM!

The addition of the oracleKeeper field is consistent with the PR objectives and enhances the functionality of the Keeper.

The code changes are approved.


30-38: LGTM!

The update to the constructor ensures that the oracleKeeper dependency is properly initialized.

The code changes are approved.

x/delegation/types/expected_keepers.go (2)

4-4: LGTM!

The import statement is necessary for the new method's return type.

The code changes are approved.


59-61: LGTM!

The method UpdateNativeTokenByDelegation is consistent with the PR objectives and enhances the functionality related to token management.

The code changes are approved.

x/assets/keeper/bank.go (1)

49-52: LGTM! But clarify or address the comment about skipping the check for withdrawable amounts.

The new logic for handling native tokens is correctly implemented. However, the comment about skipping the check for withdrawable amounts should be clarified or addressed to ensure robustness.

The code changes are approved.

Ensure that the skipping of the check for withdrawable amounts is intentional and will not cause any issues in the future.

x/operator/keeper/abci.go (1)

67-70: LGTM! But verify the error handling in GetSpecifiedAssetsPrice.

The new logic for updating prices of native token asset IDs is correctly implemented. However, ensure that the GetSpecifiedAssetsPrice method handles errors gracefully and does not cause any issues.

The code changes are approved.

Ensure that the GetSpecifiedAssetsPrice method handles errors gracefully and does not cause any issues.

x/assets/types/general.go (3)

174-177: LGTM!

The function correctly checks if an asset ID corresponds to a native token. The TODO comment indicates future extensibility.

The code changes are approved.


179-182: LGTM!

The function correctly returns a list of native token asset IDs. The TODO comment indicates future extensibility.

The code changes are approved.


184-186: LGTM!

The function correctly retrieves the base token associated with a given asset ID.

The code changes are approved.

x/oracle/keeper/prices.go (2)

285-291: LGTM!

The function correctly returns the nextRoundID value.

The code changes are approved.


58-61: LGTM! But verify the function usage.

The function correctly includes a new conditional check to handle cases where the assetID corresponds to a native token. Ensure that all function calls to GetSpecifiedAssetsPrice handle this new condition correctly.

The code changes are approved.

Run the following script to verify the function usage:

x/delegation/keeper/delegation.go (1)

52-54: LGTM! But verify the function usage.

The function correctly includes new logic to handle native tokens. Ensure that all function calls to delegateTo handle this new condition correctly.

The code changes are approved.

Run the following script to verify the function usage:

Verification successful

Verification Successful: Native Token Handling in delegateTo Function

The delegateTo function correctly implements the logic for handling native tokens. The usage in both genesis.go and delegation.go aligns with the intended functionality, ensuring that the new condition is applied appropriately.

  • File: x/delegation/keeper/delegation.go
  • Function: delegateTo
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify all function calls to `delegateTo` handle the new condition correctly.

# Test: Search for the function usage. Expect: Only occurrences of the new condition.
rg --type go -A 5 $'delegateTo'

Length of output: 6396


Script:

#!/bin/bash
# Extract the full implementation of the `delegateTo` function and its usage in relevant files.

# Extract the implementation of `delegateTo` function.
ast-grep --lang go --pattern 'func (k *Keeper) delegateTo($_, $_, $_) error { $$$ }'

# Extract the usage of `delegateTo` in `genesis.go`.
rg --type go -A 10 -B 2 'delegateTo' x/delegation/keeper/genesis.go

# Extract the usage of `delegateTo` in `delegation.go`.
rg --type go -A 10 -B 2 'delegateTo' x/delegation/keeper/delegation.go

Length of output: 7513

x/oracle/keeper/native_token.go (6)

150-159: LGTM!

The function is correctly implemented.

The code changes are approved.


192-197: LGTM!

The function is correctly implemented.

The code changes are approved.


244-249: LGTM!

The function is correctly implemented.

The code changes are approved.


251-256: LGTM!

The function is correctly implemented.

The code changes are approved.


258-280: LGTM!

The functions are correctly implemented.

The code changes are approved.


161-188: Reminder: Complete the TODO items and review iteration over map.

The function has a TODO comment indicating incomplete implementation. Additionally, review the iteration over the map for potential non-determinism.

Do you want me to generate the implementation for the TODO items or open a GitHub issue to track this task?

Run the following script to verify the iteration over the map:

Tools
GitHub Check: CodeQL

[warning] 171-188: Iteration over map
Iteration over map may be a possible source of non-determinism

x/oracle/types/params.go (1)

496-501: LGTM!

The function is correctly implemented.

The code changes are approved.

x/operator/keeper/usd_value.go (1)

303-305: LGTM!

The function is correctly implemented. The new conditional check for native tokens enhances the logic.

The code changes are approved.

app/app.go (1)

554-554: Verify the usage of the new parameter &app.OracleKeeper.

Ensure that all components or functions receiving &app.OracleKeeper are correctly handling it. This change might impact how the application interacts with the oracle service.

Run the following script to verify the function usage:

@@ -0,0 +1,37 @@
package types

// TODO: vlaidatorIndex need bridge data
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Address the TODO comments.

The TODO comments indicate that some data is missing.

Do you want me to help address the TODO comments or open a GitHub issue to track this task?

Comment on lines +29 to +32
message NativeTokenPrice {
string token = 1;
repeated PriceInfo prece_list = 2;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Fix the typo in the field name.

There is a typo in the field name prece_list. It should be price_list.

Apply this diff to fix the typo:

 message NativeTokenPrice {
   string token = 1;
-  repeated PriceInfo prece_list = 2;
+  repeated PriceInfo price_list = 2;
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
message NativeTokenPrice {
string token = 1;
repeated PriceInfo prece_list = 2;
}
message NativeTokenPrice {
string token = 1;
repeated PriceInfo price_list = 2;
}

Comment on lines 15 to 17
func NativeTokenStakerDelegationKey(assetID, stakerAddr, operatorAddr string) []byte {
return append([]byte(NativeTokenStakerDelegationKeyPrefix), []byte(assetID)...)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove or use the unused parameter stakerAddr.

The parameter stakerAddr is not used in the function, which may lead to confusion or potential bugs. Consider removing it or using it appropriately.

Apply this diff to remove the unused parameter:

-func NativeTokenStakerDelegationKey(assetID, stakerAddr, operatorAddr string) []byte {
+func NativeTokenStakerDelegationKey(assetID, operatorAddr string) []byte {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func NativeTokenStakerDelegationKey(assetID, stakerAddr, operatorAddr string) []byte {
return append([]byte(NativeTokenStakerDelegationKeyPrefix), []byte(assetID)...)
}
func NativeTokenStakerDelegationKey(assetID, operatorAddr string) []byte {
return append([]byte(NativeTokenStakerDelegationKeyPrefix), []byte(assetID)...)
}
Tools
GitHub Check: Run golangci-lint

[warning] 15-15:
unused-parameter: parameter 'stakerAddr' seems to be unused, consider removing or renaming it as _ (revive)

@@ -46,6 +46,7 @@
}
// and the other is the fact that it matures at the next block
err = k.StorePendingUndelegationRecord(ctx, recordKey, record)
//TODO: remove previous index with currentHeight for pendingUndelegationRecord
Copy link
Contributor

Choose a reason for hiding this comment

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

Acknowledged TODO comment.

The TODO comment about removing the previous index with currentHeight for pendingUndelegationRecord is noted. If you need assistance with this task, please let me know.

Do you want me to generate the code for this task or open a GitHub issue to track it?

Tools
GitHub Check: Run golangci-lint

[failure] 49-49:
File is not gofumpt-ed (gofumpt)

Comment on lines 198 to 211
// update for native tokens
// TODO: set hooks as a genral approach
var p types.Params
// get params from cache if exists
if agc != nil {
p = agc.GetParams()
} else {
p = k.GetParams(ctx)
}
assetIDs := p.GetAssetIDsFromTokenID(tokenID)
for _, assetID := range assetIDs {
if assetstypes.IsNativeToken(assetID) {
k.UpdateNativeTokenByBalanceChange(ctx, assetID, []byte(priceTR.Price), roundID)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Check the error return value.

The error return value of k.UpdateNativeTokenByBalanceChange is not checked. Ensure that the error is handled appropriately.

Apply this diff to check the error return value:

- k.UpdateNativeTokenByBalanceChange(ctx, assetID, []byte(priceTR.Price), roundID)
+ if err := k.UpdateNativeTokenByBalanceChange(ctx, assetID, []byte(priceTR.Price), roundID); err != nil {
+   return false
+ }

Committable suggestion was skipped due to low confidence.

Tools
GitHub Check: Run golangci-lint

[failure] 210-210:
Error return value of k.UpdateNativeTokenByBalanceChange is not checked (errcheck)

Comment on lines 25 to 47
func (k Keeper) UpdateNativeTokenByDepositOrWithdraw(ctx sdk.Context, assetID, stakerAddr, amount string) *big.Int {
// TODO: just convert the number for assets module, and don't store state in oracleModule, can use cache only here
// TODO: we havn't included validatorIndex here, need the bridge info
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenStakerKey(assetID, stakerAddr)
stakerInfo := &types.StakerInfo{}
if value := store.Get(key); value == nil {
// create a new item for this staker
stakerInfo = types.NewStakerInfo(stakerAddr)
} else {
k.cdc.MustUnmarshal(value, stakerInfo)
}
latestIndex := len(stakerInfo.PriceList) - 1
// calculate amount of virtual LST from nativetoken with price
// amountVirtualLST := convertNativeAmountStringToVirtualLSTAmount(amount, stakerInfo.PriceList[latestIndex].Price)
amountFloat := convertAmountOriginalStringToAmountFloat(amount, stakerInfo.PriceList[latestIndex].Price)
amountInt, _ := amountFloat.Int(nil)
prevTotalDeposit, _ := new(big.Int).SetString(stakerInfo.TotalDeposit, 10)
// update totalDeposit of staker, and price won't change on either deposit or withdraw
stakerInfo.TotalDeposit = prevTotalDeposit.Add(prevTotalDeposit, amountInt).String()
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
return amountInt
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Complete the TODO items.

The function has several TODO comments indicating incomplete implementation. Ensure to address these before finalizing the PR.

Do you want me to generate the implementation for the TODO items or open a GitHub issue to track this task?

Comment on lines 52 to 119
func (k Keeper) UpdateNativeTokenByDelegation(ctx sdk.Context, assetID, operatorAddr, stakerAddr, amountOriginal string) *big.Int {
store := ctx.KVStore(k.storeKey)
keyOperator := types.NativeTokenOperatorKey(assetID, operatorAddr)
operatorInfo := &types.OperatorInfo{}
value := store.Get(keyOperator)
if value == nil {
operatorInfo = types.NewOperatorInfo(operatorAddr)
} else {
k.cdc.MustUnmarshal(value, operatorInfo)
}
stakerInfo := &types.StakerInfo{}
keyStaker := types.NativeTokenStakerKey(assetID, stakerAddr)
if value = store.Get(keyStaker); value == nil {
panic("staker must exist before delegation")
}
k.cdc.MustUnmarshal(value, stakerInfo)

operatorAmountFloat, operatorAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
amountFloat, amountOriginalFloat := parseStakerAmountOriginal(amountOriginal, stakerInfo)

operatorAmountOriginalFloat = operatorAmountOriginalFloat.Add(operatorAmountOriginalFloat, amountOriginalFloat)
operatorAmountFloat = operatorAmountFloat.Add(operatorAmountFloat, amountFloat)

// update operator's price for native token base on new delegation
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: operatorAmountOriginalFloat.Quo(operatorAmountOriginalFloat, operatorAmountFloat).String(),
Block: uint64(ctx.BlockHeight()),
})
tmpAmountInt, _ := operatorAmountFloat.Int(nil)
// update operator's total amount for native token, for this 'amount' we don't disginguish different tokens from different stakers. That difference reflects in 'operator price'
operatorInfo.TotalAmount = tmpAmountInt.String()
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
amountInt, _ := amountFloat.Int(nil)
keyDelegation := types.NativeTokenStakerDelegationKey(assetID, stakerAddr, operatorAddr)
stakerDelegation := &types.StakerDelegationInfo{}
if value = store.Get(keyDelegation); value == nil {
stakerDelegation.Delegations = []*types.DelegationInfo{
{
OperatorAddr: operatorAddr,
Amount: amountInt.String(),
},
}
} else {
k.cdc.MustUnmarshal(value, stakerDelegation)
for idx, delegationInfo := range stakerDelegation.Delegations {
if delegationInfo.OperatorAddr == operatorAddr {
prevAmount, _ := new(big.Int).SetString(delegationInfo.Amount, 10)
if prevAmount = prevAmount.Add(prevAmount, amountInt); prevAmount.Cmp(big.NewInt(0)) <= 0 {
stakerDelegation.Delegations = append(stakerDelegation.Delegations[0:idx], stakerDelegation.Delegations[idx:]...)
} else {
delegationInfo.Amount = prevAmount.Add(prevAmount, amountInt).String()
}
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)
return amountInt
}
}
stakerDelegation.Delegations = append(stakerDelegation.Delegations, &types.DelegationInfo{
OperatorAddr: operatorAddr,
Amount: amountInt.String(),
})
}
// update staker delegation infos for related operators
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)

return amountInt
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace panic statement with proper error handling.

The function contains a panic statement if the staker does not exist. Replace it with proper error handling to avoid crashing the application.

Apply this diff to replace the panic statement with proper error handling:

-    panic("staker must exist before delegation")
+    return errors.New("staker must exist before delegation")

Committable suggestion was skipped due to low confidence.

Comment on lines 122 to 147
func (k Keeper) GetNativeTokenPriceUSDForOperator(ctx sdk.Context, assetID string) (types.Price, error) {
parsedAssetID := strings.Split(assetID, "_")
if len(parsedAssetID) != 3 {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
assetID = strings.Join([]string{parsedAssetID[0], parsedAssetID[1]}, "_")
operatorAddr := parsedAssetID[2]

store := ctx.KVStore(k.storeKey)
key := types.NativeTokenOperatorKey(assetID, operatorAddr)
if value := store.Get(key); value == nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
} else {
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
if err != nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
tmpValue, _ := priceValueFloat.Int(nil)
baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
return baseTokenUSDPrice, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove else block for better readability.

The function contains an else block that can be removed for better readability.

Apply this diff to remove the else block:

-    if value := store.Get(key); value == nil {
-        return types.Price{}, types.ErrGetPriceAssetNotFound
-    } else {
-        operatorInfo := &types.OperatorInfo{}
-        k.cdc.MustUnmarshal(value, operatorInfo)
-        baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
-        if err != nil {
-            return types.Price{}, types.ErrGetPriceAssetNotFound
-        }
-        operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
-        priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
-        priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
-        tmpValue, _ := priceValueFloat.Int(nil)
-        baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
-        return baseTokenUSDPrice, nil
-    }
+    value := store.Get(key)
+    if value == nil {
+        return types.Price{}, types.ErrGetPriceAssetNotFound
+    }
+    operatorInfo := &types.OperatorInfo{}
+    k.cdc.MustUnmarshal(value, operatorInfo)
+    baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
+    if err != nil {
+        return types.Price{}, types.ErrGetPriceAssetNotFound
+    }
+    operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
+    priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
+    priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
+    tmpValue, _ := priceValueFloat.Int(nil)
+    baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
+    return baseTokenUSDPrice, nil
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (k Keeper) GetNativeTokenPriceUSDForOperator(ctx sdk.Context, assetID string) (types.Price, error) {
parsedAssetID := strings.Split(assetID, "_")
if len(parsedAssetID) != 3 {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
assetID = strings.Join([]string{parsedAssetID[0], parsedAssetID[1]}, "_")
operatorAddr := parsedAssetID[2]
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenOperatorKey(assetID, operatorAddr)
if value := store.Get(key); value == nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
} else {
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
if err != nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
tmpValue, _ := priceValueFloat.Int(nil)
baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
return baseTokenUSDPrice, nil
}
func (k Keeper) GetNativeTokenPriceUSDForOperator(ctx sdk.Context, assetID string) (types.Price, error) {
parsedAssetID := strings.Split(assetID, "_")
if len(parsedAssetID) != 3 {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
assetID = strings.Join([]string{parsedAssetID[0], parsedAssetID[1]}, "_")
operatorAddr := parsedAssetID[2]
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenOperatorKey(assetID, operatorAddr)
value := store.Get(key)
if value == nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
if err != nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
tmpValue, _ := priceValueFloat.Int(nil)
baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
return baseTokenUSDPrice, nil
}
Tools
GitHub Check: Run golangci-lint

[warning] 134-134:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Outside diff range, codebase verification and nitpick comments (3)
x/assets/types/expected_keepers.go (1)

12-12: Inconsistent return types in OracleKeeper interface.

The UpdateNativeTokenByDepositOrWithdraw method returns a *big.Int, which is inconsistent with the other methods in the OracleKeeper interface that return oracletypes.Price and error. Consider aligning the return types for consistency and clarity.

  • File: x/assets/types/expected_keepers.go
  • Interface: OracleKeeper
Analysis chain

Review return type and consider simplifying the function name.

The function signature appears correct. However, ensure that returning a pointer to big.Int is consistent with other methods in the interface. Additionally, consider simplifying the function name for readability.

Verify the return type consistency with other methods in the interface:

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify return type consistency with other methods in the interface.

# Test: Search for other methods in the OracleKeeper interface. Expect: Consistent return types.
rg --type go -A 5 $'type OracleKeeper interface {'

Length of output: 173


Script:

#!/bin/bash
# Description: Corrected search for the OracleKeeper interface and its methods.

# Test: Locate the OracleKeeper interface and list its methods to verify return type consistency.
rg --type go 'type OracleKeeper interface {' -A 10

Length of output: 174


Script:

#!/bin/bash
# Description: Attempt to locate the OracleKeeper interface and its methods using a simpler pattern.

# Test: Search for the OracleKeeper interface and list its methods to verify return type consistency.
rg 'OracleKeeper' -A 10 --type go

Length of output: 33214

x/oracle/keeper/native_token.go (1)

199-241: Remove commented-out lines.

The commented-out lines should be removed for better readability.

Apply this diff to remove the commented-out lines:

-	//	lenChanges := len(changes)
app/app.go (1)

554-554: Update function calls to match the new signature

The function NewExocoreApp has been updated to include &app.OracleKeeper as a parameter. Ensure that all calls to this function are updated accordingly to prevent potential issues.

  • Files to update:
    • testutil/network/network.go
    • cmd/exocored/root.go
    • app/test_helpers.go
    • app/ethtest_helper.go
Analysis chain

Verify the function usage in the codebase.

The function signature has been updated to include &app.OracleKeeper. Ensure that all function calls to NewExocoreApp match the new signature.

Run the following script to verify the function usage:

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify all function calls to `NewExocoreApp` match the new signature.

# Test: Search for the function usage. Expect: Only occurrences of the new signature.
rg --type go -A 5 $'NewExocoreApp'

Length of output: 2548

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 60c3f17 and a5433e5.

Files ignored due to path filters (1)
  • x/oracle/types/native_token.pb.go is excluded by !**/*.pb.go
Files selected for processing (18)
  • app/app.go (1 hunks)
  • proto/exocore/oracle/v1/native_token.proto (1 hunks)
  • x/assets/keeper/bank.go (2 hunks)
  • x/assets/types/expected_keepers.go (1 hunks)
  • x/assets/types/general.go (2 hunks)
  • x/delegation/keeper/abci.go (1 hunks)
  • x/delegation/keeper/delegation.go (3 hunks)
  • x/delegation/keeper/keeper.go (2 hunks)
  • x/delegation/types/expected_keepers.go (2 hunks)
  • x/delegation/types/hooks.go (1 hunks)
  • x/operator/keeper/abci.go (2 hunks)
  • x/operator/keeper/usd_value.go (2 hunks)
  • x/oracle/keeper/native_token.go (1 hunks)
  • x/oracle/keeper/prices.go (4 hunks)
  • x/oracle/types/key_native_token.go (1 hunks)
  • x/oracle/types/keys.go (1 hunks)
  • x/oracle/types/native_token.go (1 hunks)
  • x/oracle/types/params.go (1 hunks)
Files skipped from review due to trivial changes (2)
  • x/delegation/types/hooks.go
  • x/oracle/types/keys.go
Additional context used
Learnings (1)
x/operator/keeper/usd_value.go (1)
Learnt from: TimmyExogenous
PR: ExocoreNetwork/exocore#129
File: x/operator/keeper/consensus_keys.go:394-395
Timestamp: 2024-07-25T06:38:34.825Z
Learning: The decimals and prices retrieval in the `GetOrCalculateOperatorUSDValues` function cannot be combined because they call different modules.
golangci-lint
x/oracle/types/key_native_token.go

[warning] 15-15: unused-parameter: parameter 'stakerAddr' seems to be unused, consider removing or renaming it as _

(revive)

x/assets/keeper/bank.go

9-9: ST1019: package "github.com/ExocoreNetwork/exocore/x/assets/types" is being imported more than once

(stylecheck)


10-10: ST1019(related information): other import of "github.com/ExocoreNetwork/exocore/x/assets/types"

(stylecheck)

x/delegation/keeper/abci.go

49-49: File is not gofumpt-ed

(gofumpt)

x/oracle/keeper/prices.go

210-210: Error return value of k.UpdateNativeTokenByBalanceChange is not checked

(errcheck)

x/oracle/keeper/native_token.go

[warning] 134-134: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)

(revive)

GitHub Check: Run golangci-lint
x/oracle/types/key_native_token.go

[warning] 15-15:
unused-parameter: parameter 'stakerAddr' seems to be unused, consider removing or renaming it as _ (revive)

x/assets/keeper/bank.go

[failure] 9-9:
ST1019: package "github.com/ExocoreNetwork/exocore/x/assets/types" is being imported more than once (stylecheck)


[failure] 10-10:
ST1019(related information): other import of "github.com/ExocoreNetwork/exocore/x/assets/types" (stylecheck)

x/delegation/keeper/abci.go

[failure] 49-49:
File is not gofumpt-ed (gofumpt)

x/oracle/keeper/prices.go

[failure] 210-210:
Error return value of k.UpdateNativeTokenByBalanceChange is not checked (errcheck)

x/oracle/keeper/native_token.go

[warning] 134-134:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)

GitHub Check: CodeQL
x/oracle/keeper/native_token.go

[warning] 171-188: Iteration over map
Iteration over map may be a possible source of non-determinism

Additional comments not posted (31)
x/oracle/types/native_token.go (1)

24-36: LGTM!

The function is correctly implemented.

The code changes are approved.

proto/exocore/oracle/v1/native_token.proto (6)

9-13: LGTM!

The message is correctly defined.

The code changes are approved.


15-19: LGTM!

The message is correctly defined.

The code changes are approved.


21-27: LGTM!

The message is correctly defined.

The code changes are approved.


34-36: LGTM!

The message is correctly defined.

The code changes are approved.


38-41: LGTM!

The message is correctly defined.

The code changes are approved.


42-44: LGTM!

The message is correctly defined.

The code changes are approved.

x/oracle/types/key_native_token.go (3)

19-21: LGTM!

The function is correctly implemented.

The code changes are approved.


23-26: LGTM!

The function is correctly implemented.

The code changes are approved.


28-30: LGTM!

The function is correctly implemented.

The code changes are approved.

x/delegation/keeper/keeper.go (2)

20-20: LGTM!

The addition of the oracleKeeper field to the Keeper struct is correctly implemented.

The code changes are approved.


30-38: LGTM!

The update to the NewKeeper constructor is correctly implemented.

The code changes are approved.

x/delegation/types/expected_keepers.go (1)

59-61: LGTM!

The addition of the OracleKeeper interface is correctly implemented.

The code changes are approved.

x/assets/keeper/bank.go (2)

49-51: Address the TODO comment.

The TODO comment indicates that the check for withdrawal amounts greater than the withdrawable amount is skipped. Ensure this edge case is handled to prevent potential issues.

Do you want me to help implement the check for this edge case?


13-13: Verify the function usage in the codebase.

Ensure that all function calls to PerformDepositOrWithdraw match the new logic.

Run the following script to verify the function usage:

x/operator/keeper/abci.go (1)

5-5: LGTM!

The new import is necessary for the updated logic.

The code changes are approved.

x/assets/types/general.go (3)

174-177: LGTM!

The function is correctly implemented. Remember to address the TODO comment in future updates.

The code changes are approved.


179-182: LGTM!

The function is correctly implemented. Remember to address the TODO comment in future updates.

The code changes are approved.


184-186: LGTM!

The function is correctly implemented.

The code changes are approved.

x/oracle/keeper/prices.go (2)

58-61: LGTM!

The function is correctly implemented. Ensure that the GetNativeTokenPriceUSDForOperator method is properly tested.

The code changes are approved.


285-291: LGTM!

The function is correctly implemented.

The code changes are approved.

x/delegation/keeper/delegation.go (2)

52-54: LGTM!

The function is correctly implemented. Ensure that the UpdateNativeTokenByDelegation method is properly tested.

The code changes are approved.


124-126: LGTM!

The function is correctly implemented. Ensure that the UpdateNativeTokenByDelegation method is properly tested.

The code changes are approved.

x/oracle/keeper/native_token.go (5)

150-159: LGTM!

The function is correctly implemented and retrieves the staker list as expected.

The code changes are approved.


192-197: LGTM!

The function is correctly implemented and retrieves the staker list as expected.

The code changes are approved.


244-249: LGTM!

The function is correctly implemented and retrieves the latest operator price as expected.

The code changes are approved.


251-256: LGTM!

The function is correctly implemented and converts the amount string to a float as expected.

The code changes are approved.


258-280: LGTM!

The functions are correctly implemented and retrieve and parse amounts as expected.

The code changes are approved.

x/oracle/types/params.go (1)

496-501: LGTM!

The function is correctly implemented and retrieves asset IDs as expected.

The code changes are approved.

x/operator/keeper/usd_value.go (1)

303-305: LGTM!

The function is correctly calculating the USD value for an operator. The new conditional check for native tokens is appropriately handling the asset ID modification.

The code changes are approved.

app/app.go (1)

554-554: LGTM!

The addition of &app.OracleKeeper to the function signature is correctly implemented.

The code changes are approved.

Comment on lines 3 to 21
// TODO: vlaidatorIndex need bridge data
// func NewStakerInfo(stakerAddr string, validatorIndex uint64) *StakerInfo {
func NewStakerInfo(stakerAddr string) *StakerInfo {
return &StakerInfo{
StakerAddr: stakerAddr,
StakerIndex: 0,
// TODO: need bridge information
// ValidatorIndexs: []uint64{validatorIndex},
ValidatorIndexs: make([]uint64, 0, 1),
TotalDeposit: "0",
PriceList: []*PriceInfo{
{
// default price should be 1
Price: "1",
Block: 0,
RoundID: 0,
},
},
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Address TODO comments and confirm initialization.

The function initializes the StakerInfo struct correctly. However, the TODO comments indicate incomplete implementation. Confirm if initializing ValidatorIndexs with an empty slice is intentional.

Do you want me to generate the missing bridge information or open a GitHub issue to track this task?

Comment on lines +29 to +32
message NativeTokenPrice {
string token = 1;
repeated PriceInfo prece_list = 2;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Fix the typo in the field name.

There is a typo in the field name prece_list. It should be price_list.

Apply this diff to fix the typo:

 message NativeTokenPrice {
   string token = 1;
-  repeated PriceInfo prece_list = 2;
+  repeated PriceInfo price_list = 2;
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
message NativeTokenPrice {
string token = 1;
repeated PriceInfo prece_list = 2;
}
message NativeTokenPrice {
string token = 1;
repeated PriceInfo price_list = 2;
}

Comment on lines 15 to 17
func NativeTokenStakerDelegationKey(assetID, stakerAddr, operatorAddr string) []byte {
return append([]byte(NativeTokenStakerDelegationKeyPrefix), []byte(assetID)...)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove unused parameter.

The stakerAddr parameter is not used in the function and should be removed to avoid confusion.

Apply this diff to remove the unused parameter:

-func NativeTokenStakerDelegationKey(assetID, stakerAddr, operatorAddr string) []byte {
+func NativeTokenStakerDelegationKey(assetID, operatorAddr string) []byte {
  return append([]byte(NativeTokenStakerDelegationKeyPrefix), []byte(assetID)...)
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func NativeTokenStakerDelegationKey(assetID, stakerAddr, operatorAddr string) []byte {
return append([]byte(NativeTokenStakerDelegationKeyPrefix), []byte(assetID)...)
}
func NativeTokenStakerDelegationKey(assetID, operatorAddr string) []byte {
return append([]byte(NativeTokenStakerDelegationKeyPrefix), []byte(assetID)...)
}
Tools
golangci-lint

[warning] 15-15: unused-parameter: parameter 'stakerAddr' seems to be unused, consider removing or renaming it as _

(revive)

GitHub Check: Run golangci-lint

[warning] 15-15:
unused-parameter: parameter 'stakerAddr' seems to be unused, consider removing or renaming it as _ (revive)

@@ -43,6 +46,11 @@
return errorsmod.Wrapf(assetstypes.ErrInvalidOperationType, "the operation type is: %v", params.Action)
}

if types.IsNativeToken(assetID) {
// TODO: we skip check for case like withdraw amount>withdrawable is fine since it will fail for later check and the state will be rollback
actualOpAmount = sdkmath.NewIntFromBigInt(k.UpdateNativeTokenByDepositOrWithdraw(ctx, assetID, hexutil.Encode(params.StakerAddress), params.OpAmount.String()))
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Add tests for the new function.

Ensure that the UpdateNativeTokenByDepositOrWithdraw function is thoroughly tested to verify its correctness.

Do you want me to generate the unit testing code or open a GitHub issue to track this task?

@@ -46,6 +46,7 @@
}
// and the other is the fact that it matures at the next block
err = k.StorePendingUndelegationRecord(ctx, recordKey, record)
//TODO: remove previous index with currentHeight for pendingUndelegationRecord
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Address the TODO comment.

The TODO comment indicates a planned enhancement to remove the previous index with the current height for pendingUndelegationRecord. Ensure this enhancement is tracked and implemented.

Do you want me to open a GitHub issue to track this task?

Tools
golangci-lint

49-49: File is not gofumpt-ed

(gofumpt)

GitHub Check: Run golangci-lint

[failure] 49-49:
File is not gofumpt-ed (gofumpt)

Comment on lines 198 to 211
// update for native tokens
// TODO: set hooks as a genral approach
var p types.Params
// get params from cache if exists
if agc != nil {
p = agc.GetParams()
} else {
p = k.GetParams(ctx)
}
assetIDs := p.GetAssetIDsFromTokenID(tokenID)
for _, assetID := range assetIDs {
if assetstypes.IsNativeToken(assetID) {
k.UpdateNativeTokenByBalanceChange(ctx, assetID, []byte(priceTR.Price), roundID)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Check the error return value.

The error return value of k.UpdateNativeTokenByBalanceChange is not checked. Ensure to handle the error properly.

Apply this diff to check the error return value:

 for _, assetID := range assetIDs {
  if assetstypes.IsNativeToken(assetID) {
-   k.UpdateNativeTokenByBalanceChange(ctx, assetID, []byte(priceTR.Price), roundID)
+   if err := k.UpdateNativeTokenByBalanceChange(ctx, assetID, []byte(priceTR.Price), roundID); err != nil {
+     return false
+   }
  }
}

Committable suggestion was skipped due to low confidence.

Tools
golangci-lint

210-210: Error return value of k.UpdateNativeTokenByBalanceChange is not checked

(errcheck)

GitHub Check: Run golangci-lint

[failure] 210-210:
Error return value of k.UpdateNativeTokenByBalanceChange is not checked (errcheck)

Comment on lines 25 to 47
func (k Keeper) UpdateNativeTokenByDepositOrWithdraw(ctx sdk.Context, assetID, stakerAddr, amount string) *big.Int {
// TODO: just convert the number for assets module, and don't store state in oracleModule, can use cache only here
// TODO: we havn't included validatorIndex here, need the bridge info
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenStakerKey(assetID, stakerAddr)
stakerInfo := &types.StakerInfo{}
if value := store.Get(key); value == nil {
// create a new item for this staker
stakerInfo = types.NewStakerInfo(stakerAddr)
} else {
k.cdc.MustUnmarshal(value, stakerInfo)
}
latestIndex := len(stakerInfo.PriceList) - 1
// calculate amount of virtual LST from nativetoken with price
// amountVirtualLST := convertNativeAmountStringToVirtualLSTAmount(amount, stakerInfo.PriceList[latestIndex].Price)
amountFloat := convertAmountOriginalStringToAmountFloat(amount, stakerInfo.PriceList[latestIndex].Price)
amountInt, _ := amountFloat.Int(nil)
prevTotalDeposit, _ := new(big.Int).SetString(stakerInfo.TotalDeposit, 10)
// update totalDeposit of staker, and price won't change on either deposit or withdraw
stakerInfo.TotalDeposit = prevTotalDeposit.Add(prevTotalDeposit, amountInt).String()
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
return amountInt
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Address TODO items.

The function is correctly updating the staker's total deposit. However, the TODO comments indicate that the implementation is not complete, and there are potential issues with state management and validator index inclusion.

Do you want me to help address the TODO items or open a GitHub issue to track this task?

Comment on lines 52 to 119
func (k Keeper) UpdateNativeTokenByDelegation(ctx sdk.Context, assetID, operatorAddr, stakerAddr, amountOriginal string) *big.Int {
store := ctx.KVStore(k.storeKey)
keyOperator := types.NativeTokenOperatorKey(assetID, operatorAddr)
operatorInfo := &types.OperatorInfo{}
value := store.Get(keyOperator)
if value == nil {
operatorInfo = types.NewOperatorInfo(operatorAddr)
} else {
k.cdc.MustUnmarshal(value, operatorInfo)
}
stakerInfo := &types.StakerInfo{}
keyStaker := types.NativeTokenStakerKey(assetID, stakerAddr)
if value = store.Get(keyStaker); value == nil {
panic("staker must exist before delegation")
}
k.cdc.MustUnmarshal(value, stakerInfo)

operatorAmountFloat, operatorAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
amountFloat, amountOriginalFloat := parseStakerAmountOriginal(amountOriginal, stakerInfo)

operatorAmountOriginalFloat = operatorAmountOriginalFloat.Add(operatorAmountOriginalFloat, amountOriginalFloat)
operatorAmountFloat = operatorAmountFloat.Add(operatorAmountFloat, amountFloat)

// update operator's price for native token base on new delegation
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: operatorAmountOriginalFloat.Quo(operatorAmountOriginalFloat, operatorAmountFloat).String(),
Block: uint64(ctx.BlockHeight()),
})
tmpAmountInt, _ := operatorAmountFloat.Int(nil)
// update operator's total amount for native token, for this 'amount' we don't disginguish different tokens from different stakers. That difference reflects in 'operator price'
operatorInfo.TotalAmount = tmpAmountInt.String()
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
amountInt, _ := amountFloat.Int(nil)
keyDelegation := types.NativeTokenStakerDelegationKey(assetID, stakerAddr, operatorAddr)
stakerDelegation := &types.StakerDelegationInfo{}
if value = store.Get(keyDelegation); value == nil {
stakerDelegation.Delegations = []*types.DelegationInfo{
{
OperatorAddr: operatorAddr,
Amount: amountInt.String(),
},
}
} else {
k.cdc.MustUnmarshal(value, stakerDelegation)
for idx, delegationInfo := range stakerDelegation.Delegations {
if delegationInfo.OperatorAddr == operatorAddr {
prevAmount, _ := new(big.Int).SetString(delegationInfo.Amount, 10)
if prevAmount = prevAmount.Add(prevAmount, amountInt); prevAmount.Cmp(big.NewInt(0)) <= 0 {
stakerDelegation.Delegations = append(stakerDelegation.Delegations[0:idx], stakerDelegation.Delegations[idx:]...)
} else {
delegationInfo.Amount = prevAmount.Add(prevAmount, amountInt).String()
}
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)
return amountInt
}
}
stakerDelegation.Delegations = append(stakerDelegation.Delegations, &types.DelegationInfo{
OperatorAddr: operatorAddr,
Amount: amountInt.String(),
})
}
// update staker delegation infos for related operators
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)

return amountInt
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace panic with proper error handling.

The panic statement should be replaced with proper error handling to avoid crashing the application if the staker does not exist.

Apply this diff to replace the panic statement with proper error handling:

-	if value = store.Get(keyStaker); value == nil {
-		panic("staker must exist before delegation")
-	}
+	if value = store.Get(keyStaker); value == nil {
+		return nil, errors.New("staker must exist before delegation")
+	}

Committable suggestion was skipped due to low confidence.

Comment on lines 122 to 147
func (k Keeper) GetNativeTokenPriceUSDForOperator(ctx sdk.Context, assetID string) (types.Price, error) {
parsedAssetID := strings.Split(assetID, "_")
if len(parsedAssetID) != 3 {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
assetID = strings.Join([]string{parsedAssetID[0], parsedAssetID[1]}, "_")
operatorAddr := parsedAssetID[2]

store := ctx.KVStore(k.storeKey)
key := types.NativeTokenOperatorKey(assetID, operatorAddr)
if value := store.Get(key); value == nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
} else {
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
if err != nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
tmpValue, _ := priceValueFloat.Int(nil)
baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
return baseTokenUSDPrice, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Simplify code by removing else block.

The code can be simplified by removing the else block since the if block ends with a return statement.

Apply this diff to simplify the code:

	if value := store.Get(key); value == nil {
		return types.Price{}, types.ErrGetPriceAssetNotFound
	}
-	else {
-		operatorInfo := &types.OperatorInfo{}
-		k.cdc.MustUnmarshal(value, operatorInfo)
-		baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
-		if err != nil {
-			return types.Price{}, types.ErrGetPriceAssetNotFound
-		}
-		operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
-		priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
-		priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
-		tmpValue, _ := priceValueFloat.Int(nil)
-		baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
-		return baseTokenUSDPrice, nil
-	}
+	operatorInfo := &types.OperatorInfo{}
+	k.cdc.MustUnmarshal(value, operatorInfo)
+	baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
+	if err != nil {
+		return types.Price{}, types.ErrGetPriceAssetNotFound
+	}
+	operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
+	priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
+	priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
+	tmpValue, _ := priceValueFloat.Int(nil)
+	baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
+	return baseTokenUSDPrice, nil
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (k Keeper) GetNativeTokenPriceUSDForOperator(ctx sdk.Context, assetID string) (types.Price, error) {
parsedAssetID := strings.Split(assetID, "_")
if len(parsedAssetID) != 3 {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
assetID = strings.Join([]string{parsedAssetID[0], parsedAssetID[1]}, "_")
operatorAddr := parsedAssetID[2]
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenOperatorKey(assetID, operatorAddr)
if value := store.Get(key); value == nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
} else {
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
if err != nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
tmpValue, _ := priceValueFloat.Int(nil)
baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
return baseTokenUSDPrice, nil
}
func (k Keeper) GetNativeTokenPriceUSDForOperator(ctx sdk.Context, assetID string) (types.Price, error) {
parsedAssetID := strings.Split(assetID, "_")
if len(parsedAssetID) != 3 {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
assetID = strings.Join([]string{parsedAssetID[0], parsedAssetID[1]}, "_")
operatorAddr := parsedAssetID[2]
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenOperatorKey(assetID, operatorAddr)
if value := store.Get(key); value == nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
if err != nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
priceValueFloat := new(big.Float).SetInt(baseTokenUSDPrice.Value.BigInt())
priceValueFloat = priceValueFloat.Mul(priceValueFloat, operatorPriceFloat)
tmpValue, _ := priceValueFloat.Int(nil)
baseTokenUSDPrice.Value = sdkmath.NewIntFromBigInt(tmpValue)
return baseTokenUSDPrice, nil
}
Tools
golangci-lint

[warning] 134-134: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)

(revive)

GitHub Check: Run golangci-lint

[warning] 134-134:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)

Comment on lines 161 to 189
func (k Keeper) UpdateNativeTokenByBalanceChange(ctx sdk.Context, assetID string, rawData []byte, roundID uint64) error {
sl := k.getStakerList(ctx, assetID)
if len(sl.StakerAddrs) == 0 {
return errors.New("staker list is empty")
}
stakerChanges, err := parseBalanceChange(rawData, sl)
if err != nil {
return err
}
store := ctx.KVStore(k.storeKey)
for stakerAddr, change := range stakerChanges {
value := store.Get(types.NativeTokenStakerKey(assetID, stakerAddr))
if value == nil {
return errors.New("stakerInfo does not exist")
}
stakerInfo := &types.StakerInfo{}
k.cdc.MustUnmarshal(value, stakerInfo)
changeOriginalFloat := big.NewFloat(float64(change))
totalAmountFloat, totalAmountOriginalFloat := parseStakerAmount(stakerInfo.TotalDeposit, stakerInfo)
totalAmountOriginalFloat = totalAmountOriginalFloat.Add(totalAmountOriginalFloat, changeOriginalFloat)
// update staker price based on beacon chain effective balance change
stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
Price: totalAmountOriginalFloat.Quo(totalAmountOriginalFloat, totalAmountFloat).String(),
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
// update related operator's price
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Address non-determinism in map iteration.

Iteration over a map may introduce non-determinism. Consider sorting the map keys before iteration to ensure deterministic behavior.

Apply this diff to address the non-determinism issue:

+	keys := make([]string, 0, len(stakerChanges))
+	for k := range stakerChanges {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
	for _, stakerAddr := range keys {
		change := stakerChanges[stakerAddr]
		value := store.Get(types.NativeTokenStakerKey(assetID, stakerAddr))
		if value == nil {
			return errors.New("stakerInfo does not exist")
		}
		stakerInfo := &types.StakerInfo{}
		k.cdc.MustUnmarshal(value, stakerInfo)
		changeOriginalFloat := big.NewFloat(float64(change))
		totalAmountFloat, totalAmountOriginalFloat := parseStakerAmount(stakerInfo.TotalDeposit, stakerInfo)
		totalAmountOriginalFloat = totalAmountOriginalFloat.Add(totalAmountOriginalFloat, changeOriginalFloat)
		// update staker price based on beacon chain effective balance change
		stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
			Price:   totalAmountOriginalFloat.Quo(totalAmountOriginalFloat, totalAmountFloat).String(),
			Block:   uint64(ctx.BlockHeight()),
			RoundID: roundID,
		})
		// update related operator's price
	}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (k Keeper) UpdateNativeTokenByBalanceChange(ctx sdk.Context, assetID string, rawData []byte, roundID uint64) error {
sl := k.getStakerList(ctx, assetID)
if len(sl.StakerAddrs) == 0 {
return errors.New("staker list is empty")
}
stakerChanges, err := parseBalanceChange(rawData, sl)
if err != nil {
return err
}
store := ctx.KVStore(k.storeKey)
for stakerAddr, change := range stakerChanges {
value := store.Get(types.NativeTokenStakerKey(assetID, stakerAddr))
if value == nil {
return errors.New("stakerInfo does not exist")
}
stakerInfo := &types.StakerInfo{}
k.cdc.MustUnmarshal(value, stakerInfo)
changeOriginalFloat := big.NewFloat(float64(change))
totalAmountFloat, totalAmountOriginalFloat := parseStakerAmount(stakerInfo.TotalDeposit, stakerInfo)
totalAmountOriginalFloat = totalAmountOriginalFloat.Add(totalAmountOriginalFloat, changeOriginalFloat)
// update staker price based on beacon chain effective balance change
stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
Price: totalAmountOriginalFloat.Quo(totalAmountOriginalFloat, totalAmountFloat).String(),
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
// update related operator's price
}
return nil
func (k Keeper) UpdateNativeTokenByBalanceChange(ctx sdk.Context, assetID string, rawData []byte, roundID uint64) error {
sl := k.getStakerList(ctx, assetID)
if len(sl.StakerAddrs) == 0 {
return errors.New("staker list is empty")
}
stakerChanges, err := parseBalanceChange(rawData, sl)
if err != nil {
return err
}
store := ctx.KVStore(k.storeKey)
keys := make([]string, 0, len(stakerChanges))
for k := range stakerChanges {
keys = append(keys, k)
}
sort.Strings(keys)
for _, stakerAddr := range keys {
change := stakerChanges[stakerAddr]
value := store.Get(types.NativeTokenStakerKey(assetID, stakerAddr))
if value == nil {
return errors.New("stakerInfo does not exist")
}
stakerInfo := &types.StakerInfo{}
k.cdc.MustUnmarshal(value, stakerInfo)
changeOriginalFloat := big.NewFloat(float64(change))
totalAmountFloat, totalAmountOriginalFloat := parseStakerAmount(stakerInfo.TotalDeposit, stakerInfo)
totalAmountOriginalFloat = totalAmountOriginalFloat.Add(totalAmountOriginalFloat, changeOriginalFloat)
// update staker price based on beacon chain effective balance change
stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
Price: totalAmountOriginalFloat.Quo(totalAmountOriginalFloat, totalAmountFloat).String(),
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
// update related operator's price
}
return nil
Tools
GitHub Check: CodeQL

[warning] 171-188: Iteration over map
Iteration over map may be a possible source of non-determinism

Comment on lines +162 to +210
for stakerAddr, change := range stakerChanges {
key := types.NativeTokenStakerKey(assetID, stakerAddr)
value := store.Get(key)
if value == nil {
return errors.New("stakerInfo does not exist")
}
stakerInfo := &types.StakerInfo{}
k.cdc.MustUnmarshal(value, stakerInfo)
changeOriginalFloat := sdkmath.LegacyNewDec(int64(change))
totalAmountFloat, totalAmountOriginalFloat := parseStakerAmountInt(stakerInfo.TotalDeposit, stakerInfo)
totalAmountOriginalFloat = totalAmountOriginalFloat.Add(changeOriginalFloat)
prevStakerPrice := getLatestStakerPriceFloat(stakerInfo)
// update staker price based on beacon chain effective balance change
stakerPrice := totalAmountOriginalFloat.Quo(totalAmountFloat)
stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
Price: stakerPrice,
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
// update related operator's price
keyStakerDelegations := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
value = store.Get(keyStakerDelegations)
if value != nil {
delegationInfo := &types.StakerDelegationInfo{}
k.cdc.MustUnmarshal(value, delegationInfo)
for _, delegation := range delegationInfo.Delegations {
keyOperator := types.NativeTokenOperatorKey(assetID, delegation.OperatorAddr)
value = store.Get(keyOperator)
if value == nil {
panic("staker delegation related to operator not exists")
}
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
AmountFloat, prevAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
delta := delegation.Amount.ToLegacyDec().Mul(stakerPrice.Sub(prevStakerPrice))
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: prevAmountOriginalFloat.Add(delta).Quo(AmountFloat),
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
}

}

}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
keyOperator := types.NativeTokenOperatorKey(assetID, delegation.OperatorAddr)
value = store.Get(keyOperator)
if value == nil {
panic("staker delegation related to operator not exists")

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods Warning

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Outside diff range, codebase verification and nitpick comments (1)
x/oracle/keeper/native_token.go (1)

221-264: Fix the typo in the comment.

There is a typo in the comment on line 234. The word "effect" should be "effective".

Apply this diff to fix the typo:

-				// effect balance  f stakerAddr[index] has changed
+				// effective balance of stakerAddr[index] has changed
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between a5433e5 and 359d0f2.

Files ignored due to path filters (1)
  • x/oracle/types/native_token.pb.go is excluded by !**/*.pb.go
Files selected for processing (8)
  • proto/exocore/oracle/v1/native_token.proto (1 hunks)
  • x/assets/keeper/bank.go (2 hunks)
  • x/assets/types/expected_keepers.go (1 hunks)
  • x/delegation/keeper/delegation.go (3 hunks)
  • x/delegation/types/expected_keepers.go (1 hunks)
  • x/oracle/keeper/native_token.go (1 hunks)
  • x/oracle/types/key_native_token.go (1 hunks)
  • x/oracle/types/native_token.go (1 hunks)
Files skipped from review as they are similar to previous changes (4)
  • x/delegation/keeper/delegation.go
  • x/delegation/types/expected_keepers.go
  • x/oracle/types/key_native_token.go
  • x/oracle/types/native_token.go
Additional context used
buf
proto/exocore/oracle/v1/native_token.proto

5-5: import "gogoproto/gogo.proto": file does not exist

(COMPILE)

GitHub Check: Run golangci-lint
x/assets/keeper/bank.go

[failure] 9-9:
ST1019: package "github.com/ExocoreNetwork/exocore/x/assets/types" is being imported more than once (stylecheck)


[failure] 10-10:
ST1019(related information): other import of "github.com/ExocoreNetwork/exocore/x/assets/types" (stylecheck)

x/oracle/keeper/native_token.go

[warning] 128-128:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)

GitHub Check: CodeQL
x/oracle/keeper/native_token.go

[warning] 162-210: Iteration over map
Iteration over map may be a possible source of non-determinism


[warning] 193-193: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt

Additional comments not posted (14)
x/assets/types/expected_keepers.go (1)

11-11: LGTM!

The method signature is correctly implemented and follows the expected pattern for handling large integers in a financial context.

The code changes are approved.

proto/exocore/oracle/v1/native_token.proto (7)

9-13: LGTM!

The message structure is correctly implemented and follows the expected pattern for handling price information.

The code changes are approved.


15-19: LGTM!

The message structure is correctly implemented and follows the expected pattern for handling operator information.

The code changes are approved.


21-27: LGTM!

The message structure is correctly implemented and follows the expected pattern for handling staker information.

The code changes are approved.


34-36: LGTM!

The message structure is correctly implemented and follows the expected pattern for handling staker lists.

The code changes are approved.


38-41: LGTM!

The message structure is correctly implemented and follows the expected pattern for handling delegation information.

The code changes are approved.


42-44: LGTM!

The message structure is correctly implemented and follows the expected pattern for handling staker delegation information.

The code changes are approved.


29-32: Fix the typo in the field name.

There is a typo in the field name prece_list. It should be price_list.

Apply this diff to fix the typo:

message NativeTokenPrice {
  string token = 1;
-  repeated PriceInfo prece_list = 2;
+  repeated PriceInfo price_list = 2;
}

Likely invalid or redundant comment.

x/oracle/keeper/native_token.go (6)

141-150: LGTM!

The function is correctly implemented.

The code changes are approved.


214-219: LGTM!

The function is correctly implemented.

The code changes are approved.


266-269: LGTM!

The function is correctly implemented.

The code changes are approved.


271-274: LGTM!

The function is correctly implemented.

The code changes are approved.


276-279: LGTM!

The function is correctly implemented.

The code changes are approved.


281-303: LGTM!

The functions are correctly implemented.

The code changes are approved.

Comment on lines 49 to 52
if types.IsNativeToken(assetID) {
// TODO: we skip check for case like withdraw amount>withdrawable is fine since it will fail for later check and the state will be rollback
actualOpAmount = k.UpdateNativeTokenByDepositOrWithdraw(ctx, assetID, hexutil.Encode(params.StakerAddress), params.OpAmount)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Add tests for the new logic.

Ensure that the UpdateNativeTokenByDepositOrWithdraw function is thoroughly tested to verify its correctness.

Do you want me to generate the unit testing code or open a GitHub issue to track this task?

Comment on lines 9 to 10
"github.com/ExocoreNetwork/exocore/x/assets/types"
assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types"
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove duplicate import.

The package github.com/ExocoreNetwork/exocore/x/assets/types is being imported more than once. Remove the duplicate import to clean up the code.

Apply this diff to remove the duplicate import:

-	"github.com/ExocoreNetwork/exocore/x/assets/types"
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"github.com/ExocoreNetwork/exocore/x/assets/types"
assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types"
assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types"
Tools
GitHub Check: Run golangci-lint

[failure] 9-9:
ST1019: package "github.com/ExocoreNetwork/exocore/x/assets/types" is being imported more than once (stylecheck)


[failure] 10-10:
ST1019(related information): other import of "github.com/ExocoreNetwork/exocore/x/assets/types" (stylecheck)

Comment on lines 24 to 43
func (k Keeper) UpdateNativeTokenByDepositOrWithdraw(ctx sdk.Context, assetID, stakerAddr string, amount sdkmath.Int) sdkmath.Int {
// TODO: just convert the number for assets module, and don't store state in oracleModule, can use cache only here
// TODO: we havn't included validatorIndex here, need the bridge info
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenStakerKey(assetID, stakerAddr)
stakerInfo := &types.StakerInfo{}
if value := store.Get(key); value == nil {
// create a new item for this staker
stakerInfo = types.NewStakerInfo(stakerAddr)
} else {
k.cdc.MustUnmarshal(value, stakerInfo)
}
latestIndex := len(stakerInfo.PriceList) - 1
// calculate amount of virtual LST from nativetoken with price
amountInt := convertAmountOriginalIntToAmountFloat(amount, stakerInfo.PriceList[latestIndex].Price).RoundInt()
stakerInfo.TotalDeposit = stakerInfo.TotalDeposit.Add(amountInt)
// update totalDeposit of staker, and price won't change on either deposit or withdraw
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
return amountInt
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Complete the TODO items.

The function has several TODO comments indicating incomplete implementation. Ensure to address these before finalizing the PR.

Do you want me to generate the implementation for the TODO items or open a GitHub issue to track this task?

Comment on lines 152 to 211
func (k Keeper) UpdateNativeTokenByBalanceChange(ctx sdk.Context, assetID string, rawData []byte, roundID uint64) error {
sl := k.getStakerList(ctx, assetID)
if len(sl.StakerAddrs) == 0 {
return errors.New("staker list is empty")
}
stakerChanges, err := parseBalanceChange(rawData, sl)
if err != nil {
return err
}
store := ctx.KVStore(k.storeKey)
for stakerAddr, change := range stakerChanges {
key := types.NativeTokenStakerKey(assetID, stakerAddr)
value := store.Get(key)
if value == nil {
return errors.New("stakerInfo does not exist")
}
stakerInfo := &types.StakerInfo{}
k.cdc.MustUnmarshal(value, stakerInfo)
changeOriginalFloat := sdkmath.LegacyNewDec(int64(change))
totalAmountFloat, totalAmountOriginalFloat := parseStakerAmountInt(stakerInfo.TotalDeposit, stakerInfo)
totalAmountOriginalFloat = totalAmountOriginalFloat.Add(changeOriginalFloat)
prevStakerPrice := getLatestStakerPriceFloat(stakerInfo)
// update staker price based on beacon chain effective balance change
stakerPrice := totalAmountOriginalFloat.Quo(totalAmountFloat)
stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
Price: stakerPrice,
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
// update related operator's price
keyStakerDelegations := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
value = store.Get(keyStakerDelegations)
if value != nil {
delegationInfo := &types.StakerDelegationInfo{}
k.cdc.MustUnmarshal(value, delegationInfo)
for _, delegation := range delegationInfo.Delegations {
keyOperator := types.NativeTokenOperatorKey(assetID, delegation.OperatorAddr)
value = store.Get(keyOperator)
if value == nil {
panic("staker delegation related to operator not exists")
}
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
AmountFloat, prevAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
delta := delegation.Amount.ToLegacyDec().Mul(stakerPrice.Sub(prevStakerPrice))
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: prevAmountOriginalFloat.Add(delta).Quo(AmountFloat),
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
}

}

}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace panic statement with proper error handling.

The function contains a panic statement if the staker delegation related to the operator does not exist. Replace it with proper error handling to avoid crashing the application.

Apply this diff to replace the panic statement with proper error handling:

-	if value == nil {
-		panic("staker delegation related to operator not exists")
-	}
+	if value == nil {
+		return errors.New("staker delegation related to operator not exists")
+	}

Address non-determinism in map iteration.

Iteration over a map may introduce non-determinism. Consider sorting the map keys before iteration to ensure deterministic behavior.

Apply this diff to address the non-determinism issue:

+	keys := make([]string, 0, len(stakerChanges))
+	for k := range stakerChanges {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
	for stakerAddr, change := range keys {
		change := stakerChanges[stakerAddr]
		value := store.Get(types.NativeTokenStakerKey(assetID, stakerAddr))
		if value == nil {
			return errors.New("stakerInfo does not exist")
		}
		stakerInfo := &types.StakerInfo{}
		k.cdc.MustUnmarshal(value, stakerInfo)
		changeOriginalFloat := sdkmath.LegacyNewDec(int64(change))
		totalAmountFloat, totalAmountOriginalFloat := parseStakerAmountInt(stakerInfo.TotalDeposit, stakerInfo)
		totalAmountOriginalFloat = totalAmountOriginalFloat.Add(changeOriginalFloat)
		prevStakerPrice := getLatestStakerPriceFloat(stakerInfo)
		// update staker price based on beacon chain effective balance change
		stakerPrice := totalAmountOriginalFloat.Quo(totalAmountFloat)
		stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
			Price:   stakerPrice,
			Block:   uint64(ctx.BlockHeight()),
			RoundID: roundID,
		})
		bz := k.cdc.MustMarshal(stakerInfo)
		store.Set(key, bz)
		// update related operator's price
		keyStakerDelegations := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
		value = store.Get(keyStakerDelegations)
		if value != nil {
			delegationInfo := &types.StakerDelegationInfo{}
			k.cdc.MustUnmarshal(value, delegationInfo)
			for _, delegation := range delegationInfo.Delegations {
				keyOperator := types.NativeTokenOperatorKey(assetID, delegation.OperatorAddr)
				value = store.Get(keyOperator)
				if value == nil {
					return errors.New("staker delegation related to operator not exists")
				}
				operatorInfo := &types.OperatorInfo{}
				k.cdc.MustUnmarshal(value, operatorInfo)
				AmountFloat, prevAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
				delta := delegation.Amount.ToLegacyDec().Mul(stakerPrice.Sub(prevStakerPrice))
				operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
					Price:   prevAmountOriginalFloat.Add(delta).Quo(AmountFloat),
					Block:   uint64(ctx.BlockHeight()),
					RoundID: roundID,
				})
				bz := k.cdc.MustMarshal(operatorInfo)
				store.Set(keyOperator, bz)
			}
		}
	}
Tools
GitHub Check: CodeQL

[warning] 162-210: Iteration over map
Iteration over map may be a possible source of non-determinism


[warning] 193-193: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt

Comment on lines 48 to 113
func (k Keeper) UpdateNativeTokenByDelegation(ctx sdk.Context, assetID, operatorAddr, stakerAddr string, amountOriginal sdkmath.Int) sdkmath.Int {
store := ctx.KVStore(k.storeKey)
keyOperator := types.NativeTokenOperatorKey(assetID, operatorAddr)
operatorInfo := &types.OperatorInfo{}
value := store.Get(keyOperator)
if value == nil {
operatorInfo = types.NewOperatorInfo(operatorAddr)
} else {
k.cdc.MustUnmarshal(value, operatorInfo)
}
stakerInfo := &types.StakerInfo{}
keyStaker := types.NativeTokenStakerKey(assetID, stakerAddr)
if value = store.Get(keyStaker); value == nil {
panic("staker must exist before delegation")
}
k.cdc.MustUnmarshal(value, stakerInfo)

operatorAmountFloat, operatorAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
amountFloat, amountOriginalFloat := parseStakerAmountOriginalInt(amountOriginal, stakerInfo)

operatorAmountOriginalFloat = operatorAmountOriginalFloat.Add(amountOriginalFloat)
operatorAmountFloat = operatorAmountFloat.Add(amountFloat)

// update operator's price for native token base on new delegation
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat),
Block: uint64(ctx.BlockHeight()),
})

// update operator's total amount for native token, for this 'amount' we don't disginguish different tokens from different stakers. That difference reflects in 'operator price'
operatorInfo.TotalAmount = operatorAmountFloat.RoundInt()
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
amountInt := amountFloat.RoundInt()
// update staker's related operator list
keyDelegation := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
stakerDelegation := &types.StakerDelegationInfo{}
if value = store.Get(keyDelegation); value == nil {
stakerDelegation.Delegations = []*types.DelegationInfo{
{
OperatorAddr: operatorAddr,
Amount: amountInt,
},
}
} else {
k.cdc.MustUnmarshal(value, stakerDelegation)
for idx, delegationInfo := range stakerDelegation.Delegations {
if delegationInfo.OperatorAddr == operatorAddr {
if delegationInfo.Amount = delegationInfo.Amount.Add(amountInt); !delegationInfo.Amount.IsPositive() {
stakerDelegation.Delegations = append(stakerDelegation.Delegations[0:idx], stakerDelegation.Delegations[idx:]...)
}
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)
return amountInt
}
}
stakerDelegation.Delegations = append(stakerDelegation.Delegations, &types.DelegationInfo{
OperatorAddr: operatorAddr,
Amount: amountInt,
})
}
// update staker delegation infos for related operators
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)

return amountInt
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace panic statement with proper error handling.

The function contains a panic statement if the staker does not exist. Replace it with proper error handling to avoid crashing the application.

Apply this diff to replace the panic statement with proper error handling:

-	if value = store.Get(keyStaker); value == nil {
-		panic("staker must exist before delegation")
-	}
+	if value = store.Get(keyStaker); value == nil {
+		return nil, errors.New("staker must exist before delegation")
+	}

Committable suggestion was skipped due to low confidence.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Outside diff range, codebase verification and nitpick comments (2)
x/oracle/keeper/native_token_test.go (1)

165-282: LGTM! Consider adding more comments for better readability.

The implementation of the convertBalanceChangeToBytes function is correct. Adding more comments to explain the bitwise operations would improve readability and maintainability.

x/oracle/keeper/native_token.go (1)

307-366: LGTM! Consider adding more comments for better readability.

The implementation of the parseBalanceChange function is correct. Adding more comments to explain the bitwise operations would improve readability and maintainability.

Tools
golangci-lint

343-343: commentFormatting: put a space between // and comment text

(gocritic)

GitHub Check: Run golangci-lint

[failure] 343-343:
commentFormatting: put a space between // and comment text (gocritic)

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 359d0f2 and d2e117d.

Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
Files selected for processing (3)
  • go.mod (1 hunks)
  • x/oracle/keeper/native_token.go (1 hunks)
  • x/oracle/keeper/native_token_test.go (1 hunks)
Additional context used
golangci-lint
x/oracle/keeper/native_token.go

343-343: commentFormatting: put a space between // and comment text

(gocritic)


[warning] 211-211: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)

(revive)

GitHub Check: CodeQL
x/oracle/keeper/native_token.go

[warning] 248-296: Iteration over map
Iteration over map may be a possible source of non-determinism


[warning] 279-279: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt

GitHub Check: Run golangci-lint
x/oracle/keeper/native_token.go

[failure] 343-343:
commentFormatting: put a space between // and comment text (gocritic)


[warning] 211-211:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)

Additional comments not posted (7)
go.mod (1)

158-158: Verify the compatibility and security of the added dependency.

The new indirect dependency github.com/imroc/biu should be checked for compatibility with the existing codebase and potential security vulnerabilities. Ensure that the dependency is well-maintained and supported.

Run the following script to check for any known vulnerabilities in the added dependency:

x/oracle/keeper/native_token_test.go (1)

50-163: LGTM! Consider adding more edge cases.

The test cases for the native token lifecycle are well-implemented. Consider adding more edge cases, such as invalid inputs or edge conditions, to ensure comprehensive coverage.

x/oracle/keeper/native_token.go (5)

21-30: LGTM!

The implementation of the GetStakerInfo function is correct.


32-41: LGTM!

The implementation of the GetStakerDelegations function is correct.


43-52: LGTM!

The implementation of the GetOperatorInfo function is correct.


224-233: LGTM!

The implementation of the GetStakerList function is correct.


300-305: LGTM!

The implementation of the getStakerList function is correct.

Comment on lines 199 to 221
func (k Keeper) GetNativeTokenPriceUSDForOperator(ctx sdk.Context, assetID string) (types.Price, error) {
parsedAssetID := strings.Split(assetID, "_")
if len(parsedAssetID) != 3 {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
assetID = strings.Join([]string{parsedAssetID[0], parsedAssetID[1]}, "_")
operatorAddr := parsedAssetID[2]

store := ctx.KVStore(k.storeKey)
key := types.NativeTokenOperatorKey(assetID, operatorAddr)
if value := store.Get(key); value == nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
} else {
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
if err != nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
baseTokenUSDPrice.Value = (baseTokenUSDPrice.Value.ToLegacyDec().Mul(operatorPriceFloat)).RoundInt()
return baseTokenUSDPrice, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove else block for better readability.

The function contains an else block that can be removed for better readability.

Apply this diff to remove the else block:

	if value := store.Get(key); value == nil {
		return types.Price{}, types.ErrGetPriceAssetNotFound
	}
-	else {
-		operatorInfo := &types.OperatorInfo{}
-		k.cdc.MustUnmarshal(value, operatorInfo)
-		baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
-		if err != nil {
-			return types.Price{}, types.ErrGetPriceAssetNotFound
-		}
-		operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
-		baseTokenUSDPrice.Value = (baseTokenUSDPrice.Value.ToLegacyDec().Mul(operatorPriceFloat)).RoundInt()
-		return baseTokenUSDPrice, nil
-	}
+	operatorInfo := &types.OperatorInfo{}
+	k.cdc.MustUnmarshal(value, operatorInfo)
+	baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
+	if err != nil {
+		return types.Price{}, types.ErrGetPriceAssetNotFound
+	}
+	operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
+	baseTokenUSDPrice.Value = (baseTokenUSDPrice.Value.ToLegacyDec().Mul(operatorPriceFloat)).RoundInt()
+	return baseTokenUSDPrice, nil
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (k Keeper) GetNativeTokenPriceUSDForOperator(ctx sdk.Context, assetID string) (types.Price, error) {
parsedAssetID := strings.Split(assetID, "_")
if len(parsedAssetID) != 3 {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
assetID = strings.Join([]string{parsedAssetID[0], parsedAssetID[1]}, "_")
operatorAddr := parsedAssetID[2]
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenOperatorKey(assetID, operatorAddr)
if value := store.Get(key); value == nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
} else {
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
if err != nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
baseTokenUSDPrice.Value = (baseTokenUSDPrice.Value.ToLegacyDec().Mul(operatorPriceFloat)).RoundInt()
return baseTokenUSDPrice, nil
}
func (k Keeper) GetNativeTokenPriceUSDForOperator(ctx sdk.Context, assetID string) (types.Price, error) {
parsedAssetID := strings.Split(assetID, "_")
if len(parsedAssetID) != 3 {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
assetID = strings.Join([]string{parsedAssetID[0], parsedAssetID[1]}, "_")
operatorAddr := parsedAssetID[2]
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenOperatorKey(assetID, operatorAddr)
if value := store.Get(key); value == nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
if err != nil {
return types.Price{}, types.ErrGetPriceAssetNotFound
}
operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
baseTokenUSDPrice.Value = (baseTokenUSDPrice.Value.ToLegacyDec().Mul(operatorPriceFloat)).RoundInt()
return baseTokenUSDPrice, nil
}
Tools
golangci-lint

[warning] 211-211: indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary)

(revive)

GitHub Check: Run golangci-lint

[warning] 211-211:
indent-error-flow: if block ends with a return statement, so drop this else and outdent its block (move short variable declaration to its own line if necessary) (revive)

Comment on lines +235 to +297
func (k Keeper) UpdateNativeTokenByBalanceChange(ctx sdk.Context, assetID string, rawData []byte, roundID uint64) error {
if len(rawData) < 32 {
return errors.New("length of indicate maps for stakers shoule be exactly 32 bytes")
}
sl := k.getStakerList(ctx, assetID)
if len(sl.StakerAddrs) == 0 {
return errors.New("staker list is empty")
}
stakerChanges, err := parseBalanceChange(rawData, sl)
if err != nil {
return err
}
store := ctx.KVStore(k.storeKey)
for stakerAddr, change := range stakerChanges {
key := types.NativeTokenStakerKey(assetID, stakerAddr)
value := store.Get(key)
if value == nil {
return errors.New("stakerInfo does not exist")
}
stakerInfo := &types.StakerInfo{}
k.cdc.MustUnmarshal(value, stakerInfo)
changeOriginalFloat := sdkmath.LegacyNewDec(int64(change))
totalAmountFloat, totalAmountOriginalFloat := parseStakerAmountInt(stakerInfo.TotalDeposit, stakerInfo)
totalAmountOriginalFloat = totalAmountOriginalFloat.Add(changeOriginalFloat)
prevStakerPrice := getLatestStakerPriceFloat(stakerInfo)
// update staker price based on beacon chain effective balance change
stakerPrice := totalAmountOriginalFloat.Quo(totalAmountFloat)
stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
Price: stakerPrice,
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
// update related operator's price
keyStakerDelegations := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
value = store.Get(keyStakerDelegations)
if value != nil {
delegationInfo := &types.StakerDelegationInfo{}
k.cdc.MustUnmarshal(value, delegationInfo)
for _, delegation := range delegationInfo.Delegations {
keyOperator := types.NativeTokenOperatorKey(assetID, delegation.OperatorAddr)
value = store.Get(keyOperator)
if value == nil {
panic("staker delegation related to operator not exists")
}
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
AmountFloat, prevAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
delta := delegation.Amount.ToLegacyDec().Mul(stakerPrice.Sub(prevStakerPrice))
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: prevAmountOriginalFloat.Add(delta).Quo(AmountFloat),
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
}

}

}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace panic statement with proper error handling.

The function contains a panic statement if the staker delegation related to the operator does not exist. Replace it with proper error handling to avoid crashing the application.

Apply this diff to replace the panic statement with proper error handling:

-	if value == nil {
-		panic("staker delegation related to operator not exists")
-	}
+	if value == nil {
+		return errors.New("staker delegation related to operator not exists")
+	}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (k Keeper) UpdateNativeTokenByBalanceChange(ctx sdk.Context, assetID string, rawData []byte, roundID uint64) error {
if len(rawData) < 32 {
return errors.New("length of indicate maps for stakers shoule be exactly 32 bytes")
}
sl := k.getStakerList(ctx, assetID)
if len(sl.StakerAddrs) == 0 {
return errors.New("staker list is empty")
}
stakerChanges, err := parseBalanceChange(rawData, sl)
if err != nil {
return err
}
store := ctx.KVStore(k.storeKey)
for stakerAddr, change := range stakerChanges {
key := types.NativeTokenStakerKey(assetID, stakerAddr)
value := store.Get(key)
if value == nil {
return errors.New("stakerInfo does not exist")
}
stakerInfo := &types.StakerInfo{}
k.cdc.MustUnmarshal(value, stakerInfo)
changeOriginalFloat := sdkmath.LegacyNewDec(int64(change))
totalAmountFloat, totalAmountOriginalFloat := parseStakerAmountInt(stakerInfo.TotalDeposit, stakerInfo)
totalAmountOriginalFloat = totalAmountOriginalFloat.Add(changeOriginalFloat)
prevStakerPrice := getLatestStakerPriceFloat(stakerInfo)
// update staker price based on beacon chain effective balance change
stakerPrice := totalAmountOriginalFloat.Quo(totalAmountFloat)
stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
Price: stakerPrice,
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
// update related operator's price
keyStakerDelegations := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
value = store.Get(keyStakerDelegations)
if value != nil {
delegationInfo := &types.StakerDelegationInfo{}
k.cdc.MustUnmarshal(value, delegationInfo)
for _, delegation := range delegationInfo.Delegations {
keyOperator := types.NativeTokenOperatorKey(assetID, delegation.OperatorAddr)
value = store.Get(keyOperator)
if value == nil {
panic("staker delegation related to operator not exists")
}
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
AmountFloat, prevAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
delta := delegation.Amount.ToLegacyDec().Mul(stakerPrice.Sub(prevStakerPrice))
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: prevAmountOriginalFloat.Add(delta).Quo(AmountFloat),
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
}
}
}
return nil
func (k Keeper) UpdateNativeTokenByBalanceChange(ctx sdk.Context, assetID string, rawData []byte, roundID uint64) error {
if len(rawData) < 32 {
return errors.New("length of indicate maps for stakers shoule be exactly 32 bytes")
}
sl := k.getStakerList(ctx, assetID)
if len(sl.StakerAddrs) == 0 {
return errors.New("staker list is empty")
}
stakerChanges, err := parseBalanceChange(rawData, sl)
if err != nil {
return err
}
store := ctx.KVStore(k.storeKey)
for stakerAddr, change := range stakerChanges {
key := types.NativeTokenStakerKey(assetID, stakerAddr)
value := store.Get(key)
if value == nil {
return errors.New("stakerInfo does not exist")
}
stakerInfo := &types.StakerInfo{}
k.cdc.MustUnmarshal(value, stakerInfo)
changeOriginalFloat := sdkmath.LegacyNewDec(int64(change))
totalAmountFloat, totalAmountOriginalFloat := parseStakerAmountInt(stakerInfo.TotalDeposit, stakerInfo)
totalAmountOriginalFloat = totalAmountOriginalFloat.Add(changeOriginalFloat)
prevStakerPrice := getLatestStakerPriceFloat(stakerInfo)
// update staker price based on beacon chain effective balance change
stakerPrice := totalAmountOriginalFloat.Quo(totalAmountFloat)
stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
Price: stakerPrice,
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
// update related operator's price
keyStakerDelegations := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
value = store.Get(keyStakerDelegations)
if value != nil {
delegationInfo := &types.StakerDelegationInfo{}
k.cdc.MustUnmarshal(value, delegationInfo)
for _, delegation := range delegationInfo.Delegations {
keyOperator := types.NativeTokenOperatorKey(assetID, delegation.OperatorAddr)
value = store.Get(keyOperator)
if value == nil {
return errors.New("staker delegation related to operator not exists")
}
operatorInfo := &types.OperatorInfo{}
k.cdc.MustUnmarshal(value, operatorInfo)
AmountFloat, prevAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
delta := delegation.Amount.ToLegacyDec().Mul(stakerPrice.Sub(prevStakerPrice))
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: prevAmountOriginalFloat.Add(delta).Quo(AmountFloat),
Block: uint64(ctx.BlockHeight()),
RoundID: roundID,
})
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
}
}
}
return nil
Tools
GitHub Check: CodeQL

[warning] 248-296: Iteration over map
Iteration over map may be a possible source of non-determinism


[warning] 279-279: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt

Comment on lines +114 to +197
// UpdateNativeTokenByDelegation update operator's price, operator's totalAmount, operator's totalShare, staker's share bsed on either delegation or undelegation
// this amount passed in from delegation hooks represent originalToken(not virtualLST)
func (k Keeper) UpdateNativeTokenByDelegation(ctx sdk.Context, assetID, operatorAddr, stakerAddr string, amountOriginal sdkmath.Int) sdkmath.Int {
store := ctx.KVStore(k.storeKey)
keyOperator := types.NativeTokenOperatorKey(assetID, operatorAddr)
operatorInfo := &types.OperatorInfo{}
valueOperator := store.Get(keyOperator)
if valueOperator == nil {
operatorInfo = types.NewOperatorInfo(operatorAddr)
} else {
k.cdc.MustUnmarshal(valueOperator, operatorInfo)
}
stakerInfo := &types.StakerInfo{}
keyStaker := types.NativeTokenStakerKey(assetID, stakerAddr)
value := store.Get(keyStaker)
if value == nil {
panic("staker must exist before delegation")
}
k.cdc.MustUnmarshal(value, stakerInfo)

operatorAmountFloat, operatorAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
amountFloat, amountOriginalFloat := parseStakerAmountOriginalInt(amountOriginal, stakerInfo)

operatorAmountOriginalFloat = operatorAmountOriginalFloat.Add(amountOriginalFloat)
operatorAmountFloat = operatorAmountFloat.Add(amountFloat)

// update operator's price for native token base on new delegation
if valueOperator == nil {
// undelegation should not happen on nil operatorInfo, this is delegate case
operatorInfo.PriceList = []*types.PriceInfo{
{
Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat),
Block: uint64(ctx.BlockHeight()),
},
}
} else if operatorAmountFloat.IsPositive() {
// if amount <=0 thies operatorInfo will be rmoved, no need to append any price
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat),
Block: uint64(ctx.BlockHeight()),
})
}
// update operator's total amount for native token, for this 'amount' we don't disginguish different tokens from different stakers. That difference reflects in 'operator price'
operatorInfo.TotalAmount = operatorAmountFloat.RoundInt()
if operatorInfo.TotalAmount.IsPositive() {
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
} else {
store.Delete(keyOperator)
}
amountInt := amountFloat.RoundInt()
// update staker's related operator list
keyDelegation := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
stakerDelegation := &types.StakerDelegationInfo{}
if value = store.Get(keyDelegation); value == nil {
stakerDelegation.Delegations = []*types.DelegationInfo{
{
OperatorAddr: operatorAddr,
Amount: amountInt,
},
}
} else {
k.cdc.MustUnmarshal(value, stakerDelegation)
for idx, delegationInfo := range stakerDelegation.Delegations {
if delegationInfo.OperatorAddr == operatorAddr {
if delegationInfo.Amount = delegationInfo.Amount.Add(amountInt); !delegationInfo.Amount.IsPositive() {
stakerDelegation.Delegations = append(stakerDelegation.Delegations[:idx], stakerDelegation.Delegations[idx+1:]...)
}
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)
return amountInt
}
}
stakerDelegation.Delegations = append(stakerDelegation.Delegations, &types.DelegationInfo{
OperatorAddr: operatorAddr,
Amount: amountInt,
})
}
// update staker delegation infos for related operators
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)

return amountInt
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace panic statement with proper error handling.

The function contains a panic statement if the staker does not exist. Replace it with proper error handling to avoid crashing the application.

Apply this diff to replace the panic statement with proper error handling:

-	if value == nil {
-		panic("staker must exist before delegation")
-	}
+	if value == nil {
+		return sdkmath.Int{}, errors.New("staker must exist before delegation")
+	}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// UpdateNativeTokenByDelegation update operator's price, operator's totalAmount, operator's totalShare, staker's share bsed on either delegation or undelegation
// this amount passed in from delegation hooks represent originalToken(not virtualLST)
func (k Keeper) UpdateNativeTokenByDelegation(ctx sdk.Context, assetID, operatorAddr, stakerAddr string, amountOriginal sdkmath.Int) sdkmath.Int {
store := ctx.KVStore(k.storeKey)
keyOperator := types.NativeTokenOperatorKey(assetID, operatorAddr)
operatorInfo := &types.OperatorInfo{}
valueOperator := store.Get(keyOperator)
if valueOperator == nil {
operatorInfo = types.NewOperatorInfo(operatorAddr)
} else {
k.cdc.MustUnmarshal(valueOperator, operatorInfo)
}
stakerInfo := &types.StakerInfo{}
keyStaker := types.NativeTokenStakerKey(assetID, stakerAddr)
value := store.Get(keyStaker)
if value == nil {
panic("staker must exist before delegation")
}
k.cdc.MustUnmarshal(value, stakerInfo)
operatorAmountFloat, operatorAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
amountFloat, amountOriginalFloat := parseStakerAmountOriginalInt(amountOriginal, stakerInfo)
operatorAmountOriginalFloat = operatorAmountOriginalFloat.Add(amountOriginalFloat)
operatorAmountFloat = operatorAmountFloat.Add(amountFloat)
// update operator's price for native token base on new delegation
if valueOperator == nil {
// undelegation should not happen on nil operatorInfo, this is delegate case
operatorInfo.PriceList = []*types.PriceInfo{
{
Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat),
Block: uint64(ctx.BlockHeight()),
},
}
} else if operatorAmountFloat.IsPositive() {
// if amount <=0 thies operatorInfo will be rmoved, no need to append any price
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat),
Block: uint64(ctx.BlockHeight()),
})
}
// update operator's total amount for native token, for this 'amount' we don't disginguish different tokens from different stakers. That difference reflects in 'operator price'
operatorInfo.TotalAmount = operatorAmountFloat.RoundInt()
if operatorInfo.TotalAmount.IsPositive() {
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
} else {
store.Delete(keyOperator)
}
amountInt := amountFloat.RoundInt()
// update staker's related operator list
keyDelegation := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
stakerDelegation := &types.StakerDelegationInfo{}
if value = store.Get(keyDelegation); value == nil {
stakerDelegation.Delegations = []*types.DelegationInfo{
{
OperatorAddr: operatorAddr,
Amount: amountInt,
},
}
} else {
k.cdc.MustUnmarshal(value, stakerDelegation)
for idx, delegationInfo := range stakerDelegation.Delegations {
if delegationInfo.OperatorAddr == operatorAddr {
if delegationInfo.Amount = delegationInfo.Amount.Add(amountInt); !delegationInfo.Amount.IsPositive() {
stakerDelegation.Delegations = append(stakerDelegation.Delegations[:idx], stakerDelegation.Delegations[idx+1:]...)
}
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)
return amountInt
}
}
stakerDelegation.Delegations = append(stakerDelegation.Delegations, &types.DelegationInfo{
OperatorAddr: operatorAddr,
Amount: amountInt,
})
}
// update staker delegation infos for related operators
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)
return amountInt
}
// UpdateNativeTokenByDelegation update operator's price, operator's totalAmount, operator's totalShare, staker's share bsed on either delegation or undelegation
// this amount passed in from delegation hooks represent originalToken(not virtualLST)
func (k Keeper) UpdateNativeTokenByDelegation(ctx sdk.Context, assetID, operatorAddr, stakerAddr string, amountOriginal sdkmath.Int) (sdkmath.Int, error) {
store := ctx.KVStore(k.storeKey)
keyOperator := types.NativeTokenOperatorKey(assetID, operatorAddr)
operatorInfo := &types.OperatorInfo{}
valueOperator := store.Get(keyOperator)
if valueOperator == nil {
operatorInfo = types.NewOperatorInfo(operatorAddr)
} else {
k.cdc.MustUnmarshal(valueOperator, operatorInfo)
}
stakerInfo := &types.StakerInfo{}
keyStaker := types.NativeTokenStakerKey(assetID, stakerAddr)
value := store.Get(keyStaker)
if value == nil {
return sdkmath.Int{}, errors.New("staker must exist before delegation")
}
k.cdc.MustUnmarshal(value, stakerInfo)
operatorAmountFloat, operatorAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
amountFloat, amountOriginalFloat := parseStakerAmountOriginalInt(amountOriginal, stakerInfo)
operatorAmountOriginalFloat = operatorAmountOriginalFloat.Add(amountOriginalFloat)
operatorAmountFloat = operatorAmountFloat.Add(amountFloat)
// update operator's price for native token base on new delegation
if valueOperator == nil {
// undelegation should not happen on nil operatorInfo, this is delegate case
operatorInfo.PriceList = []*types.PriceInfo{
{
Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat),
Block: uint64(ctx.BlockHeight()),
},
}
} else if operatorAmountFloat.IsPositive() {
// if amount <=0 thies operatorInfo will be rmoved, no need to append any price
operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
Price: operatorAmountOriginalFloat.Quo(operatorAmountFloat),
Block: uint64(ctx.BlockHeight()),
})
}
// update operator's total amount for native token, for this 'amount' we don't disginguish different tokens from different stakers. That difference reflects in 'operator price'
operatorInfo.TotalAmount = operatorAmountFloat.RoundInt()
if operatorInfo.TotalAmount.IsPositive() {
bz := k.cdc.MustMarshal(operatorInfo)
store.Set(keyOperator, bz)
} else {
store.Delete(keyOperator)
}
amountInt := amountFloat.RoundInt()
// update staker's related operator list
keyDelegation := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
stakerDelegation := &types.StakerDelegationInfo{}
if value = store.Get(keyDelegation); value == nil {
stakerDelegation.Delegations = []*types.DelegationInfo{
{
OperatorAddr: operatorAddr,
Amount: amountInt,
},
}
} else {
k.cdc.MustUnmarshal(value, stakerDelegation)
for idx, delegationInfo := range stakerDelegation.Delegations {
if delegationInfo.OperatorAddr == operatorAddr {
if delegationInfo.Amount = delegationInfo.Amount.Add(amountInt); !delegationInfo.Amount.IsPositive() {
stakerDelegation.Delegations = append(stakerDelegation.Delegations[:idx], stakerDelegation.Delegations[idx+1:]...)
}
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)
return amountInt, nil
}
}
stakerDelegation.Delegations = append(stakerDelegation.Delegations, &types.DelegationInfo{
OperatorAddr: operatorAddr,
Amount: amountInt,
})
}
// update staker delegation infos for related operators
value = k.cdc.MustMarshal(stakerDelegation)
store.Set(keyDelegation, value)
return amountInt, nil
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between d2e117d and a4d13e6.

Files selected for processing (5)
  • x/assets/keeper/bank.go (2 hunks)
  • x/delegation/keeper/abci.go (1 hunks)
  • x/oracle/keeper/native_token.go (1 hunks)
  • x/oracle/keeper/prices.go (4 hunks)
  • x/oracle/types/errors.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • x/delegation/keeper/abci.go
Additional context used
GitHub Check: CodeQL
x/oracle/keeper/native_token.go

[warning] 248-296: Iteration over map
Iteration over map may be a possible source of non-determinism


[warning] 279-279: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt

Additional comments not posted (26)
x/oracle/types/errors.go (2)

16-16: LGTM!

The addition of the new error constant updateNativeTokenVirtualPriceFail is straightforward and follows the existing pattern.


21-27: LGTM!

The registration of the new error constant ErrUpdateNativeTokenVirtualPriceFail is straightforward and follows the existing pattern.

x/assets/keeper/bank.go (3)

11-12: LGTM!

The import of the hexutil package from the Ethereum library is necessary for the new logic added to the PerformDepositOrWithdraw function.


50-50: LGTM!

The call to UpdateNativeTokenByDepositOrWithdraw is necessary for the new logic to update the native token's state.


52-52: LGTM!

The empty line improves readability by separating the new logic from the existing code.

x/oracle/keeper/prices.go (9)

5-5: LGTM!

The import of the strings package is necessary for the new logic added to the GetSpecifiedAssetsPrice method.


58-61: LGTM!

The new logic enhances the method to handle native tokens specifically, which is a necessary addition for the new feature.


198-215: LGTM!

The new logic enhances the method to handle native tokens specifically, which is a necessary addition for the new feature. The error handling is also appropriately added.


287-293: LGTM!

The signature change enhances the method's utility by allowing the caller to access the updated round ID directly after it is incremented.


196-197: LGTM!

The call to IncreaseNextRoundID and the assignment of its return value to roundID is necessary for the new logic to update the native token's state.


216-216: LGTM!

The empty line improves readability by separating the new logic from the existing code.


208-215: LGTM!

The new logic enhances the method to handle native tokens specifically, which is a necessary addition for the new feature.


210-213: LGTM!

The call to UpdateNativeTokenByBalanceChange and the error handling is necessary for the new logic to update the native token's state.


216-216: LGTM!

The empty line improves readability by separating the new logic from the existing code.

x/oracle/keeper/native_token.go (12)

21-30: LGTM!

The code changes are approved.


32-41: LGTM!

The code changes are approved.


43-52: LGTM!

The code changes are approved.


114-197: Replace panic statement with proper error handling.

The function contains a panic statement if the staker does not exist. Replace it with proper error handling to avoid crashing the application.

Apply this diff to replace the panic statement with proper error handling:

-	if value == nil {
-		panic("staker must exist before delegation")
-	}
+	if value == nil {
+		return sdkmath.Int{}, errors.New("staker must exist before delegation")
+	}

199-222: Remove else block for better readability.

The function contains an else block that can be removed for better readability.

Apply this diff to remove the else block:

	if value := store.Get(key); value == nil {
		return types.Price{}, types.ErrGetPriceAssetNotFound
	}
-	else {
-		operatorInfo := &types.OperatorInfo{}
-		k.cdc.MustUnmarshal(value, operatorInfo)
-		baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
-		if err != nil {
-			return types.Price{}, types.ErrGetPriceAssetNotFound
-		}
-		operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
-		baseTokenUSDPrice.Value = (baseTokenUSDPrice.Value.ToLegacyDec().Mul(operatorPriceFloat)).RoundInt()
-		return baseTokenUSDPrice, nil
-	}
+	operatorInfo := &types.OperatorInfo{}
+	k.cdc.MustUnmarshal(value, operatorInfo)
+	baseTokenUSDPrice, err := k.GetSpecifiedAssetsPrice(ctx, assetstypes.GetBaseTokenForNativeToken(assetID))
+	if err != nil {
+		return types.Price{}, types.ErrGetPriceAssetNotFound
+	}
+	operatorPriceFloat := getLatestOperatorPriceFloat(operatorInfo)
+	baseTokenUSDPrice.Value = (baseTokenUSDPrice.Value.ToLegacyDec().Mul(operatorPriceFloat)).RoundInt()
+	return baseTokenUSDPrice, nil

224-233: LGTM!

The code changes are approved.


235-297: Replace panic statement with proper error handling.

The function contains a panic statement if the staker delegation related to the operator does not exist. Replace it with proper error handling to avoid crashing the application.

Apply this diff to replace the panic statement with proper error handling:

-	if value == nil {
-		panic("staker delegation related to operator not exists")
-	}
+	if value == nil {
+		return errors.New("staker delegation related to operator not exists")
+	}

Address non-determinism in map iteration.

Iteration over a map may introduce non-determinism. Consider sorting the map keys before iteration to ensure deterministic behavior.

Apply this diff to address the non-determinism issue:

+	keys := make([]string, 0, len(stakerChanges))
+	for k := range stakerChanges {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
	for _, stakerAddr := range keys {
		change := stakerChanges[stakerAddr]
		value := store.Get(types.NativeTokenStakerKey(assetID, stakerAddr))
		if value == nil {
			return errors.New("stakerInfo does not exist")
		}
		stakerInfo := &types.StakerInfo{}
		k.cdc.MustUnmarshal(value, stakerInfo)
		changeOriginalFloat := sdkmath.LegacyNewDec(int64(change))
		totalAmountFloat, totalAmountOriginalFloat := parseStakerAmountInt(stakerInfo.TotalDeposit, stakerInfo)
		totalAmountOriginalFloat = totalAmountOriginalFloat.Add(changeOriginalFloat)
		prevStakerPrice := getLatestStakerPriceFloat(stakerInfo)
		// update staker price based on beacon chain effective balance change
		stakerPrice := totalAmountOriginalFloat.Quo(totalAmountFloat)
		stakerInfo.PriceList = append(stakerInfo.PriceList, &types.PriceInfo{
			Price:   stakerPrice,
			Block:   uint64(ctx.BlockHeight()),
			RoundID: roundID,
		})
		bz := k.cdc.MustMarshal(stakerInfo)
		store.Set(key, bz)
		// update related operator's price
		keyStakerDelegations := types.NativeTokenStakerDelegationKey(assetID, stakerAddr)
		value = store.Get(keyStakerDelegations)
		if value != nil {
			delegationInfo := &types.StakerDelegationInfo{}
			k.cdc.MustUnmarshal(value, delegationInfo)
			for _, delegation := range delegationInfo.Delegations {
				keyOperator := types.NativeTokenOperatorKey(assetID, delegation.OperatorAddr)
				value = store.Get(keyOperator)
				if value == nil {
					return errors.New("staker delegation related to operator not exists")
				}
				operatorInfo := &types.OperatorInfo{}
				k.cdc.MustUnmarshal(value, operatorInfo)
				AmountFloat, prevAmountOriginalFloat := getOperatorAmountFloat(operatorInfo)
				delta := delegation.Amount.ToLegacyDec().Mul(stakerPrice.Sub(prevStakerPrice))
				operatorInfo.PriceList = append(operatorInfo.PriceList, &types.PriceInfo{
					Price:   prevAmountOriginalFloat.Add(delta).Quo(AmountFloat),
					Block:   uint64(ctx.BlockHeight()),
					RoundID: roundID,
				})
				bz := k.cdc.MustMarshal(operatorInfo)
				store.Set(keyOperator, bz)
			}
		}
	}
Tools
GitHub Check: CodeQL

[warning] 248-296: Iteration over map
Iteration over map may be a possible source of non-determinism


[warning] 279-279: Panic in BeginBock or EndBlock consensus methods
Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt


300-305: LGTM!

The code changes are approved.


307-366: LGTM! Ensure the length of the change value is at least 1 bit.

The function is correctly parsing the balance changes. However, the length of the change value must be at least 1 bit, and this should be enforced.

The code changes are approved.


413-416: LGTM!

The code changes are approved.


418-421: LGTM!

The code changes are approved.


423-450: LGTM!

The code changes are approved.

Comment on lines +48 to +51
if assetstypes.IsNativeToken(assetID) {
// TODO: we skip check for case like withdraw amount>withdrawable is fine since it will fail for later check and the state will be rollback
actualOpAmount = k.UpdateNativeTokenByDepositOrWithdraw(ctx, assetID, hexutil.Encode(params.StakerAddress), params.OpAmount)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM! But add tests for the new logic.

The new logic enhances the function to handle native tokens specifically, which is a necessary addition for the new feature. Ensure that the UpdateNativeTokenByDepositOrWithdraw function is thoroughly tested to verify its correctness.

Do you want me to generate the unit testing code or open a GitHub issue to track this task?

@@ -43,6 +45,11 @@
return errorsmod.Wrapf(assetstypes.ErrInvalidOperationType, "the operation type is: %v", params.Action)
}

if assetstypes.IsNativeToken(assetID) {
// TODO: we skip check for case like withdraw amount>withdrawable is fine since it will fail for later check and the state will be rollback
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Address the TODO comment.

The TODO comment indicates a potential edge case regarding withdrawal amounts. Ensure that this edge case is handled appropriately in the future.

Do you want me to help address this TODO comment or open a GitHub issue to track this task?

roundID := k.IncreaseNextRoundID(ctx, tokenID)

// update for native tokens
// TODO: set hooks as a genral approach
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Address the TODO comment.

The TODO comment indicates a potential enhancement to set hooks as a general approach. Ensure that this enhancement is handled appropriately in the future.

Do you want me to help address this TODO comment or open a GitHub issue to track this task?

Comment on lines +57 to +112
func (k Keeper) UpdateNativeTokenByDepositOrWithdraw(ctx sdk.Context, assetID, stakerAddr string, amount sdkmath.Int) sdkmath.Int {
// TODO: just convert the number for assets module, and don't store state in oracleModule, can use cache only here
// TODO: we havn't included validatorIndex here, need the bridge info
store := ctx.KVStore(k.storeKey)
key := types.NativeTokenStakerKey(assetID, stakerAddr)
stakerInfo := &types.StakerInfo{}
if value := store.Get(key); value == nil {
// create a new item for this staker
stakerInfo = types.NewStakerInfo(stakerAddr)
} else {
k.cdc.MustUnmarshal(value, stakerInfo)
}
latestIndex := len(stakerInfo.PriceList) - 1
// calculate amount of virtual LST from nativetoken with price
amountInt := convertAmountOriginalIntToAmountFloat(amount, stakerInfo.PriceList[latestIndex].Price).RoundInt()
stakerInfo.TotalDeposit = stakerInfo.TotalDeposit.Add(amountInt)

keyStakerList := types.NativeTokenStakerListKey(assetID)
valueStakerList := store.Get(keyStakerList)
stakerList := &types.StakerList{}
if valueStakerList != nil {
k.cdc.MustUnmarshal(valueStakerList, stakerList)
}
exists := false
for idx, stakerExists := range stakerList.StakerAddrs {
if stakerExists == stakerAddr {
if !stakerInfo.TotalDeposit.IsPositive() {
stakerList.StakerAddrs = append(stakerList.StakerAddrs[:idx], stakerList.StakerAddrs[idx+1:]...)
valueStakerList = k.cdc.MustMarshal(stakerList)
store.Set(keyStakerList, valueStakerList)
}
exists = true
stakerInfo.StakerIndex = int64(idx)
break
}
}

// update totalDeposit of staker, and price won't change on either deposit or withdraw
if !stakerInfo.TotalDeposit.IsPositive() {
store.Delete(key)
} else {
bz := k.cdc.MustMarshal(stakerInfo)
store.Set(key, bz)
}

if !exists {
if !stakerInfo.TotalDeposit.IsPositive() {
// this should not happened, if a staker execute the 'withdraw' action, he must have already been in the stakerList
return amountInt
}
stakerList.StakerAddrs = append(stakerList.StakerAddrs, stakerAddr)
valueStakerList = k.cdc.MustMarshal(stakerList)
store.Set(keyStakerList, valueStakerList)
}
return amountInt
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder: Complete the TODO items.

The function is correctly updating the staker's total deposit. However, the TODO comments indicate that the implementation is not complete, and there are potential issues with state management and validator index inclusion.

Do you want me to help address the TODO items or open a GitHub issue to track this task?

@leonz789 leonz789 changed the title [WIP]feat(oracle): support update for eth-native-restaking balance change feat(oracle): support update for eth-native-restaking balance change Aug 30, 2024
Copy link
Contributor

@TimmyExogenous TimmyExogenous left a comment

Choose a reason for hiding this comment

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

It seems that this PR treats the native token as an independent token. It might be possible to treat the same native token from different stakers as a base token for processing. The oracle would only need to update the changes in the native token amount for each staker. When the amount increases, it would be equivalent to a deposit operation by the staker. When the amount decreases, it would be treated as a slash operation on the staker, similar to an immediate undelegation where the undelegated share gets slashed. This way, there would be no need to modify the existing processes for deposit, withdraw, delegation, undelegation, and updating voting power.

@@ -62,6 +64,11 @@ func (k *Keeper) UpdateVotingPower(ctx sdk.Context, avsAddr string) error {
SelfUSDValue: sdkmath.LegacyNewDec(0),
ActiveUSDValue: sdkmath.LegacyNewDec(0),
}
for _, assetID := range assetstypes.GetNativeTokenAssetIDs() {
if _, ok := prices[assetID]; ok {
prices[assetID], err = k.oracleKeeper.GetSpecifiedAssetsPrice(ctx, strings.Join([]string{assetID, operator}, "_"))
Copy link
Contributor

Choose a reason for hiding this comment

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

should the returned error be addressed?

@cloud8little cloud8little added this to the Testnet V6 milestone Sep 10, 2024
@cloud8little cloud8little mentioned this pull request Sep 10, 2024
13 tasks
@leonz789 leonz789 marked this pull request as draft September 15, 2024 07:30
@leonz789 leonz789 removed this from the Testnet V6 milestone Sep 15, 2024
@leonz789 leonz789 closed this Sep 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants