Skip to content

Commit

Permalink
Add confidential transfer queries to seid (#1931)
Browse files Browse the repository at this point in the history
* update codec

* cleanup and queryies

* enable in module

* add other query

* fix bug

* fix lint

* add decryption query

* cleanup

* validate address

* comments

* rectify test failures

* speed up withdraw test

* make other tests slightly faster
  • Loading branch information
mj850 authored Nov 15, 2024
1 parent 3c23484 commit f8e5fac
Show file tree
Hide file tree
Showing 13 changed files with 687 additions and 104 deletions.
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

0 comments on commit f8e5fac

Please sign in to comment.