From 2184e6e757a49332e081b005f79b16dcc456e913 Mon Sep 17 00:00:00 2001 From: mj Date: Mon, 13 Jan 2025 00:14:39 +0800 Subject: [PATCH 1/6] apply precompile --- precompiles/common/expected_keepers.go | 1 + precompiles/confidentialtransfers/CT.sol | 8 + precompiles/confidentialtransfers/abi.json | 2 +- precompiles/confidentialtransfers/ct.go | 79 +++++++ precompiles/confidentialtransfers/ct_test.go | 229 ++++++++++++++++++- 5 files changed, 316 insertions(+), 3 deletions(-) diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index 80b8a8c49..2092b8aa0 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -132,6 +132,7 @@ type ChannelKeeper interface { } type ConfidentialTransfersKeeper interface { + ApplyPendingBalance(context.Context, *cttypes.MsgApplyPendingBalance) (*cttypes.MsgApplyPendingBalanceResponse, error) Deposit(context.Context, *cttypes.MsgDeposit) (*cttypes.MsgDepositResponse, error) InitializeAccount(context.Context, *cttypes.MsgInitializeAccount) (*cttypes.MsgInitializeAccountResponse, error) Transfer(goCtx context.Context, req *cttypes.MsgTransfer) (*cttypes.MsgTransferResponse, error) diff --git a/precompiles/confidentialtransfers/CT.sol b/precompiles/confidentialtransfers/CT.sol index c7d19585c..2352b69f3 100644 --- a/precompiles/confidentialtransfers/CT.sol +++ b/precompiles/confidentialtransfers/CT.sol @@ -59,4 +59,12 @@ interface ICT { string denom, uint64 amount ) external returns (bool success); + + function applyPendingBalance( + string fromAddress, + string denom, + string decryptableBalance, + uint32 pendingBalanceCreditCounter, + bytes availableBalance, + ) external returns (bool success); } diff --git a/precompiles/confidentialtransfers/abi.json b/precompiles/confidentialtransfers/abi.json index 1eb165c7a..3ea035e32 100644 --- a/precompiles/confidentialtransfers/abi.json +++ b/precompiles/confidentialtransfers/abi.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableAvailableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableAvailableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"internalType":"struct Auditor[]","name":"auditors","type":"tuple[]","components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}]}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] +[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableAvailableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableAvailableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}],"internalType":"struct Auditor[]","name":"auditors","type":"tuple[]"}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"}],"name":"applyPendingBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] diff --git a/precompiles/confidentialtransfers/ct.go b/precompiles/confidentialtransfers/ct.go index 9ffac1442..b62c5c779 100644 --- a/precompiles/confidentialtransfers/ct.go +++ b/precompiles/confidentialtransfers/ct.go @@ -17,6 +17,7 @@ import ( const ( InitializeAccountMethod = "initializeAccount" DepositMethod = "deposit" + ApplyPendingBalanceMethod = "applyPendingBalance" TransferMethod = "transfer" TransferWithAuditorsMethod = "transferWithAuditors" ) @@ -37,6 +38,7 @@ type PrecompileExecutor struct { InitializeAccountID []byte DepositID []byte + ApplyPendingBalanceID []byte TransferID []byte TransferWithAuditorsID []byte } @@ -56,6 +58,8 @@ func NewPrecompile(ctkeeper pcommon.ConfidentialTransfersKeeper, evmKeeper pcomm p.DepositID = m.ID case InitializeAccountMethod: p.InitializeAccountID = m.ID + case ApplyPendingBalanceMethod: + p.ApplyPendingBalanceID = m.ID case TransferMethod: p.TransferID = m.ID case TransferWithAuditorsMethod: @@ -74,6 +78,11 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller return nil, 0, err } switch method.Name { + case ApplyPendingBalanceMethod: + if readOnly { + return nil, 0, errors.New("cannot call ct precompile from staticcall") + } + return p.applyPendingBalance(ctx, method, caller, args) case DepositMethod: if readOnly { return nil, 0, errors.New("cannot call ct precompile from staticcall") @@ -543,3 +552,73 @@ func (p PrecompileExecutor) deposit(ctx sdk.Context, method *abi.Method, caller remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) return } + +func (p PrecompileExecutor) applyPendingBalance(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 5); err != nil { + rerr = err + return + } + + seiAddr, evmAddr, err := p.getValidAddressesFromString(ctx, args[0].(string)) + if err != nil { + rerr = err + return + } + + if evmAddr != caller { + rerr = errors.New("caller is not the same as the user address") + return + } + + denom := args[1].(string) + if denom == "" { + rerr = errors.New("invalid denom") + return + } + + decryptableBalance := args[2].(string) + if decryptableBalance == "" { + rerr = errors.New("invalid decryptable balance") + return + } + + pendingBalanceCreditCounter, ok := args[3].(uint32) + if !ok { + rerr = errors.New("invalid pendingBalanceCreditCounter") + return + } + + var availableBalance cttypes.Ciphertext + err = availableBalance.Unmarshal(args[4].([]byte)) + if err != nil { + rerr = err + return + } + + msg := &cttypes.MsgApplyPendingBalance{ + Address: seiAddr.String(), + Denom: denom, + NewDecryptableAvailableBalance: decryptableBalance, + CurrentPendingBalanceCounter: pendingBalanceCreditCounter, + CurrentAvailableBalance: &availableBalance, + } + + _, err = p.ctKeeper.ApplyPendingBalance(sdk.WrapSDKContext(ctx), msg) + if err != nil { + rerr = err + return + } + + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/confidentialtransfers/ct_test.go b/precompiles/confidentialtransfers/ct_test.go index 6fc45ca2b..afe5dc2ec 100644 --- a/precompiles/confidentialtransfers/ct_test.go +++ b/precompiles/confidentialtransfers/ct_test.go @@ -3,6 +3,9 @@ package confidentialtransfers_test import ( "encoding/hex" "fmt" + "math/big" + "testing" + "github.com/coinbase/kryptology/pkg/core/curves" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" @@ -20,8 +23,6 @@ import ( "github.com/sei-protocol/sei-cryptography/pkg/encryption/elgamal" "github.com/stretchr/testify/require" tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" - "math/big" - "testing" ) func TestPrecompileTransfer_Execute(t *testing.T) { @@ -1090,3 +1091,227 @@ func TestPrecompileDeposit_Execute(t *testing.T) { }) } } + +func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { + testDenom := "usei" + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + + p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + require.Nil(t, err) + ApplyPendingBalanceMethod, _ := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).ApplyPendingBalanceID) + expectedTrueResponse, _ := ApplyPendingBalanceMethod.Outputs.Pack(true) + var senderAddr, otherAddr sdk.AccAddress + var senderEVMAddr, otherEVMAddr common.Address + + type inputs struct { + senderAddr string + Denom string + pendingBalanceCounter uint32 + availableBalance []byte + DecryptableBalance string + } + + type args struct { + isReadOnly bool + isFromDelegateCall bool + value *big.Int + setUp func(in inputs) inputs + } + tests := []struct { + name string + args args + wantRet []byte + wantRemainingGas uint64 + wantErr bool + wantErrMsg string + }{ + { + name: "precompile should return true if input is valid", + wantRet: expectedTrueResponse, + wantRemainingGas: 0x1e43df, + wantErr: false, + }, + { + name: "precompile should return true if input is valid and sender is Sei address", + args: args{setUp: func(in inputs) inputs { + in.senderAddr = senderAddr.String() + return in + }}, + wantRet: expectedTrueResponse, + wantRemainingGas: 0x1e43df, + wantErr: false, + }, + { + name: "precompile should return error if address is empty", + args: args{ + setUp: func(in inputs) inputs { + in.senderAddr = "" + return in + }}, + wantErr: true, + wantErrMsg: "invalid address : empty address string is not allowed", + }, + { + name: "precompile should return error if caller is not the sender", + args: args{ + setUp: func(in inputs) inputs { + in.senderAddr = otherEVMAddr.String() + return in + }, + }, + wantErr: true, + wantErrMsg: "caller is not the same as the user address", + }, + { + name: "precompile should return error if denom is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.Denom = "" + return in + }, + }, + wantErr: true, + wantErrMsg: "invalid denom", + }, + { + name: "precompile should return error if availableBalance is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.availableBalance = []byte("invalid") + return in + }, + }, + wantErr: true, + wantErrMsg: "unexpected EOF", + }, + { + name: "precompile should return error if decryptable balance is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.DecryptableBalance = "" + return in + }, + }, + wantErr: true, + wantErrMsg: "invalid decryptable balance", + }, + { + name: "precompile should return error if called from static call", + args: args{isReadOnly: true}, + wantErr: true, + wantErrMsg: "cannot call ct precompile from staticcall", + }, + { + name: "precompile should return error if value is not nil", + args: args{value: big.NewInt(100)}, + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + } + for _, tt := range tests { + // Setup sender addresses and environment + senderPrivateKey := testkeeper.MockPrivateKey() + senderAddr, senderEVMAddr = testkeeper.PrivateKeyToAddresses(senderPrivateKey) + otherAddr, otherEVMAddr = testkeeper.PrivateKeyToAddresses(testkeeper.MockPrivateKey()) + k.SetAddressMapping(ctx, senderAddr, senderEVMAddr) + k.SetAddressMapping(ctx, otherAddr, otherEVMAddr) + + err := k.BankKeeper().MintCoins( + ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000)))) + require.Nil(t, err) + err = k.BankKeeper().SendCoinsFromModuleToAccount( + ctx, types.ModuleName, senderAddr, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000)))) + require.Nil(t, err) + + // setup sender and receiver ct accounts + ctKeeper := k.CtKeeper() + privHex := hex.EncodeToString(senderPrivateKey.Bytes()) + senderKey, _ := crypto.HexToECDSA(privHex) + initSenderAccount, err := cttypes.NewInitializeAccount(senderAddr.String(), testDenom, *senderKey) + require.NoError(t, err) + teg := elgamal.NewTwistedElgamal() + senderAvailableBalance, err := teg.AddScalar(initSenderAccount.AvailableBalance, big.NewInt(1000)) + senderPendingBalanceLo, err := teg.AddScalar(initSenderAccount.PendingBalanceLo, big.NewInt(2000)) + senderPendingBalanceHi, err := teg.AddScalar(initSenderAccount.PendingBalanceHi, big.NewInt(3000)) + senderAesKey, err := utils.GetAESKey(*senderKey, testDenom) + senderDecryptableBalance, err := encryption.EncryptAESGCM(big.NewInt(1000), senderAesKey) + require.NoError(t, err) + senderAccount := cttypes.Account{ + PublicKey: *initSenderAccount.Pubkey, + PendingBalanceLo: senderPendingBalanceLo, + PendingBalanceHi: senderPendingBalanceHi, + PendingBalanceCreditCounter: 3, + AvailableBalance: senderAvailableBalance, + DecryptableAvailableBalance: senderDecryptableBalance, + } + err = ctKeeper.SetAccount(ctx, senderAddr.String(), testDenom, senderAccount) + require.NoError(t, err) + + p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + require.Nil(t, err) + statedb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: statedb, + TxContext: vm.TxContext{Origin: senderEVMAddr}, + } + + applyPendingBalance, err := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).ApplyPendingBalanceID) + require.Nil(t, err) + + applyBalance, _ := cttypes.NewApplyPendingBalance( + *senderKey, + senderAddr.String(), + testDenom, + senderAccount.DecryptableAvailableBalance, + senderAccount.PendingBalanceCreditCounter, + senderAccount.AvailableBalance, + senderAccount.PendingBalanceLo, + senderAccount.PendingBalanceHi) + + trProto := cttypes.NewMsgApplyPendingBalanceProto(applyBalance) + availableBalance, _ := trProto.CurrentAvailableBalance.Marshal() + + t.Run(tt.name, func(t *testing.T) { + in := inputs{ + senderAddr: senderEVMAddr.String(), + Denom: testDenom, + pendingBalanceCounter: uint32(senderAccount.PendingBalanceCreditCounter), + availableBalance: availableBalance, + DecryptableBalance: senderAccount.DecryptableAvailableBalance, + } + if tt.args.setUp != nil { + in = tt.args.setUp(in) + } + inputArgs, err := applyPendingBalance.Inputs.Pack( + in.senderAddr, + in.Denom, + in.DecryptableBalance, + in.pendingBalanceCounter, + in.availableBalance) + require.Nil(t, err) + + resp, remainingGas, err := p.RunAndCalculateGas( + &evm, + senderEVMAddr, + senderEVMAddr, + append(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).ApplyPendingBalanceID, inputArgs...), + 2000000, + tt.args.value, + nil, + tt.args.isReadOnly, + tt.args.isFromDelegateCall) + if tt.wantErr { + require.NotNil(t, err) + require.Equal(t, tt.wantErrMsg, string(resp)) + return + } else { + require.NoError(t, err) + require.Equal(t, tt.wantRet, resp) + require.Equal(t, tt.wantRemainingGas, remainingGas) + } + + }) + } +} From 66c6f4d9b267ecef7010c5cd54626c7247dba289 Mon Sep 17 00:00:00 2001 From: mj Date: Mon, 13 Jan 2025 01:11:34 +0800 Subject: [PATCH 2/6] add withdraw precompile as well --- precompiles/common/expected_keepers.go | 1 + precompiles/confidentialtransfers/CT.sol | 9 + precompiles/confidentialtransfers/abi.json | 2 +- precompiles/confidentialtransfers/ct.go | 87 +++++++ precompiles/confidentialtransfers/ct_test.go | 254 ++++++++++++++++++- 5 files changed, 350 insertions(+), 3 deletions(-) diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index 2092b8aa0..a617fbc8c 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -136,4 +136,5 @@ type ConfidentialTransfersKeeper interface { Deposit(context.Context, *cttypes.MsgDeposit) (*cttypes.MsgDepositResponse, error) InitializeAccount(context.Context, *cttypes.MsgInitializeAccount) (*cttypes.MsgInitializeAccountResponse, error) Transfer(goCtx context.Context, req *cttypes.MsgTransfer) (*cttypes.MsgTransferResponse, error) + Withdraw(goCtx context.Context, req *cttypes.MsgWithdraw) (*cttypes.MsgWithdrawResponse, error) } diff --git a/precompiles/confidentialtransfers/CT.sol b/precompiles/confidentialtransfers/CT.sol index 2352b69f3..b7c3c139d 100644 --- a/precompiles/confidentialtransfers/CT.sol +++ b/precompiles/confidentialtransfers/CT.sol @@ -67,4 +67,13 @@ interface ICT { uint32 pendingBalanceCreditCounter, bytes availableBalance, ) external returns (bool success); + + function withdraw( + string fromAddress, + string denom, + uint256 amount, + string decryptableBalance, + bytes remainingBalanceCommitment, + bytes proofs + ) external returns (bool success); } diff --git a/precompiles/confidentialtransfers/abi.json b/precompiles/confidentialtransfers/abi.json index 3ea035e32..1bb0d9d0c 100644 --- a/precompiles/confidentialtransfers/abi.json +++ b/precompiles/confidentialtransfers/abi.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableAvailableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableAvailableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}],"internalType":"struct Auditor[]","name":"auditors","type":"tuple[]"}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"}],"name":"applyPendingBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] +[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}],"internalType":"struct ICT.Auditor[]","name":"auditors","type":"tuple[]"}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"}],"name":"applyPendingBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"remainingBalanceCommitment","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] diff --git a/precompiles/confidentialtransfers/ct.go b/precompiles/confidentialtransfers/ct.go index b62c5c779..f7de6f5e6 100644 --- a/precompiles/confidentialtransfers/ct.go +++ b/precompiles/confidentialtransfers/ct.go @@ -20,6 +20,7 @@ const ( ApplyPendingBalanceMethod = "applyPendingBalance" TransferMethod = "transfer" TransferWithAuditorsMethod = "transferWithAuditors" + WithdrawMethod = "withdraw" ) const ( @@ -41,6 +42,7 @@ type PrecompileExecutor struct { ApplyPendingBalanceID []byte TransferID []byte TransferWithAuditorsID []byte + WithdrawID []byte } func NewPrecompile(ctkeeper pcommon.ConfidentialTransfersKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { @@ -64,6 +66,8 @@ func NewPrecompile(ctkeeper pcommon.ConfidentialTransfersKeeper, evmKeeper pcomm p.TransferID = m.ID case TransferWithAuditorsMethod: p.TransferWithAuditorsID = m.ID + case WithdrawMethod: + p.WithdrawID = m.ID } } @@ -103,6 +107,11 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller return nil, 0, errors.New("cannot call ct precompile from staticcall") } return p.transferWithAuditors(ctx, method, caller, args) + case WithdrawMethod: + if readOnly { + return nil, 0, errors.New("cannot call ct precompile from staticcall") + } + return p.withdraw(ctx, method, caller, args) } return } @@ -622,3 +631,81 @@ func (p PrecompileExecutor) applyPendingBalance(ctx sdk.Context, method *abi.Met remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) return } + +func (p PrecompileExecutor) withdraw(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 6); err != nil { + rerr = err + return + } + + seiAddr, evmAddr, err := p.getValidAddressesFromString(ctx, args[0].(string)) + if err != nil { + rerr = err + return + } + + if evmAddr != caller { + rerr = errors.New("caller is not the same as the user address") + return + } + + denom := args[1].(string) + if denom == "" { + rerr = errors.New("invalid denom") + return + } + + amount, ok := args[2].(*big.Int) + if !ok { + rerr = errors.New("invalid amount") + return + } + + decryptableBalance := args[3].(string) + if decryptableBalance == "" { + rerr = errors.New("invalid decryptable balance") + return + } + + var remainingBalanceCommitment cttypes.Ciphertext + err = remainingBalanceCommitment.Unmarshal(args[4].([]byte)) + if err != nil { + rerr = errors.New("invalid remainingBalanceCommitment") + return + } + + var withdrawProofs cttypes.WithdrawMsgProofs + err = withdrawProofs.Unmarshal(args[5].([]byte)) + if err != nil { + rerr = err + return + } + + msg := &cttypes.MsgWithdraw{ + FromAddress: seiAddr.String(), + Denom: denom, + Amount: amount.String(), + DecryptableBalance: decryptableBalance, + RemainingBalanceCommitment: &remainingBalanceCommitment, + Proofs: &withdrawProofs, + } + + _, err = p.ctKeeper.Withdraw(sdk.WrapSDKContext(ctx), msg) + if err != nil { + rerr = err + return + } + + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/confidentialtransfers/ct_test.go b/precompiles/confidentialtransfers/ct_test.go index afe5dc2ec..cbdf5bf33 100644 --- a/precompiles/confidentialtransfers/ct_test.go +++ b/precompiles/confidentialtransfers/ct_test.go @@ -1270,8 +1270,8 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { senderAccount.PendingBalanceLo, senderAccount.PendingBalanceHi) - trProto := cttypes.NewMsgApplyPendingBalanceProto(applyBalance) - availableBalance, _ := trProto.CurrentAvailableBalance.Marshal() + apbProto := cttypes.NewMsgApplyPendingBalanceProto(applyBalance) + availableBalance, _ := apbProto.CurrentAvailableBalance.Marshal() t.Run(tt.name, func(t *testing.T) { in := inputs{ @@ -1315,3 +1315,253 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { }) } } + +func TestPrecompileWithdraw_Execute(t *testing.T) { + testDenom := "usei" + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + + p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + require.Nil(t, err) + ApplyPendingBalanceMethod, _ := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).WithdrawID) + expectedTrueResponse, _ := ApplyPendingBalanceMethod.Outputs.Pack(true) + var senderAddr, otherAddr sdk.AccAddress + var senderEVMAddr, otherEVMAddr common.Address + + type inputs struct { + senderAddr string + denom string + amount *big.Int + decryptableBalance string + remainingBalanceCommitment []byte + proofs []byte + } + + type args struct { + isReadOnly bool + isFromDelegateCall bool + value *big.Int + setUp func(in inputs) inputs + } + tests := []struct { + name string + args args + wantRet []byte + wantRemainingGas uint64 + wantErr bool + wantErrMsg string + }{ + { + name: "precompile should return true if input is valid", + wantRet: expectedTrueResponse, + wantRemainingGas: 0xecd4e, + wantErr: false, + }, + { + name: "precompile should return true if input is valid and sender is Sei address", + args: args{setUp: func(in inputs) inputs { + in.senderAddr = senderAddr.String() + return in + }}, + wantRet: expectedTrueResponse, + wantRemainingGas: 0xecd30, + wantErr: false, + }, + { + name: "precompile should return error if address is empty", + args: args{ + setUp: func(in inputs) inputs { + in.senderAddr = "" + return in + }}, + wantErr: true, + wantErrMsg: "invalid address : empty address string is not allowed", + }, + { + name: "precompile should return error if caller is not the sender", + args: args{ + setUp: func(in inputs) inputs { + in.senderAddr = otherEVMAddr.String() + return in + }, + }, + wantErr: true, + wantErrMsg: "caller is not the same as the user address", + }, + { + name: "precompile should return error if denom is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.denom = "" + return in + }, + }, + wantErr: true, + wantErrMsg: "invalid denom", + }, + { + name: "precompile should return error if amount is zero", + args: args{ + setUp: func(in inputs) inputs { + in.amount = big.NewInt(0) + return in + }, + }, + wantErr: true, + wantErrMsg: "invalid msg: invalid request", + }, + { + name: "precompile should return error if decryptable balance is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.decryptableBalance = "" + return in + }, + }, + wantErr: true, + wantErrMsg: "invalid decryptable balance", + }, + { + name: "precompile should return error if remainingBalanceCommitment is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.remainingBalanceCommitment = []byte("invalid") + return in + }, + }, + wantErr: true, + wantErrMsg: "invalid remainingBalanceCommitment", + }, + { + name: "precompile should return error if proofs is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.proofs = []byte("invalid") + return in + }, + }, + wantErr: true, + wantErrMsg: "unexpected EOF", + }, + { + name: "precompile should return error if called from static call", + args: args{isReadOnly: true}, + wantErr: true, + wantErrMsg: "cannot call ct precompile from staticcall", + }, + { + name: "precompile should return error if value is not nil", + args: args{value: big.NewInt(100)}, + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + } + for _, tt := range tests { + // Setup sender addresses and environment + senderPrivateKey := testkeeper.MockPrivateKey() + senderAddr, senderEVMAddr = testkeeper.PrivateKeyToAddresses(senderPrivateKey) + otherAddr, otherEVMAddr = testkeeper.PrivateKeyToAddresses(testkeeper.MockPrivateKey()) + k.SetAddressMapping(ctx, senderAddr, senderEVMAddr) + k.SetAddressMapping(ctx, otherAddr, otherEVMAddr) + + err := k.BankKeeper().MintCoins( + ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(20000000)))) + require.Nil(t, err) + err = k.BankKeeper().SendCoinsFromModuleToModule(ctx, types.ModuleName, cttypes.ModuleName, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000)))) + err = k.BankKeeper().SendCoinsFromModuleToAccount( + ctx, types.ModuleName, senderAddr, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000)))) + require.Nil(t, err) + + // setup sender and receiver ct accounts + ctKeeper := k.CtKeeper() + privHex := hex.EncodeToString(senderPrivateKey.Bytes()) + senderKey, _ := crypto.HexToECDSA(privHex) + initSenderAccount, err := cttypes.NewInitializeAccount(senderAddr.String(), testDenom, *senderKey) + require.NoError(t, err) + teg := elgamal.NewTwistedElgamal() + senderAvailableBalance, err := teg.AddScalar(initSenderAccount.AvailableBalance, big.NewInt(1000)) + senderPendingBalanceLo, err := teg.AddScalar(initSenderAccount.PendingBalanceLo, big.NewInt(2000)) + senderPendingBalanceHi, err := teg.AddScalar(initSenderAccount.PendingBalanceHi, big.NewInt(3000)) + senderAesKey, err := utils.GetAESKey(*senderKey, testDenom) + senderDecryptableBalance, err := encryption.EncryptAESGCM(big.NewInt(1000), senderAesKey) + require.NoError(t, err) + senderAccount := cttypes.Account{ + PublicKey: *initSenderAccount.Pubkey, + PendingBalanceLo: senderPendingBalanceLo, + PendingBalanceHi: senderPendingBalanceHi, + PendingBalanceCreditCounter: 3, + AvailableBalance: senderAvailableBalance, + DecryptableAvailableBalance: senderDecryptableBalance, + } + err = ctKeeper.SetAccount(ctx, senderAddr.String(), testDenom, senderAccount) + require.NoError(t, err) + + p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + require.Nil(t, err) + statedb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: statedb, + TxContext: vm.TxContext{Origin: senderEVMAddr}, + } + + withdrawMethod, err := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).WithdrawID) + require.Nil(t, err) + + withdrawAmount := big.NewInt(500) + withdraw, _ := cttypes.NewWithdraw( + *senderKey, + senderAccount.AvailableBalance, + testDenom, + senderAddr.String(), + senderAccount.DecryptableAvailableBalance, + withdrawAmount) + + wdProto := cttypes.NewMsgWithdrawProto(withdraw) + remainingBalanceCommitment, _ := wdProto.RemainingBalanceCommitment.Marshal() + proofs, _ := wdProto.Proofs.Marshal() + + t.Run(tt.name, func(t *testing.T) { + in := inputs{ + senderAddr: senderEVMAddr.String(), + denom: testDenom, + amount: withdrawAmount, + decryptableBalance: senderAccount.DecryptableAvailableBalance, + remainingBalanceCommitment: remainingBalanceCommitment, + proofs: proofs, + } + if tt.args.setUp != nil { + in = tt.args.setUp(in) + } + inputArgs, err := withdrawMethod.Inputs.Pack( + in.senderAddr, + in.denom, + in.amount, + in.decryptableBalance, + in.remainingBalanceCommitment, + in.proofs) + require.Nil(t, err) + + resp, remainingGas, err := p.RunAndCalculateGas( + &evm, + senderEVMAddr, + senderEVMAddr, + append(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).WithdrawID, inputArgs...), + 2000000, + tt.args.value, + nil, + tt.args.isReadOnly, + tt.args.isFromDelegateCall) + if tt.wantErr { + require.NotNil(t, err) + require.Equal(t, tt.wantErrMsg, string(resp)) + return + } else { + require.NoError(t, err) + require.Equal(t, tt.wantRet, resp) + require.Equal(t, tt.wantRemainingGas, remainingGas) + } + + }) + } +} From f153b836db609e5697eea6ec04aa707412d99cae Mon Sep 17 00:00:00 2001 From: mj Date: Mon, 13 Jan 2025 19:13:08 +0800 Subject: [PATCH 3/6] update with queries --- precompiles/confidentialtransfers/CT.sol | 2 + precompiles/confidentialtransfers/abi.json | 2 +- x/evm/client/cli/query.go | 205 ++++++++++++++++++++- 3 files changed, 206 insertions(+), 3 deletions(-) diff --git a/precompiles/confidentialtransfers/CT.sol b/precompiles/confidentialtransfers/CT.sol index b7c3c139d..c20265ba9 100644 --- a/precompiles/confidentialtransfers/CT.sol +++ b/precompiles/confidentialtransfers/CT.sol @@ -28,6 +28,7 @@ interface ICT { bytes toAmountLo, bytes toAmountHi, bytes remainingBalance, + string decryptableBalance, bytes proofs ) external returns (bool success); @@ -40,6 +41,7 @@ interface ICT { bytes toAmountLo, bytes toAmountHi, bytes remainingBalance, + string decryptableBalance, bytes proofs, Auditor[] auditors ) external returns (bool success); diff --git a/precompiles/confidentialtransfers/abi.json b/precompiles/confidentialtransfers/abi.json index 1bb0d9d0c..162b6c5cc 100644 --- a/precompiles/confidentialtransfers/abi.json +++ b/precompiles/confidentialtransfers/abi.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}],"internalType":"struct ICT.Auditor[]","name":"auditors","type":"tuple[]"}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"}],"name":"applyPendingBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"remainingBalanceCommitment","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] +[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}],"internalType":"struct ICT.Auditor[]","name":"auditors","type":"tuple[]"}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"}],"name":"applyPendingBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"remainingBalanceCommitment","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] diff --git a/x/evm/client/cli/query.go b/x/evm/client/cli/query.go index 4c537834b..50d805f16 100644 --- a/x/evm/client/cli/query.go +++ b/x/evm/client/cli/query.go @@ -57,6 +57,8 @@ func GetQueryCmd(_ string) *cobra.Command { cmd.AddCommand(CmdQueryPointee()) cmd.AddCommand(GetCmdQueryCtTransferPayload()) cmd.AddCommand(GetCmdQueryCtInitAccountPayload()) + cmd.AddCommand(GetCmdQueryCtApplyPendingBalancePayload()) + cmd.AddCommand(GetCmdQueryCtWithdrawPayload()) return cmd } @@ -734,8 +736,8 @@ func queryCtInitAccountPayload(cmd *cobra.Command, args []string) error { bz, err := newAbi.Pack( confidentialtransfers.InitializeAccountMethod, - seiAddress, - denom, + initAccountProto.FromAddress, + initAccountProto.Denom, initAccountProto.PublicKey, initAccountProto.DecryptableBalance, pendingBalanceLo, @@ -749,6 +751,205 @@ func queryCtInitAccountPayload(cmd *cobra.Command, args []string) error { return queryClientCtx.PrintString(hex.EncodeToString(bz)) } +func GetCmdQueryCtApplyPendingBalancePayload() *cobra.Command { + cmd := &cobra.Command{ + Use: "ct-apply-pending-balance-payload [abi-filepath] [from_address] [denom]", + Short: "get hex payload for the confidential transfers apply pending balance method", + Args: cobra.ExactArgs(3), + RunE: queryCtApplyPendingBalancePayload, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func queryCtApplyPendingBalancePayload(cmd *cobra.Command, args []string) error { + queryClientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(queryClientCtx) + + dat, err := os.ReadFile(args[0]) + if err != nil { + return err + } + + newAbi, err := abi.JSON(bytes.NewReader(dat)) + if err != nil { + return err + } + + fromAddress := args[1] + if fromAddress == "" { + return errors.New("from address cannot be empty") + } + + seiAddress, err := getSeiAddress(queryClient, fromAddress) + if err != nil { + return err + } + + denom := args[2] + if denom == "" { + return errors.New("denom cannot be empty") + } + + _, name, _, err := client.GetFromFields(queryClientCtx, queryClientCtx.Keyring, seiAddress) + if err != nil { + return err + } + + privKey, err := getPrivateKeyForName(cmd, name) + if err != nil { + return err + } + + ctQueryClient := cttypes.NewQueryClient(queryClientCtx) + fromAccount, err := ctcliutils.GetAccount(ctQueryClient, seiAddress, denom) + if err != nil { + return err + } + + applyPendingBalance, err := cttypes.NewApplyPendingBalance( + *privKey, + seiAddress, + denom, + fromAccount.DecryptableAvailableBalance, + fromAccount.PendingBalanceCreditCounter, + fromAccount.AvailableBalance, + fromAccount.PendingBalanceLo, + fromAccount.PendingBalanceHi) + if err != nil { + return err + } + + applyPendingBalanceProto := cttypes.NewMsgApplyPendingBalanceProto(applyPendingBalance) + + if err = applyPendingBalanceProto.ValidateBasic(); err != nil { + return err + } + + availableBalance, err := applyPendingBalanceProto.CurrentAvailableBalance.Marshal() + if err != nil { + return err + } + + bz, err := newAbi.Pack( + confidentialtransfers.ApplyPendingBalanceMethod, + applyPendingBalanceProto.Address, + applyPendingBalanceProto.Denom, + applyPendingBalanceProto.NewDecryptableAvailableBalance, + applyPendingBalanceProto.CurrentPendingBalanceCounter, + availableBalance) + + if err != nil { + return err + } + return queryClientCtx.PrintString(hex.EncodeToString(bz)) +} + +func GetCmdQueryCtWithdrawPayload() *cobra.Command { + cmd := &cobra.Command{ + Use: "ct-withdraw-payload [abi-filepath] [from_address] [amount]", + Short: "get hex payload for the confidential transfers withdraw method", + Args: cobra.ExactArgs(3), + RunE: queryCtWithdrawPayload, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func queryCtWithdrawPayload(cmd *cobra.Command, args []string) error { + queryClientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(queryClientCtx) + + dat, err := os.ReadFile(args[0]) + if err != nil { + return err + } + + newAbi, err := abi.JSON(bytes.NewReader(dat)) + if err != nil { + return err + } + + fromAddress := args[1] + if fromAddress == "" { + return errors.New("from address cannot be empty") + } + + seiAddress, err := getSeiAddress(queryClient, fromAddress) + if err != nil { + return err + } + + coin, err := sdk.ParseCoinNormalized(args[2]) + if err != nil { + return err + } + + _, name, _, err := client.GetFromFields(queryClientCtx, queryClientCtx.Keyring, seiAddress) + if err != nil { + return err + } + + privKey, err := getPrivateKeyForName(cmd, name) + if err != nil { + return err + } + + ctQueryClient := cttypes.NewQueryClient(queryClientCtx) + fromAccount, err := ctcliutils.GetAccount(ctQueryClient, seiAddress, coin.Denom) + if err != nil { + return err + } + + withdraw, err := cttypes.NewWithdraw( + *privKey, + fromAccount.AvailableBalance, + coin.Denom, + seiAddress, + fromAccount.DecryptableAvailableBalance, + coin.Amount.BigInt()) + + if err != nil { + return err + } + + withdrawProto := cttypes.NewMsgWithdrawProto(withdraw) + + if err = withdrawProto.ValidateBasic(); err != nil { + return err + } + + remainingBalanceCommitment, err := withdrawProto.RemainingBalanceCommitment.Marshal() + proofs, err := withdrawProto.Proofs.Marshal() + if err != nil { + return err + } + + bz, err := newAbi.Pack( + confidentialtransfers.WithdrawMethod, + withdrawProto.FromAddress, + withdrawProto.Denom, + withdrawProto.Amount, + withdrawProto.DecryptableBalance, + remainingBalanceCommitment, + proofs) + + if err != nil { + return err + } + return queryClientCtx.PrintString(hex.EncodeToString(bz)) +} + func getSeiAddress(queryClient types.QueryClient, address string) (string, error) { if common.IsHexAddress(address) { evmAddr := common.HexToAddress(address) From 89bc27a64047cb0ee0ecd3a46a93b0ae7690b5fd Mon Sep 17 00:00:00 2001 From: mj Date: Tue, 14 Jan 2025 16:20:01 +0800 Subject: [PATCH 4/6] update precompiles for sender --- precompiles/confidentialtransfers/CT.sol | 2 - precompiles/confidentialtransfers/abi.json | 2 +- precompiles/confidentialtransfers/ct.go | 40 ++---- precompiles/confidentialtransfers/ct_test.go | 136 +++++++++---------- 4 files changed, 83 insertions(+), 97 deletions(-) diff --git a/precompiles/confidentialtransfers/CT.sol b/precompiles/confidentialtransfers/CT.sol index c20265ba9..707f7e68f 100644 --- a/precompiles/confidentialtransfers/CT.sol +++ b/precompiles/confidentialtransfers/CT.sol @@ -63,7 +63,6 @@ interface ICT { ) external returns (bool success); function applyPendingBalance( - string fromAddress, string denom, string decryptableBalance, uint32 pendingBalanceCreditCounter, @@ -71,7 +70,6 @@ interface ICT { ) external returns (bool success); function withdraw( - string fromAddress, string denom, uint256 amount, string decryptableBalance, diff --git a/precompiles/confidentialtransfers/abi.json b/precompiles/confidentialtransfers/abi.json index 162b6c5cc..c76f77bae 100644 --- a/precompiles/confidentialtransfers/abi.json +++ b/precompiles/confidentialtransfers/abi.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}],"internalType":"struct ICT.Auditor[]","name":"auditors","type":"tuple[]"}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"}],"name":"applyPendingBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"remainingBalanceCommitment","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] +[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}],"internalType":"struct ICT.Auditor[]","name":"auditors","type":"tuple[]"}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"}],"name":"applyPendingBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"remainingBalanceCommitment","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/confidentialtransfers/ct.go b/precompiles/confidentialtransfers/ct.go index f7de6f5e6..b9242014d 100644 --- a/precompiles/confidentialtransfers/ct.go +++ b/precompiles/confidentialtransfers/ct.go @@ -572,49 +572,44 @@ func (p PrecompileExecutor) applyPendingBalance(ctx sdk.Context, method *abi.Met } }() - if err := pcommon.ValidateArgsLength(args, 5); err != nil { + if err := pcommon.ValidateArgsLength(args, 4); err != nil { rerr = err return } - seiAddr, evmAddr, err := p.getValidAddressesFromString(ctx, args[0].(string)) + fromAddr, _, err := p.getAssociatedAddressesByEVMAddress(ctx, caller) if err != nil { rerr = err return } - if evmAddr != caller { - rerr = errors.New("caller is not the same as the user address") - return - } - - denom := args[1].(string) + denom := args[0].(string) if denom == "" { rerr = errors.New("invalid denom") return } - decryptableBalance := args[2].(string) + decryptableBalance := args[1].(string) if decryptableBalance == "" { rerr = errors.New("invalid decryptable balance") return } - pendingBalanceCreditCounter, ok := args[3].(uint32) + pendingBalanceCreditCounter, ok := args[2].(uint32) if !ok { rerr = errors.New("invalid pendingBalanceCreditCounter") return } var availableBalance cttypes.Ciphertext - err = availableBalance.Unmarshal(args[4].([]byte)) + err = availableBalance.Unmarshal(args[3].([]byte)) if err != nil { rerr = err return } msg := &cttypes.MsgApplyPendingBalance{ - Address: seiAddr.String(), + Address: fromAddr.String(), Denom: denom, NewDecryptableAvailableBalance: decryptableBalance, CurrentPendingBalanceCounter: pendingBalanceCreditCounter, @@ -642,56 +637,51 @@ func (p PrecompileExecutor) withdraw(ctx sdk.Context, method *abi.Method, caller } }() - if err := pcommon.ValidateArgsLength(args, 6); err != nil { + if err := pcommon.ValidateArgsLength(args, 5); err != nil { rerr = err return } - seiAddr, evmAddr, err := p.getValidAddressesFromString(ctx, args[0].(string)) + fromAddr, _, err := p.getAssociatedAddressesByEVMAddress(ctx, caller) if err != nil { rerr = err return } - if evmAddr != caller { - rerr = errors.New("caller is not the same as the user address") - return - } - - denom := args[1].(string) + denom := args[0].(string) if denom == "" { rerr = errors.New("invalid denom") return } - amount, ok := args[2].(*big.Int) + amount, ok := args[1].(*big.Int) if !ok { rerr = errors.New("invalid amount") return } - decryptableBalance := args[3].(string) + decryptableBalance := args[2].(string) if decryptableBalance == "" { rerr = errors.New("invalid decryptable balance") return } var remainingBalanceCommitment cttypes.Ciphertext - err = remainingBalanceCommitment.Unmarshal(args[4].([]byte)) + err = remainingBalanceCommitment.Unmarshal(args[3].([]byte)) if err != nil { rerr = errors.New("invalid remainingBalanceCommitment") return } var withdrawProofs cttypes.WithdrawMsgProofs - err = withdrawProofs.Unmarshal(args[5].([]byte)) + err = withdrawProofs.Unmarshal(args[4].([]byte)) if err != nil { rerr = err return } msg := &cttypes.MsgWithdraw{ - FromAddress: seiAddr.String(), + FromAddress: fromAddr.String(), Denom: denom, Amount: amount.String(), DecryptableBalance: decryptableBalance, diff --git a/precompiles/confidentialtransfers/ct_test.go b/precompiles/confidentialtransfers/ct_test.go index cbdf5bf33..257f94654 100644 --- a/precompiles/confidentialtransfers/ct_test.go +++ b/precompiles/confidentialtransfers/ct_test.go @@ -1104,9 +1104,10 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { expectedTrueResponse, _ := ApplyPendingBalanceMethod.Outputs.Pack(true) var senderAddr, otherAddr sdk.AccAddress var senderEVMAddr, otherEVMAddr common.Address + notAssociatedUserPrivateKey := testkeeper.MockPrivateKey() + _, notAssociatedEVMAddr := testkeeper.PrivateKeyToAddresses(notAssociatedUserPrivateKey) type inputs struct { - senderAddr string Denom string pendingBalanceCounter uint32 availableBalance []byte @@ -1118,6 +1119,7 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { isFromDelegateCall bool value *big.Int setUp func(in inputs) inputs + caller *common.Address } tests := []struct { name string @@ -1128,41 +1130,28 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { wantErrMsg string }{ { - name: "precompile should return true if input is valid", - wantRet: expectedTrueResponse, - wantRemainingGas: 0x1e43df, - wantErr: false, - }, - { - name: "precompile should return true if input is valid and sender is Sei address", - args: args{setUp: func(in inputs) inputs { - in.senderAddr = senderAddr.String() - return in - }}, + name: "precompile should return true if input is valid", + args: args{ + caller: &senderEVMAddr, + }, wantRet: expectedTrueResponse, wantRemainingGas: 0x1e43df, wantErr: false, }, + // Technically this is possible, although both accounts would have to have the same pendingBalanceCreditCounter and AvailableBalance, which is highly improbable. { - name: "precompile should return error if address is empty", + name: "precompile should return error if calldata was not created by the sender", args: args{ - setUp: func(in inputs) inputs { - in.senderAddr = "" - return in - }}, + caller: &otherEVMAddr, + }, wantErr: true, - wantErrMsg: "invalid address : empty address string is not allowed", + wantErrMsg: "available balance mismatch: invalid request", }, { - name: "precompile should return error if caller is not the sender", - args: args{ - setUp: func(in inputs) inputs { - in.senderAddr = otherEVMAddr.String() - return in - }, - }, + name: "precompile should return error if Sei address is not associated with an EVM address", + args: args{caller: ¬AssociatedEVMAddr}, wantErr: true, - wantErrMsg: "caller is not the same as the user address", + wantErrMsg: fmt.Sprintf("address %s is not associated", notAssociatedEVMAddr), }, { name: "precompile should return error if denom is invalid", @@ -1171,6 +1160,7 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { in.Denom = "" return in }, + caller: &senderEVMAddr, }, wantErr: true, wantErrMsg: "invalid denom", @@ -1182,6 +1172,7 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { in.availableBalance = []byte("invalid") return in }, + caller: &senderEVMAddr, }, wantErr: true, wantErrMsg: "unexpected EOF", @@ -1193,19 +1184,26 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { in.DecryptableBalance = "" return in }, + caller: &senderEVMAddr, }, wantErr: true, wantErrMsg: "invalid decryptable balance", }, { - name: "precompile should return error if called from static call", - args: args{isReadOnly: true}, + name: "precompile should return error if called from static call", + args: args{ + isReadOnly: true, + caller: &senderEVMAddr, + }, wantErr: true, wantErrMsg: "cannot call ct precompile from staticcall", }, { - name: "precompile should return error if value is not nil", - args: args{value: big.NewInt(100)}, + name: "precompile should return error if value is not nil", + args: args{ + value: big.NewInt(100), + caller: &senderEVMAddr, + }, wantErr: true, wantErrMsg: "sending funds to a non-payable function", }, @@ -1214,7 +1212,7 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { // Setup sender addresses and environment senderPrivateKey := testkeeper.MockPrivateKey() senderAddr, senderEVMAddr = testkeeper.PrivateKeyToAddresses(senderPrivateKey) - otherAddr, otherEVMAddr = testkeeper.PrivateKeyToAddresses(testkeeper.MockPrivateKey()) + otherAddr, otherEVMAddr, _, _ = setUpCtAccount(k, ctx, testDenom) k.SetAddressMapping(ctx, senderAddr, senderEVMAddr) k.SetAddressMapping(ctx, otherAddr, otherEVMAddr) @@ -1225,7 +1223,7 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { ctx, types.ModuleName, senderAddr, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000)))) require.Nil(t, err) - // setup sender and receiver ct accounts + // setup sender ct account ctKeeper := k.CtKeeper() privHex := hex.EncodeToString(senderPrivateKey.Bytes()) senderKey, _ := crypto.HexToECDSA(privHex) @@ -1249,6 +1247,11 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { err = ctKeeper.SetAccount(ctx, senderAddr.String(), testDenom, senderAccount) require.NoError(t, err) + otherAccount, _ := k.CtKeeper().GetAccount(ctx, otherAddr.String(), testDenom) + otherAccount.PendingBalanceLo, _ = teg.AddScalar(otherAccount.PendingBalanceLo, big.NewInt(1000)) + otherAccount.PendingBalanceCreditCounter = 3 + err = ctKeeper.SetAccount(ctx, otherAddr.String(), testDenom, otherAccount) + p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) require.Nil(t, err) statedb := state.NewDBImpl(ctx, k, true) @@ -1275,7 +1278,6 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { t.Run(tt.name, func(t *testing.T) { in := inputs{ - senderAddr: senderEVMAddr.String(), Denom: testDenom, pendingBalanceCounter: uint32(senderAccount.PendingBalanceCreditCounter), availableBalance: availableBalance, @@ -1285,7 +1287,6 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { in = tt.args.setUp(in) } inputArgs, err := applyPendingBalance.Inputs.Pack( - in.senderAddr, in.Denom, in.DecryptableBalance, in.pendingBalanceCounter, @@ -1294,7 +1295,7 @@ func TestPrecompileApplyPendingBalance_Execute(t *testing.T) { resp, remainingGas, err := p.RunAndCalculateGas( &evm, - senderEVMAddr, + *tt.args.caller, senderEVMAddr, append(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).ApplyPendingBalanceID, inputArgs...), 2000000, @@ -1328,9 +1329,10 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { expectedTrueResponse, _ := ApplyPendingBalanceMethod.Outputs.Pack(true) var senderAddr, otherAddr sdk.AccAddress var senderEVMAddr, otherEVMAddr common.Address + notAssociatedUserPrivateKey := testkeeper.MockPrivateKey() + _, notAssociatedEVMAddr := testkeeper.PrivateKeyToAddresses(notAssociatedUserPrivateKey) type inputs struct { - senderAddr string denom string amount *big.Int decryptableBalance string @@ -1343,6 +1345,7 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { isFromDelegateCall bool value *big.Int setUp func(in inputs) inputs + caller *common.Address } tests := []struct { name string @@ -1353,41 +1356,27 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { wantErrMsg string }{ { - name: "precompile should return true if input is valid", + name: "precompile should return true if input is valid", + args: args{ + caller: &senderEVMAddr, + }, wantRet: expectedTrueResponse, wantRemainingGas: 0xecd4e, wantErr: false, }, { - name: "precompile should return true if input is valid and sender is Sei address", - args: args{setUp: func(in inputs) inputs { - in.senderAddr = senderAddr.String() - return in - }}, - wantRet: expectedTrueResponse, - wantRemainingGas: 0xecd30, - wantErr: false, - }, - { - name: "precompile should return error if address is empty", + name: "precompile should return error if caller did not create calldata", args: args{ - setUp: func(in inputs) inputs { - in.senderAddr = "" - return in - }}, + caller: &otherEVMAddr, + }, wantErr: true, - wantErrMsg: "invalid address : empty address string is not allowed", + wantErrMsg: "ciphertext commitment equality verification failed: invalid request", }, { - name: "precompile should return error if caller is not the sender", - args: args{ - setUp: func(in inputs) inputs { - in.senderAddr = otherEVMAddr.String() - return in - }, - }, + name: "precompile should return error if Sei address is not associated with an EVM address", + args: args{caller: ¬AssociatedEVMAddr}, wantErr: true, - wantErrMsg: "caller is not the same as the user address", + wantErrMsg: fmt.Sprintf("address %s is not associated", notAssociatedEVMAddr), }, { name: "precompile should return error if denom is invalid", @@ -1396,6 +1385,7 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { in.denom = "" return in }, + caller: &senderEVMAddr, }, wantErr: true, wantErrMsg: "invalid denom", @@ -1407,6 +1397,7 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { in.amount = big.NewInt(0) return in }, + caller: &senderEVMAddr, }, wantErr: true, wantErrMsg: "invalid msg: invalid request", @@ -1418,6 +1409,7 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { in.decryptableBalance = "" return in }, + caller: &senderEVMAddr, }, wantErr: true, wantErrMsg: "invalid decryptable balance", @@ -1429,6 +1421,7 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { in.remainingBalanceCommitment = []byte("invalid") return in }, + caller: &senderEVMAddr, }, wantErr: true, wantErrMsg: "invalid remainingBalanceCommitment", @@ -1440,19 +1433,26 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { in.proofs = []byte("invalid") return in }, + caller: &senderEVMAddr, }, wantErr: true, wantErrMsg: "unexpected EOF", }, { - name: "precompile should return error if called from static call", - args: args{isReadOnly: true}, + name: "precompile should return error if called from static call", + args: args{ + isReadOnly: true, + caller: &senderEVMAddr, + }, wantErr: true, wantErrMsg: "cannot call ct precompile from staticcall", }, { - name: "precompile should return error if value is not nil", - args: args{value: big.NewInt(100)}, + name: "precompile should return error if value is not nil", + args: args{ + value: big.NewInt(100), + caller: &senderEVMAddr, + }, wantErr: true, wantErrMsg: "sending funds to a non-payable function", }, @@ -1461,7 +1461,7 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { // Setup sender addresses and environment senderPrivateKey := testkeeper.MockPrivateKey() senderAddr, senderEVMAddr = testkeeper.PrivateKeyToAddresses(senderPrivateKey) - otherAddr, otherEVMAddr = testkeeper.PrivateKeyToAddresses(testkeeper.MockPrivateKey()) + otherAddr, otherEVMAddr, _, _ = setUpCtAccount(k, ctx, testDenom) k.SetAddressMapping(ctx, senderAddr, senderEVMAddr) k.SetAddressMapping(ctx, otherAddr, otherEVMAddr) @@ -1523,7 +1523,6 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { t.Run(tt.name, func(t *testing.T) { in := inputs{ - senderAddr: senderEVMAddr.String(), denom: testDenom, amount: withdrawAmount, decryptableBalance: senderAccount.DecryptableAvailableBalance, @@ -1534,7 +1533,6 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { in = tt.args.setUp(in) } inputArgs, err := withdrawMethod.Inputs.Pack( - in.senderAddr, in.denom, in.amount, in.decryptableBalance, @@ -1544,7 +1542,7 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { resp, remainingGas, err := p.RunAndCalculateGas( &evm, - senderEVMAddr, + *tt.args.caller, senderEVMAddr, append(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).WithdrawID, inputArgs...), 2000000, From ff10601f8ef37e4ade64ba5c4e8ea93d9b739ee6 Mon Sep 17 00:00:00 2001 From: mj Date: Wed, 15 Jan 2025 06:15:51 +0800 Subject: [PATCH 5/6] close account and integration tests --- .../apply_pending_balance_test.yaml | 78 ++++++- .../close_account_test.yaml | 38 ++++ .../withdraw_test.yaml | 80 +++++++- precompiles/common/expected_keepers.go | 1 + precompiles/confidentialtransfers/CT.sol | 5 + precompiles/confidentialtransfers/abi.json | 2 +- precompiles/confidentialtransfers/ct.go | 59 ++++++ precompiles/confidentialtransfers/ct_test.go | 192 ++++++++++++++++++ x/evm/client/cli/query.go | 99 ++++++++- 9 files changed, 548 insertions(+), 6 deletions(-) diff --git a/integration_test/confidential_transfers_module/apply_pending_balance_test.yaml b/integration_test/confidential_transfers_module/apply_pending_balance_test.yaml index d1ef5815e..4dfbf1c35 100644 --- a/integration_test/confidential_transfers_module/apply_pending_balance_test.yaml +++ b/integration_test/confidential_transfers_module/apply_pending_balance_test.yaml @@ -97,4 +97,80 @@ verifiers: # Verify that the apply pending balance was unsuccessful - type: eval - expr: APPLY_PENDING_BALANCE_CODE != 0 \ No newline at end of file + expr: APPLY_PENDING_BALANCE_CODE != 0 + +- name: Test applying pending balances on confidential token account via precompile + inputs: + # Setup test account + - cmd: printf "12345678\n" | seid keys add ctprapply + - cmd: printf "12345678\n" | seid keys show -a admin + env: ADMIN_ADDR + - cmd: printf "12345678\n" | seid keys show -a ctprapply + env: TEST_PRECOMPILE_ADDR + - cmd: printf "12345678\n" | seid tx bank send $ADMIN_ADDR $TEST_PRECOMPILE_ADDR 10sei -b block --fees 2000usei --chain-id sei -y --output json | jq -r ".code" + - cmd: printf "12345678\n" | seid tx bank send $ADMIN_ADDR $TEST_PRECOMPILE_ADDR 20000uatom -b block --fees 2000usei --chain-id sei -y --output json | jq -r ".code" + + # Initialize confidential token account for denom uatom for ctprapply + - cmd: printf "12345678\n" | seid tx ct init-account uatom --from ctprapply --fees 4000usei --chain-id sei -b block -y --output json | jq -r ".code" + env: INIT_ACCOUNT_CODE + + # Deposit to the confidential token account + - cmd: printf "12345678\n" | seid tx ct deposit 10000uatom --from ctprapply --fees 4000usei --chain-id sei -b block -y --output json | jq -r ".code" + env: DEPOSIT_CODE + + # Query the initial account state + - cmd: printf "12345678\n" | seid q ct account uatom $TEST_PRECOMPILE_ADDR --decryptor ctprapply --output json + env: INITIAL_ACCOUNT_STATE + - cmd: echo $INITIAL_ACCOUNT_STATE | jq -r ".combined_pending_balance" + env: INITIAL_PENDING_BALANCE + - cmd: echo $(( $(echo $INITIAL_ACCOUNT_STATE | jq -r ".decryptable_available_balance") )) # Convert to integer + env: INITIAL_AVAILABLE_BALANCE + + # Apply Pending Balances on the confidential token account via precompile + - cmd: | + printf "12345678\n" | seid tx evm call-contract --from ctprapply --fees=40000usei --gas=2000000 0x0000000000000000000000000000000000001010 -b block "$(printf "12345678\n" | seid q evm ct-apply-pending-balance-payload precompiles/confidentialtransfers/abi.json $TEST_PRECOMPILE_ADDR uatom)" | sed "s/.*: //" + env: TX_HASH + - cmd: sleep 2 + # Query the Cosmos tx hash + - cmd: | + curl --location --request GET "localhost:8545/" --header "Content-Type: application/json" --data "{\"jsonrpc\":\"2.0\",\"method\":\"sei_getCosmosTx\",\"params\":[\"$TX_HASH\"],\"id\":788}" | jq -r ".result" + env: COSMOS_TX_HASH + + # Query the Cosmos tx result + - cmd: seid q tx $COSMOS_TX_HASH --output json | jq -r ".code" + env: APPLY_PENDING_BALANCE_CODE + + # Query the account to verify the new account state + - cmd: printf "12345678\n" | seid q ct account uatom $TEST_PRECOMPILE_ADDR --decryptor ctprapply --output json + env: FINAL_ACCOUNT_STATE + - cmd: echo $FINAL_ACCOUNT_STATE | jq -r ".combined_pending_balance" + env: FINAL_PENDING_BALANCE + - cmd: echo $FINAL_ACCOUNT_STATE | jq -r ".pending_balance_credit_counter" + env: FINAL_PENDING_BALANCE_COUNTER + - cmd: echo $FINAL_ACCOUNT_STATE | jq -r ".decryptable_available_balance" + env: FINAL_AVAILABLE_BALANCE + + verifiers: + # Verify that the account exists after the instruction is executed. + - type: eval + expr: INIT_ACCOUNT_CODE == 0 or INIT_ACCOUNT_CODE == 38 + + # Verify that the deposit was successful + - type: eval + expr: DEPOSIT_CODE == 0 + + # Verify that the deposit was successful + - type: eval + expr: APPLY_PENDING_BALANCE_CODE == 0 + + # Verify that the pending balance is now 0. + - type: eval + expr: FINAL_PENDING_BALANCE == 0 + + # Verify that the pending balance counter is 0 + - type: eval + expr: FINAL_PENDING_BALANCE_COUNTER == 0 + + # Verify that the available balance is the sum of the initial pending balance and initial available balance + - type: eval + expr: INITIAL_AVAILABLE_BALANCE + INITIAL_PENDING_BALANCE == FINAL_AVAILABLE_BALANCE \ No newline at end of file diff --git a/integration_test/confidential_transfers_module/close_account_test.yaml b/integration_test/confidential_transfers_module/close_account_test.yaml index 57f813117..92c17c7f8 100644 --- a/integration_test/confidential_transfers_module/close_account_test.yaml +++ b/integration_test/confidential_transfers_module/close_account_test.yaml @@ -86,6 +86,44 @@ - type: eval expr: WITHDRAW_CODE == 0 + # Verify that closing the account was successful + - type: eval + expr: CLOSE_ACCOUNT_CODE == 0 + +- name: Test closing confidential token account via precompile + inputs: + # Setup test account + - cmd: printf "12345678\n" | seid keys add ctprclose + - cmd: printf "12345678\n" | seid keys show -a admin + env: ADMIN_ADDR + - cmd: printf "12345678\n" | seid keys show -a ctprclose + env: TEST_PRECOMPILE_ADDR + - cmd: printf "12345678\n" | seid tx bank send $ADMIN_ADDR $TEST_PRECOMPILE_ADDR 10sei -b block --fees 2000usei --chain-id sei -y --output json | jq -r ".code" + - cmd: printf "12345678\n" | seid tx bank send $ADMIN_ADDR $TEST_PRECOMPILE_ADDR 20000uatom -b block --fees 2000usei --chain-id sei -y --output json | jq -r ".code" + + # Initialize confidential token account for denom uatom for ctprclose + - cmd: printf "12345678\n" | seid tx ct init-account uatom --from ctprclose --fees 4000usei --chain-id sei -b block -y --output json | jq -r ".code" + env: INIT_ACCOUNT_CODE + + # Close the confidential token account via precompile + - cmd: | + printf "12345678\n" | seid tx evm call-contract --from ctprclose --fees=40000usei --gas=2000000 0x0000000000000000000000000000000000001010 -b block "$(printf "12345678\n" | seid q evm ct-close-account-payload precompiles/confidentialtransfers/abi.json $TEST_PRECOMPILE_ADDR uatom)" | sed "s/.*: //" + env: TX_HASH + - cmd: sleep 2 + # Query the Cosmos tx hash + - cmd: | + curl --location --request GET "localhost:8545/" --header "Content-Type: application/json" --data "{\"jsonrpc\":\"2.0\",\"method\":\"sei_getCosmosTx\",\"params\":[\"$TX_HASH\"],\"id\":788}" | jq -r ".result" + env: COSMOS_TX_HASH + + # Query the Cosmos tx result + - cmd: seid q tx $COSMOS_TX_HASH --output json | jq -r ".code" + env: CLOSE_ACCOUNT_CODE + + verifiers: + # Verify that the account exists after the instruction is executed. + - type: eval + expr: INIT_ACCOUNT_CODE == 0 or INIT_ACCOUNT_CODE == 38 + # Verify that closing the account was successful - type: eval expr: CLOSE_ACCOUNT_CODE == 0 \ No newline at end of file diff --git a/integration_test/confidential_transfers_module/withdraw_test.yaml b/integration_test/confidential_transfers_module/withdraw_test.yaml index d2ed5cd1e..c6a462ba8 100644 --- a/integration_test/confidential_transfers_module/withdraw_test.yaml +++ b/integration_test/confidential_transfers_module/withdraw_test.yaml @@ -120,4 +120,82 @@ verifiers: # Verify that the withdraw was unsuccessful due to uninitialized account - type: eval - expr: WITHDRAW_CODE != 0 \ No newline at end of file + expr: WITHDRAW_CODE != 0 + +- name: Test withdrawing on confidential token account via precompile + inputs: + # Setup test account + - cmd: printf "12345678\n" | seid keys add ctprwithdraw + - cmd: printf "12345678\n" | seid keys show -a admin + env: ADMIN_ADDR + - cmd: printf "12345678\n" | seid keys show -a ctprwithdraw + env: TEST_PRECOMPILE_ADDR + - cmd: printf "12345678\n" | seid tx bank send $ADMIN_ADDR $TEST_PRECOMPILE_ADDR 10sei -b block --fees 2000usei --chain-id sei -y --output json | jq -r ".code" + - cmd: printf "12345678\n" | seid tx bank send $ADMIN_ADDR $TEST_PRECOMPILE_ADDR 20000uatom -b block --fees 2000usei --chain-id sei -y --output json | jq -r ".code" + + - cmd: echo 10000 + env: WITHDRAW_AMOUNT + + # Initialize confidential token account for denom uatom for ctprwithdraw + - cmd: printf "12345678\n" | seid tx ct init-account uatom --from ctprwithdraw --fees 4000usei --chain-id sei -b block -y --output json | jq -r ".code" + env: INIT_ACCOUNT_CODE + + # Deposit to the confidential token account + - cmd: printf "12345678\n" | seid tx ct deposit ${WITHDRAW_AMOUNT}uatom --from ctprwithdraw --fees 4000usei --chain-id sei -b block -y --output json | jq -r ".code" + env: DEPOSIT_CODE + + - cmd: printf "12345678\n" | seid tx ct apply-pending-balance uatom --from ctprwithdraw --fees 4000usei --chain-id sei -b block -y --output json | jq -r ".code" + env: APPLY_PENDING_BALANCE_CODE + + # Query the initial account state + - cmd: printf "12345678\n" | seid q ct account uatom $TEST_PRECOMPILE_ADDR --decryptor ctprwithdraw --output json + env: INITIAL_ACCOUNT_STATE + - cmd: echo $INITIAL_ACCOUNT_STATE | jq -r ".combined_pending_balance" + env: INITIAL_PENDING_BALANCE + - cmd: echo $(( $(echo $INITIAL_ACCOUNT_STATE | jq -r ".decryptable_available_balance") )) # Convert to integer + env: INITIAL_AVAILABLE_BALANCE + + # Withdraw from the confidential token account via precompile + - cmd: | + printf "12345678\n" | seid tx evm call-contract --from ctprwithdraw --fees=40000usei --gas=2000000 0x0000000000000000000000000000000000001010 -b block "$(printf "12345678\n" | seid q evm ct-withdraw-payload precompiles/confidentialtransfers/abi.json $TEST_PRECOMPILE_ADDR ${WITHDRAW_AMOUNT}uatom)" | sed "s/.*: //" + env: TX_HASH + - cmd: sleep 2 + # Query the Cosmos tx hash + - cmd: | + curl --location --request GET "localhost:8545/" --header "Content-Type: application/json" --data "{\"jsonrpc\":\"2.0\",\"method\":\"sei_getCosmosTx\",\"params\":[\"$TX_HASH\"],\"id\":788}" | jq -r ".result" + env: COSMOS_TX_HASH + + # Query the Cosmos tx result + - cmd: seid q tx $COSMOS_TX_HASH --output json | jq -r ".code" + env: WITHDRAW_CODE + + # Query the account to verify the new account state + - cmd: printf "12345678\n" | seid q ct account uatom $TEST_PRECOMPILE_ADDR --decryptor ctprwithdraw --output json + env: FINAL_ACCOUNT_STATE + - cmd: echo $FINAL_ACCOUNT_STATE | jq -r ".combined_pending_balance" + env: FINAL_PENDING_BALANCE + - cmd: echo $FINAL_ACCOUNT_STATE | jq -r ".pending_balance_credit_counter" + env: FINAL_PENDING_BALANCE_COUNTER + - cmd: echo $FINAL_ACCOUNT_STATE | jq -r ".decryptable_available_balance" + env: FINAL_AVAILABLE_BALANCE + + verifiers: + # Verify that the account exists after the instruction is executed. + - type: eval + expr: INIT_ACCOUNT_CODE == 0 or INIT_ACCOUNT_CODE == 38 + + # Verify that the deposit was successful + - type: eval + expr: DEPOSIT_CODE == 0 + + # Verify that the apply balances operation was successful + - type: eval + expr: APPLY_PENDING_BALANCE_CODE == 0 + + # Verify that the withdraw was successful + - type: eval + expr: WITHDRAW_CODE == 0 + + # Verify that the available balance is the difference between the initial pending balance and withdraw amount + - type: eval + expr: INITIAL_AVAILABLE_BALANCE - WITHDRAW_AMOUNT == FINAL_AVAILABLE_BALANCE \ No newline at end of file diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index a617fbc8c..437344fbd 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -137,4 +137,5 @@ type ConfidentialTransfersKeeper interface { InitializeAccount(context.Context, *cttypes.MsgInitializeAccount) (*cttypes.MsgInitializeAccountResponse, error) Transfer(goCtx context.Context, req *cttypes.MsgTransfer) (*cttypes.MsgTransferResponse, error) Withdraw(goCtx context.Context, req *cttypes.MsgWithdraw) (*cttypes.MsgWithdrawResponse, error) + CloseAccount(goCtx context.Context, req *cttypes.MsgCloseAccount) (*cttypes.MsgCloseAccountResponse, error) } diff --git a/precompiles/confidentialtransfers/CT.sol b/precompiles/confidentialtransfers/CT.sol index c0a70b9ef..d5e026783 100644 --- a/precompiles/confidentialtransfers/CT.sol +++ b/precompiles/confidentialtransfers/CT.sol @@ -74,4 +74,9 @@ interface ICT { bytes remainingBalanceCommitment, bytes proofs ) external returns (bool success); + + function closeAccount( + string denom, + bytes proofs + ) external returns (bool success); } diff --git a/precompiles/confidentialtransfers/abi.json b/precompiles/confidentialtransfers/abi.json index 3e720e350..ccac42114 100644 --- a/precompiles/confidentialtransfers/abi.json +++ b/precompiles/confidentialtransfers/abi.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}],"internalType":"struct ICT.Auditor[]","name":"auditors","type":"tuple[]"}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"}],"name":"applyPendingBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"remainingBalanceCommitment","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] +[{"inputs":[{"internalType":"string","name":"fromAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"initializeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"toAddress","type":"string"},{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"fromAmountLo","type":"bytes"},{"internalType":"bytes","name":"fromAmountHi","type":"bytes"},{"internalType":"bytes","name":"toAmountLo","type":"bytes"},{"internalType":"bytes","name":"toAmountHi","type":"bytes"},{"internalType":"bytes","name":"remainingBalance","type":"bytes"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"},{"components":[{"internalType":"string","name":"auditorAddress","type":"string"},{"internalType":"bytes","name":"encryptedTransferAmountLo","type":"bytes"},{"internalType":"bytes","name":"encryptedTransferAmountHi","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiValidityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountLoEqualityProof","type":"bytes"},{"internalType":"bytes","name":"transferAmountHiEqualityProof","type":"bytes"}],"internalType":"struct ICT.Auditor[]","name":"auditors","type":"tuple[]"}],"name":"transferWithAuditors","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint64","name":"amount","type":"uint64"}],"name":"deposit","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"}],"name":"applyPendingBalance","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"decryptableBalance","type":"string"},{"internalType":"bytes","name":"remainingBalanceCommitment","type":"bytes"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"withdraw","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"denom","type":"string"},{"internalType":"bytes","name":"proofs","type":"bytes"}],"name":"closeAccount","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/precompiles/confidentialtransfers/ct.go b/precompiles/confidentialtransfers/ct.go index e4556b4f9..10f3547a1 100644 --- a/precompiles/confidentialtransfers/ct.go +++ b/precompiles/confidentialtransfers/ct.go @@ -21,6 +21,7 @@ const ( TransferMethod = "transfer" TransferWithAuditorsMethod = "transferWithAuditors" WithdrawMethod = "withdraw" + CloseAccountMethod = "closeAccount" ) const ( @@ -43,6 +44,7 @@ type PrecompileExecutor struct { TransferID []byte TransferWithAuditorsID []byte WithdrawID []byte + CloseAccountID []byte } func NewPrecompile(ctkeeper pcommon.ConfidentialTransfersKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { @@ -68,6 +70,8 @@ func NewPrecompile(ctkeeper pcommon.ConfidentialTransfersKeeper, evmKeeper pcomm p.TransferWithAuditorsID = m.ID case WithdrawMethod: p.WithdrawID = m.ID + case CloseAccountMethod: + p.CloseAccountID = m.ID } } @@ -112,6 +116,11 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller return nil, 0, errors.New("cannot call ct precompile from staticcall") } return p.withdraw(ctx, method, caller, args) + case CloseAccountMethod: + if readOnly { + return nil, 0, errors.New("cannot call ct precompile from staticcall") + } + return p.closeAccount(ctx, method, caller, args) } return } @@ -686,3 +695,53 @@ func (p PrecompileExecutor) withdraw(ctx sdk.Context, method *abi.Method, caller remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) return } + +func (p PrecompileExecutor) closeAccount(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}) (ret []byte, remainingGas uint64, rerr error) { + defer func() { + if err := recover(); err != nil { + ret = nil + remainingGas = 0 + rerr = fmt.Errorf("%s", err) + return + } + }() + + if err := pcommon.ValidateArgsLength(args, 2); err != nil { + rerr = err + return + } + + fromAddr, _, err := p.getAssociatedAddressesByEVMAddress(ctx, caller) + if err != nil { + rerr = err + return + } + + denom := args[0].(string) + if denom == "" { + rerr = errors.New("invalid denom") + return + } + + var closeAccountProofs cttypes.CloseAccountMsgProofs + err = closeAccountProofs.Unmarshal(args[1].([]byte)) + if err != nil { + rerr = err + return + } + + msg := &cttypes.MsgCloseAccount{ + Address: fromAddr.String(), + Denom: denom, + Proofs: &closeAccountProofs, + } + + _, err = p.ctKeeper.CloseAccount(sdk.WrapSDKContext(ctx), msg) + if err != nil { + rerr = err + return + } + ret, rerr = method.Outputs.Pack(true) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/confidentialtransfers/ct_test.go b/precompiles/confidentialtransfers/ct_test.go index 372c5c83d..cee823c1f 100644 --- a/precompiles/confidentialtransfers/ct_test.go +++ b/precompiles/confidentialtransfers/ct_test.go @@ -1517,3 +1517,195 @@ func TestPrecompileWithdraw_Execute(t *testing.T) { }) } } + +func TestPrecompileCloseAccount_Execute(t *testing.T) { + testDenom := "usei" + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + + p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + require.Nil(t, err) + CloseAccountMethod, _ := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).CloseAccountID) + expectedTrueResponse, _ := CloseAccountMethod.Outputs.Pack(true) + var senderAddr, otherAddr sdk.AccAddress + var senderEVMAddr, otherEVMAddr common.Address + notAssociatedUserPrivateKey := testkeeper.MockPrivateKey() + _, notAssociatedEVMAddr := testkeeper.PrivateKeyToAddresses(notAssociatedUserPrivateKey) + + type inputs struct { + denom string + proofs []byte + } + + type args struct { + isReadOnly bool + isFromDelegateCall bool + value *big.Int + setUp func(in inputs) inputs + caller *common.Address + } + tests := []struct { + name string + args args + wantRet []byte + wantRemainingGas uint64 + wantErr bool + wantErrMsg string + }{ + { + name: "precompile should return true if input is valid", + args: args{ + caller: &senderEVMAddr, + }, + wantRet: expectedTrueResponse, + wantRemainingGas: 0x1e6c5d, + wantErr: false, + }, + { + name: "precompile should return error if caller did not create calldata", + args: args{ + caller: &otherEVMAddr, + }, + wantErr: true, + wantErrMsg: "pending balance lo must be 0: invalid request", + }, + { + name: "precompile should return error if Sei address is not associated with an EVM address", + args: args{caller: ¬AssociatedEVMAddr}, + wantErr: true, + wantErrMsg: fmt.Sprintf("address %s is not associated", notAssociatedEVMAddr), + }, + { + name: "precompile should return error if denom is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.denom = "" + return in + }, + caller: &senderEVMAddr, + }, + wantErr: true, + wantErrMsg: "invalid denom", + }, + { + name: "precompile should return error if proofs is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.proofs = []byte("invalid") + return in + }, + caller: &senderEVMAddr, + }, + wantErr: true, + wantErrMsg: "unexpected EOF", + }, + { + name: "precompile should return error if called from static call", + args: args{ + isReadOnly: true, + caller: &senderEVMAddr, + }, + wantErr: true, + wantErrMsg: "cannot call ct precompile from staticcall", + }, + { + name: "precompile should return error if value is not nil", + args: args{ + value: big.NewInt(100), + caller: &senderEVMAddr, + }, + wantErr: true, + wantErrMsg: "sending funds to a non-payable function", + }, + } + for _, tt := range tests { + // Setup sender addresses and environment + senderPrivateKey := testkeeper.MockPrivateKey() + senderAddr, senderEVMAddr = testkeeper.PrivateKeyToAddresses(senderPrivateKey) + otherAddr, otherEVMAddr, _, _ = setUpCtAccount(k, ctx, testDenom) + k.SetAddressMapping(ctx, senderAddr, senderEVMAddr) + k.SetAddressMapping(ctx, otherAddr, otherEVMAddr) + + err := k.BankKeeper().MintCoins( + ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(20000000)))) + require.Nil(t, err) + err = k.BankKeeper().SendCoinsFromModuleToModule(ctx, types.ModuleName, cttypes.ModuleName, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000)))) + err = k.BankKeeper().SendCoinsFromModuleToAccount( + ctx, types.ModuleName, senderAddr, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000)))) + require.Nil(t, err) + + // setup sender and receiver ct accounts + ctKeeper := k.CtKeeper() + privHex := hex.EncodeToString(senderPrivateKey.Bytes()) + senderKey, _ := crypto.HexToECDSA(privHex) + initSenderAccount, err := cttypes.NewInitializeAccount(senderAddr.String(), testDenom, *senderKey) + require.NoError(t, err) + senderAccount := cttypes.Account{ + PublicKey: *initSenderAccount.Pubkey, + PendingBalanceLo: initSenderAccount.PendingBalanceLo, + PendingBalanceHi: initSenderAccount.PendingBalanceHi, + PendingBalanceCreditCounter: 0, + AvailableBalance: initSenderAccount.AvailableBalance, + DecryptableAvailableBalance: initSenderAccount.DecryptableBalance, + } + err = ctKeeper.SetAccount(ctx, senderAddr.String(), testDenom, senderAccount) + require.NoError(t, err) + + p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + require.Nil(t, err) + statedb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: statedb, + TxContext: vm.TxContext{Origin: senderEVMAddr}, + } + + closeAccountMethod, err := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).CloseAccountID) + require.Nil(t, err) + + closeAccount, _ := cttypes.NewCloseAccount( + *senderKey, + senderAddr.String(), + testDenom, + senderAccount.PendingBalanceLo, + senderAccount.PendingBalanceHi, + senderAccount.AvailableBalance) + + clProto := cttypes.NewMsgCloseAccountProto(closeAccount) + proofs, _ := clProto.Proofs.Marshal() + + t.Run(tt.name, func(t *testing.T) { + in := inputs{ + denom: testDenom, + proofs: proofs, + } + if tt.args.setUp != nil { + in = tt.args.setUp(in) + } + inputArgs, err := closeAccountMethod.Inputs.Pack( + in.denom, + in.proofs) + require.Nil(t, err) + + resp, remainingGas, err := p.RunAndCalculateGas( + &evm, + *tt.args.caller, + senderEVMAddr, + append(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).CloseAccountID, inputArgs...), + 2000000, + tt.args.value, + nil, + tt.args.isReadOnly, + tt.args.isFromDelegateCall) + if tt.wantErr { + require.NotNil(t, err) + require.Equal(t, tt.wantErrMsg, string(resp)) + return + } else { + require.NoError(t, err) + require.Equal(t, tt.wantRet, resp) + require.Equal(t, tt.wantRemainingGas, remainingGas) + } + }) + } +} diff --git a/x/evm/client/cli/query.go b/x/evm/client/cli/query.go index df658dbaa..2ee40901c 100644 --- a/x/evm/client/cli/query.go +++ b/x/evm/client/cli/query.go @@ -59,6 +59,7 @@ func GetQueryCmd(_ string) *cobra.Command { cmd.AddCommand(GetCmdQueryCtInitAccountPayload()) cmd.AddCommand(GetCmdQueryCtApplyPendingBalancePayload()) cmd.AddCommand(GetCmdQueryCtWithdrawPayload()) + cmd.AddCommand(GetCmdQueryCtCloseAccountPayload()) return cmd } @@ -837,7 +838,6 @@ func queryCtApplyPendingBalancePayload(cmd *cobra.Command, args []string) error bz, err := newAbi.Pack( confidentialtransfers.ApplyPendingBalanceMethod, - applyPendingBalanceProto.Address, applyPendingBalanceProto.Denom, applyPendingBalanceProto.NewDecryptableAvailableBalance, applyPendingBalanceProto.CurrentPendingBalanceCounter, @@ -936,9 +936,8 @@ func queryCtWithdrawPayload(cmd *cobra.Command, args []string) error { bz, err := newAbi.Pack( confidentialtransfers.WithdrawMethod, - withdrawProto.FromAddress, withdrawProto.Denom, - withdrawProto.Amount, + coin.Amount.BigInt(), withdrawProto.DecryptableBalance, remainingBalanceCommitment, proofs) @@ -949,6 +948,100 @@ func queryCtWithdrawPayload(cmd *cobra.Command, args []string) error { return queryClientCtx.PrintString(hex.EncodeToString(bz)) } +func GetCmdQueryCtCloseAccountPayload() *cobra.Command { + cmd := &cobra.Command{ + Use: "ct-close-account-payload [abi-filepath] [from_address] [denom]", + Short: "get hex payload for the confidential transfers close account method", + Args: cobra.ExactArgs(3), + RunE: queryCtCloseAccountPayload, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +func queryCtCloseAccountPayload(cmd *cobra.Command, args []string) error { + queryClientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(queryClientCtx) + + dat, err := os.ReadFile(args[0]) + if err != nil { + return err + } + + newAbi, err := abi.JSON(bytes.NewReader(dat)) + if err != nil { + return err + } + + fromAddress := args[1] + if fromAddress == "" { + return errors.New("from address cannot be empty") + } + + seiAddress, err := getSeiAddress(queryClient, fromAddress) + if err != nil { + return err + } + + denom := args[2] + if denom == "" { + return errors.New("denom cannot be empty") + } + + _, name, _, err := client.GetFromFields(queryClientCtx, queryClientCtx.Keyring, seiAddress) + if err != nil { + return err + } + + privKey, err := getPrivateKeyForName(cmd, name) + if err != nil { + return err + } + + ctQueryClient := cttypes.NewQueryClient(queryClientCtx) + fromAccount, err := ctcliutils.GetAccount(ctQueryClient, seiAddress, denom) + if err != nil { + return err + } + + closeAccount, err := cttypes.NewCloseAccount( + *privKey, + seiAddress, + denom, + fromAccount.PendingBalanceLo, + fromAccount.PendingBalanceHi, + fromAccount.AvailableBalance) + if err != nil { + return err + } + + closeAccountProto := cttypes.NewMsgCloseAccountProto(closeAccount) + + if err = closeAccountProto.ValidateBasic(); err != nil { + return err + } + + proofs, err := closeAccountProto.Proofs.Marshal() + if err != nil { + return err + } + + bz, err := newAbi.Pack( + confidentialtransfers.CloseAccountMethod, + closeAccountProto.Denom, + proofs) + + if err != nil { + return err + } + return queryClientCtx.PrintString(hex.EncodeToString(bz)) +} + func getSeiAddress(queryClient types.QueryClient, address string) (string, error) { if common.IsHexAddress(address) { evmAddr := common.HexToAddress(address) From 760e3c58166ae986a02332226249241bebb4c890 Mon Sep 17 00:00:00 2001 From: mj Date: Wed, 15 Jan 2025 18:56:39 +0800 Subject: [PATCH 6/6] lint --- x/evm/client/cli/query.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/x/evm/client/cli/query.go b/x/evm/client/cli/query.go index 2ee40901c..000d6ddcc 100644 --- a/x/evm/client/cli/query.go +++ b/x/evm/client/cli/query.go @@ -929,6 +929,10 @@ func queryCtWithdrawPayload(cmd *cobra.Command, args []string) error { } remainingBalanceCommitment, err := withdrawProto.RemainingBalanceCommitment.Marshal() + if err != nil { + return err + } + proofs, err := withdrawProto.Proofs.Marshal() if err != nil { return err