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

Add confidential transfer queries to seid #1931

Merged
merged 14 commits into from
Nov 15, 2024
3 changes: 2 additions & 1 deletion app/apptesting/test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package apptesting
import (
"context"
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/crypto"
"time"

"github.com/ethereum/go-ethereum/crypto"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
Expand Down
13 changes: 13 additions & 0 deletions proto/confidentialtransfers/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,17 @@ message GetAllCtAccountsResponse {

// pagination defines the pagination in the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

message DecryptedCtAccount {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

bytes public_key = 1; // serialized public key
uint32 pending_balance_lo = 2; // lo bits of the pending balance
uint64 pending_balance_hi = 3; // hi bits of the pending balance
uint64 combined_pending_balance = 4; // combined pending balance
uint32 pending_balance_credit_counter = 5;
string available_balance = 6; // decrypted available balance
uint64 decryptable_available_balance = 7; // decrypted aes encrypted available balance
}
180 changes: 180 additions & 0 deletions x/confidentialtransfers/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package cli

import (
"fmt"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/sei-protocol/sei-chain/x/confidentialtransfers/types"
"github.com/sei-protocol/sei-cryptography/pkg/encryption"
"github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal"
"github.com/spf13/cobra"
)

const decryptAvailableBalanceFlag = "decrypt-available-balance"

// GetQueryCmd returns the cli query commands for the minting module.
func GetQueryCmd() *cobra.Command {
confidentialTransfersQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Querying commands for the confidential transfer module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}

confidentialTransfersQueryCmd.AddCommand(
GetCmdQueryAccount(),
GetCmdQueryAllAccount(),
)

return confidentialTransfersQueryCmd
}

// GetCmdQueryAccount implements a command to return an account asssociated with the address and denom
func GetCmdQueryAccount() *cobra.Command {
cmd := &cobra.Command{
Use: "account [denom] [address] [flags]",
Short: "Query the account state",
Long: "Queries the account state associated with the address and denom." +
"Pass the --from flag to decrypt the account" +
"Pass the --decrypt-available-balance flag to attempt to decrypt the available balance.",
Args: cobra.ExactArgs(2),
RunE: queryAccount,
}

flags.AddQueryFlagsToCmd(cmd)
cmd.Flags().String(flags.FlagFrom, "", "Name or address of private key to decrypt the account")
cmd.Flags().Bool(decryptAvailableBalanceFlag, false, "Set this to attempt to decrypt the available balance")
return cmd
}

func queryAccount(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

denom := args[0]

// Validate denom
err = sdk.ValidateDenom(denom)
if err != nil {
return fmt.Errorf("invalid denom: %v", err)
}

address := args[1]
// Validate address
_, err = sdk.AccAddressFromBech32(address)
if err != nil {
return fmt.Errorf("invalid address: %v", err)
}

from, err := cmd.Flags().GetString(flags.FlagFrom)
if err != nil {
return err
}
fromAddr, _, _, err := client.GetFromFields(clientCtx, clientCtx.Keyring, from)
if err != nil {
return err
}

res, err := queryClient.GetCtAccount(cmd.Context(), &types.GetCtAccountRequest{
Address: address,
Denom: denom,
})
if err != nil {
return err
}

// If the --from flag passed matches the queried address, attempt to decrypt the contents
if fromAddr.String() == address {
account, err := res.Account.FromProto()
if err != nil {
return err
}
privateKey, err := getPrivateKey(cmd)
if err != nil {
return err
}

aesKey, err := encryption.GetAESKey(*privateKey, denom)
if err != nil {
return err
}

decryptor := elgamal.NewTwistedElgamal()
keyPair, err := decryptor.KeyGen(*privateKey, denom)
if err != nil {
return err
}

decryptAvailableBalance, err := cmd.Flags().GetBool(decryptAvailableBalanceFlag)
if err != nil {
return err
}

decryptedAccount, err := account.Decrypt(decryptor, keyPair, aesKey, decryptAvailableBalance)
if err != nil {
return err
}

return clientCtx.PrintProto(decryptedAccount)

} else {
return clientCtx.PrintProto(res.Account)
}
}

// GetCmdQueryAccount implements a command to return an account asssociated with the address and denom
func GetCmdQueryAllAccount() *cobra.Command {
cmd := &cobra.Command{
Use: "accounts [address]",
Short: "Query all the confidential token accounts associated with the address",
Args: cobra.ExactArgs(1),
RunE: queryAllAccounts,
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func queryAllAccounts(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

address := args[0]
// Validate address
_, err = sdk.AccAddressFromBech32(address)
if err != nil {
return fmt.Errorf("invalid address: %v", err)
}

res, err := queryClient.GetAllCtAccounts(cmd.Context(), &types.GetAllCtAccountsRequest{
Address: args[0],
})

if err != nil {
return err
}

for i := range res.Accounts {
err = clientCtx.PrintString("\n")
if err != nil {
return err
}

err = clientCtx.PrintProto(&res.Accounts[i])
if err != nil {
return err
}
}

return nil
}
12 changes: 3 additions & 9 deletions x/confidentialtransfers/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ type Keeper interface {
GetParams(ctx sdk.Context) types.Params
SetParams(ctx sdk.Context, params types.Params)

// TODO: See if there's a way to put this somewhere else
SendTokens(ctx sdk.Context, to sdk.AccAddress, amount sdk.Coins) error
ReceiveTokens(ctx sdk.Context, from sdk.AccAddress, amount sdk.Coins) error
BankKeeper() types.BankKeeper

CreateModuleAccount(ctx sdk.Context)

Expand Down Expand Up @@ -177,12 +175,8 @@ func (k BaseKeeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSpace.SetParamSet(ctx, &params)
}

func (k BaseKeeper) SendTokens(ctx sdk.Context, to sdk.AccAddress, amount sdk.Coins) error {
return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, to, amount)
}

func (k BaseKeeper) ReceiveTokens(ctx sdk.Context, from sdk.AccAddress, amount sdk.Coins) error {
return k.bankKeeper.SendCoinsFromAccountToModule(ctx, from, types.ModuleName, amount)
func (k BaseKeeper) BankKeeper() types.BankKeeper {
return k.bankKeeper
}

func (k BaseKeeper) getAccountStore(ctx sdk.Context) prefix.Store {
Expand Down
5 changes: 2 additions & 3 deletions x/confidentialtransfers/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (m msgServer) Deposit(goCtx context.Context, req *types.MsgDeposit) (*types
coins := sdk.NewCoins(sdk.NewCoin(req.Denom, sdk.NewIntFromUint64(req.Amount)))

// Transfer the amount from the sender's account to the module account
if err := m.Keeper.ReceiveTokens(ctx, address, coins); err != nil {
if err := m.Keeper.BankKeeper().SendCoinsFromAccountToModule(ctx, address, types.ModuleName, coins); err != nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient funds to deposit %d %s", req.Amount, req.Denom)
}

Expand Down Expand Up @@ -228,7 +228,7 @@ func (m msgServer) Withdraw(goCtx context.Context, req *types.MsgWithdraw) (*typ

// Return the tokens to the sender
coins := sdk.NewCoins(sdk.NewCoin(instruction.Denom, sdk.NewIntFromUint64(instruction.Amount)))
if err := m.Keeper.SendTokens(ctx, address, coins); err != nil {
if err := m.Keeper.BankKeeper().SendCoinsFromModuleToAccount(ctx, types.ModuleName, address, coins); err != nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient funds to withdraw %d %s", req.Amount, req.Denom)
}

Expand Down Expand Up @@ -408,7 +408,6 @@ func (m msgServer) Transfer(goCtx context.Context, req *types.MsgTransfer) (*typ
}

// Calculate and Update the account states.
// TODO: Is there a possibility for a race condition here if multiple Transfer transactions are made to the same account at the same time?
recipientPendingBalanceLo, err := elgamal.AddCiphertext(recipientAccount.PendingBalanceLo, instruction.RecipientTransferAmountLo)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "error adding recipient transfer amount lo")
Expand Down
Loading
Loading