From c7b0c06c4e0a248dec0074dab74b69f611885a2b Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Fri, 20 Dec 2024 13:52:04 -0800 Subject: [PATCH 1/5] - add sol init account --- precompiles/confidentialtransfers/CT.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/precompiles/confidentialtransfers/CT.sol b/precompiles/confidentialtransfers/CT.sol index 64a47e3dc..a039790c8 100644 --- a/precompiles/confidentialtransfers/CT.sol +++ b/precompiles/confidentialtransfers/CT.sol @@ -8,6 +8,17 @@ ICT constant CT_CONTRACT = ICT( interface ICT { // Transactions + function initializeAccount( + string fromAddress, + string denom, + bytes publicKey, + string decryptableBalance, + bytes pendingBalanceLo, + bytes pendingBalanceHi, + bytes availableBalance, + bytes proofs + ) external returns (bool success); + function transfer( address fromAddress, address toAddress, From afb8a908f824c615553e5a3de377dfaa44e01e66 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Fri, 20 Dec 2024 15:53:45 -0800 Subject: [PATCH 2/5] - precompile + basic tests --- precompiles/common/expected_keepers.go | 1 + precompiles/confidentialtransfers/abi.json | 227 ++++++++++++++++++- precompiles/confidentialtransfers/ct.go | 134 ++++++++++- precompiles/confidentialtransfers/ct_test.go | 207 ++++++++++++++++- 4 files changed, 555 insertions(+), 14 deletions(-) diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index b00be2fdf..103e95cd6 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -133,4 +133,5 @@ type ChannelKeeper interface { type ConfidentialTransfersKeeper interface { Transfer(goCtx context.Context, req *cttypes.MsgTransfer) (*cttypes.MsgTransferResponse, error) + InitializeAccount(context.Context, *cttypes.MsgInitializeAccount) (*cttypes.MsgInitializeAccountResponse, error) } diff --git a/precompiles/confidentialtransfers/abi.json b/precompiles/confidentialtransfers/abi.json index 0451ba4b8..c6afee6e5 100644 --- a/precompiles/confidentialtransfers/abi.json +++ b/precompiles/confidentialtransfers/abi.json @@ -1 +1,226 @@ -[{"inputs":[{"internalType":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"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":"address","name":"fromAddress","type":"address"},{"internalType":"address","name":"toAddress","type":"address"},{"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":"address","name":"auditorAddress","type":"address"},{"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": "address", + "name": "fromAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "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": "address", + "name": "fromAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "toAddress", + "type": "address" + }, + { + "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": "address", + "name": "auditorAddress", + "type": "address" + }, + { + "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" + } +] diff --git a/precompiles/confidentialtransfers/ct.go b/precompiles/confidentialtransfers/ct.go index bfa3dcf24..5baa5d363 100644 --- a/precompiles/confidentialtransfers/ct.go +++ b/precompiles/confidentialtransfers/ct.go @@ -15,6 +15,7 @@ import ( ) const ( + InitializeAccountMethod = "initializeAccount" TransferMethod = "transfer" TransferWithAuditorsMethod = "transferWithAuditors" ) @@ -35,6 +36,7 @@ type PrecompileExecutor struct { TransferID []byte TransferWithAuditorsID []byte + InitializeAccountID []byte } func NewPrecompile(ctkeeper pcommon.ConfidentialTransfersKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { @@ -52,6 +54,8 @@ func NewPrecompile(ctkeeper pcommon.ConfidentialTransfersKeeper, evmKeeper pcomm p.TransferID = m.ID case TransferWithAuditorsMethod: p.TransferWithAuditorsID = m.ID + case InitializeAccountMethod: + p.InitializeAccountID = m.ID } } @@ -62,17 +66,26 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller if ctx.EVMPrecompileCalledFromDelegateCall() { return nil, 0, errors.New("cannot delegatecall ct") } + if err := pcommon.ValidateNonPayable(value); err != nil { + return nil, 0, err + } switch method.Name { case TransferMethod: if readOnly { return nil, 0, errors.New("cannot call ct precompile from staticcall") } - return p.transfer(ctx, method, caller, args, value) + return p.transfer(ctx, method, args) case TransferWithAuditorsMethod: if readOnly { return nil, 0, errors.New("cannot call ct precompile from staticcall") } - return p.transferWithAuditors(ctx, method, caller, args, value) + return p.transferWithAuditors(ctx, method, args) + + case InitializeAccountMethod: + if readOnly { + return nil, 0, errors.New("cannot call ct precompile from staticcall") + } + return p.initializeAccount(ctx, method, args) } return } @@ -81,7 +94,7 @@ func (p PrecompileExecutor) EVMKeeper() pcommon.EVMKeeper { return p.evmKeeper } -func (p PrecompileExecutor) transfer(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { +func (p PrecompileExecutor) transfer(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, remainingGas uint64, rerr error) { defer func() { if err := recover(); err != nil { ret = nil @@ -90,10 +103,6 @@ func (p PrecompileExecutor) transfer(ctx sdk.Context, method *abi.Method, caller return } }() - if err := pcommon.ValidateNonPayable(value); err != nil { - rerr = err - return - } if err := pcommon.ValidateArgsLength(args, 10); err != nil { rerr = err @@ -121,7 +130,7 @@ func (p PrecompileExecutor) transfer(ctx sdk.Context, method *abi.Method, caller return } -func (p PrecompileExecutor) transferWithAuditors(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}, value *big.Int) (ret []byte, remainingGas uint64, rerr error) { +func (p PrecompileExecutor) transferWithAuditors(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, remainingGas uint64, rerr error) { defer func() { if err := recover(); err != nil { ret = nil @@ -130,10 +139,6 @@ func (p PrecompileExecutor) transferWithAuditors(ctx sdk.Context, method *abi.Me return } }() - if err := pcommon.ValidateNonPayable(value); err != nil { - rerr = err - return - } if err := pcommon.ValidateArgsLength(args, 11); err != nil { rerr = err @@ -252,6 +257,23 @@ func (p PrecompileExecutor) accAddressFromArg(ctx sdk.Context, arg interface{}) return seiAddr, nil } +func (p PrecompileExecutor) getSeiAddressFromString(ctx sdk.Context, addr string) (sdk.AccAddress, error) { + if common.IsHexAddress(addr) { + evmAddr := common.HexToAddress(addr) + seiAddr, associated := p.evmKeeper.GetSeiAddress(ctx, evmAddr) + if associated { + return seiAddr, nil + } else { + return nil, fmt.Errorf("address %s is not associated", addr) + } + } + if seiAddress, err := sdk.AccAddressFromBech32(addr); err != nil { + return nil, fmt.Errorf("invalid address %s: %w", addr, err) + } else { + return seiAddress, nil + } +} + func (p PrecompileExecutor) getAuditorsFromArg(ctx sdk.Context, arg interface{}) (auditorsArray []*cttypes.Auditor, rerr error) { defer func() { if err := recover(); err != nil { @@ -330,3 +352,91 @@ func (p PrecompileExecutor) getAuditorsFromArg(ctx sdk.Context, arg interface{}) } return auditors, nil } + +func (p PrecompileExecutor) initializeAccount(ctx sdk.Context, method *abi.Method, 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, 8); err != nil { + rerr = err + return + } + + userAddr, err := p.getSeiAddressFromString(ctx, args[0].(string)) + if err != nil { + rerr = err + return + } + + denom := args[1].(string) + if denom == "" { + rerr = errors.New("invalid denom") + return + } + + publicKey, ok := args[2].([]byte) + if !ok { + rerr = errors.New("invalid public key") + return + } + + decryptableBalance := args[3].(string) + if decryptableBalance == "" { + rerr = errors.New("invalid decryptable balance") + return + } + + var pendingBalanceLo cttypes.Ciphertext + err = pendingBalanceLo.Unmarshal(args[4].([]byte)) + if err != nil { + rerr = err + return + } + + var pendingBalanceHi cttypes.Ciphertext + err = pendingBalanceHi.Unmarshal(args[5].([]byte)) + if err != nil { + rerr = err + return + } + + var availableBalance cttypes.Ciphertext + err = availableBalance.Unmarshal(args[6].([]byte)) + if err != nil { + rerr = err + return + } + + var initializeAccountProofs cttypes.InitializeAccountMsgProofs + err = initializeAccountProofs.Unmarshal(args[7].([]byte)) + if err != nil { + rerr = err + return + } + + msg := &cttypes.MsgInitializeAccount{ + FromAddress: userAddr.String(), + Denom: denom, + PublicKey: publicKey, + DecryptableBalance: decryptableBalance, + PendingBalanceLo: &pendingBalanceLo, + PendingBalanceHi: &pendingBalanceHi, + AvailableBalance: &availableBalance, + Proofs: &initializeAccountProofs, + } + + _, err = p.ctKeeper.InitializeAccount(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 747e42012..4dc788029 100644 --- a/precompiles/confidentialtransfers/ct_test.go +++ b/precompiles/confidentialtransfers/ct_test.go @@ -23,7 +23,7 @@ import ( "testing" ) -func TestPrecompileExecutor_Execute(t *testing.T) { +func TestPrecompileTransfer_Execute(t *testing.T) { testDenom := "usei" testApp := testkeeper.EVMTestApp ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) @@ -623,3 +623,208 @@ func setUpCtAccount(k *evmkeeper.Keeper, ctx sdk.Context, testDenom string) (sdk } return addr, EVMAddr, initializeAccount.Pubkey, nil } + +func TestPrecompileInitializeAccount_Execute(t *testing.T) { + testDenom := "usei" + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + + err := k.BankKeeper().MintCoins( + ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000)))) + require.Nil(t, err) + + userPrivateKey := testkeeper.MockPrivateKey() + userAddr, userEVMAddr := testkeeper.PrivateKeyToAddresses(userPrivateKey) + k.SetAddressMapping(ctx, userAddr, userEVMAddr) + + privHex := hex.EncodeToString(userPrivateKey.Bytes()) + userKey, _ := crypto.HexToECDSA(privHex) + + statedb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: statedb, + TxContext: vm.TxContext{Origin: userEVMAddr}, + } + + p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + require.Nil(t, err) + + initAccount, err := cttypes.NewInitializeAccount( + userAddr.String(), + testDenom, + *userKey) + + iaProto := cttypes.NewMsgInitializeAccountProto(initAccount) + pendingBalanceLo, _ := iaProto.PendingBalanceLo.Marshal() + pendingBalanceHi, _ := iaProto.PendingBalanceHi.Marshal() + availableBalance, _ := iaProto.AvailableBalance.Marshal() + proofs, _ := iaProto.Proofs.Marshal() + + InitializeAccountMethod, _ := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).InitializeAccountID) + expectedTrueResponse, _ := InitializeAccountMethod.Outputs.Pack(true) + + type inputs struct { + UserAddress string + Denom string + PublicKey []byte + DecryptableBalance string + PendingBalanceLo []byte + PendingBalanceHi []byte + AvailableBalance []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 of input is valid", + wantRet: expectedTrueResponse, + wantRemainingGas: 0x1e47ed, + wantErr: false, + }, + { + name: "precompile should return error if address is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.UserAddress = "" + return in + }}, + wantErr: true, + wantErrMsg: "invalid address : empty address string is not allowed", + }, + { + 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 decryptableBalance 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 pendingBalanceLo is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.PendingBalanceLo = []byte("invalid") + return in + }, + }, + wantErr: true, + wantErrMsg: "unexpected EOF", + }, + { + name: "precompile should return error if pendingBalanceHi is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.PendingBalanceHi = []byte("invalid") + return in + }, + }, + wantErr: true, + wantErrMsg: "unexpected EOF", + }, + { + 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 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", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + in := inputs{ + UserAddress: userAddr.String(), + Denom: testDenom, + PublicKey: iaProto.PublicKey, + DecryptableBalance: iaProto.DecryptableBalance, + PendingBalanceLo: pendingBalanceLo, + PendingBalanceHi: pendingBalanceHi, + AvailableBalance: availableBalance, + proofs: proofs, + } + if tt.args.setUp != nil { + in = tt.args.setUp(in) + } + + inputArgs, err := InitializeAccountMethod.Inputs.Pack( + in.UserAddress, + in.Denom, + in.PublicKey, + in.DecryptableBalance, + in.PendingBalanceLo, + in.PendingBalanceHi, + in.AvailableBalance, + in.proofs) + + require.Nil(t, err) + + resp, remainingGas, err := p.RunAndCalculateGas( + &evm, + userEVMAddr, + userEVMAddr, + append(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).InitializeAccountID, 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 7c74fbf7c3e92ffd0d0d3e5ad627211b41f7d0ef Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 14 Jan 2025 09:05:54 -0800 Subject: [PATCH 3/5] Basic account precompile query code and test --- app/app.go | 1 + precompiles/common/expected_keepers.go | 4 + precompiles/confidentialtransfers/CT.sol | 15 ++ precompiles/confidentialtransfers/abi.json | 2 +- precompiles/confidentialtransfers/ct.go | 107 ++++++++++++- precompiles/confidentialtransfers/ct_test.go | 156 +++++++++++++++++-- precompiles/setup.go | 5 +- 7 files changed, 265 insertions(+), 25 deletions(-) diff --git a/app/app.go b/app/app.go index 997987efd..35c09f986 100644 --- a/app/app.go +++ b/app/app.go @@ -722,6 +722,7 @@ func New( app.IBCKeeper.ConnectionKeeper, app.IBCKeeper.ChannelKeeper, app.AccountKeeper, + app.ConfidentialTransfersKeeper, ctkeeper.NewMsgServerImpl(app.ConfidentialTransfersKeeper), ); err != nil { panic(err) diff --git a/precompiles/common/expected_keepers.go b/precompiles/common/expected_keepers.go index 80b8a8c49..f8bf2d36e 100644 --- a/precompiles/common/expected_keepers.go +++ b/precompiles/common/expected_keepers.go @@ -136,3 +136,7 @@ type ConfidentialTransfersKeeper interface { InitializeAccount(context.Context, *cttypes.MsgInitializeAccount) (*cttypes.MsgInitializeAccountResponse, error) Transfer(goCtx context.Context, req *cttypes.MsgTransfer) (*cttypes.MsgTransferResponse, error) } + +type ConfidentialTransfersViewKeeper interface { + GetAccount(ctx sdk.Context, address string, denom string) (cttypes.Account, bool) +} diff --git a/precompiles/confidentialtransfers/CT.sol b/precompiles/confidentialtransfers/CT.sol index 5c3823bc9..28fb58c28 100644 --- a/precompiles/confidentialtransfers/CT.sol +++ b/precompiles/confidentialtransfers/CT.sol @@ -57,4 +57,19 @@ interface ICT { string denom, uint64 amount ) external returns (bool success); + + // Queries + function account( + string addr, + string denom + ) external view returns (CtAccount account); + + struct CtAccount { + bytes publicKey; // serialized public key + bytes pendingBalanceLo; // lo bits of the pending balance + bytes pendingBalanceHi; // hi bits of the pending balance + uint32 pendingBalanceCreditCounter; + bytes availableBalance; // elgamal encoded balance + string decryptableAvailableBalance; // aes encoded balance + } } diff --git a/precompiles/confidentialtransfers/abi.json b/precompiles/confidentialtransfers/abi.json index aaee2ade9..a5be965d1 100644 --- a/precompiles/confidentialtransfers/abi.json +++ b/precompiles/confidentialtransfers/abi.json @@ -1 +1 @@ -[{"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":"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":"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":"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":"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":"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":"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":"addr","type":"string"},{"internalType":"string","name":"denom","type":"string"}],"name":"account","outputs":[{"components":[{"internalType":"bytes","name":"publicKey","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceLo","type":"bytes"},{"internalType":"bytes","name":"pendingBalanceHi","type":"bytes"},{"internalType":"uint32","name":"pendingBalanceCreditCounter","type":"uint32"},{"internalType":"bytes","name":"availableBalance","type":"bytes"},{"internalType":"string","name":"decryptableAvailableBalance","type":"string"}],"internalType":"struct CtAccount","name":"ctAccount","type":"tuple"}],"stateMutability":"view","type":"function"}] diff --git a/precompiles/confidentialtransfers/ct.go b/precompiles/confidentialtransfers/ct.go index 681ef5996..01e005ec2 100644 --- a/precompiles/confidentialtransfers/ct.go +++ b/precompiles/confidentialtransfers/ct.go @@ -19,6 +19,7 @@ const ( DepositMethod = "deposit" TransferMethod = "transfer" TransferWithAuditorsMethod = "transferWithAuditors" + AccountMethod = "account" ) const ( @@ -31,23 +32,30 @@ const ( var f embed.FS type PrecompileExecutor struct { - evmKeeper pcommon.EVMKeeper - ctKeeper pcommon.ConfidentialTransfersKeeper - address common.Address + evmKeeper pcommon.EVMKeeper + ctViewKeeper pcommon.ConfidentialTransfersViewKeeper + ctKeeper pcommon.ConfidentialTransfersKeeper + address common.Address InitializeAccountID []byte DepositID []byte TransferID []byte TransferWithAuditorsID []byte + AccountID []byte } -func NewPrecompile(ctkeeper pcommon.ConfidentialTransfersKeeper, evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { +func NewPrecompile( + ctViewKeeper pcommon.ConfidentialTransfersViewKeeper, + ctKeeper pcommon.ConfidentialTransfersKeeper, + evmKeeper pcommon.EVMKeeper) (*pcommon.DynamicGasPrecompile, error) { + newAbi := pcommon.MustGetABI(f, "abi.json") p := &PrecompileExecutor{ - evmKeeper: evmKeeper, - ctKeeper: ctkeeper, - address: common.HexToAddress(CtAddress), + evmKeeper: evmKeeper, + ctViewKeeper: ctViewKeeper, + ctKeeper: ctKeeper, + address: common.HexToAddress(CtAddress), } for name, m := range newAbi.Methods { @@ -60,6 +68,8 @@ func NewPrecompile(ctkeeper pcommon.ConfidentialTransfersKeeper, evmKeeper pcomm p.TransferID = m.ID case TransferWithAuditorsMethod: p.TransferWithAuditorsID = m.ID + case AccountMethod: + p.AccountID = m.ID } } @@ -94,6 +104,8 @@ 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 AccountMethod: + return p.account(ctx, method, caller, args) } return } @@ -530,3 +542,84 @@ func (p PrecompileExecutor) deposit(ctx sdk.Context, method *abi.Method, caller remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) return } + +type CtAccount struct { + PublicKey []byte + PendingBalanceLo []byte + PendingBalanceHi []byte + PendingBalanceCreditCounter uint32 + AvailableBalance []byte + DecryptableAvailableBalance string +} + +func (p PrecompileExecutor) account(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 + } + + addrString, ok := (args[0]).(string) + if !ok || addrString == "" { + rerr = errors.New("invalid address") + return + } + + seiAddr, err := p.getValidSeiAddressFromString(ctx, addrString) + if err != nil { + rerr = err + return + } + + denom, ok := args[1].(string) + if !ok || denom == "" { + rerr = errors.New("invalid denom") + return + } + + account, found := p.ctViewKeeper.GetAccount(ctx, seiAddr.String(), denom) + if !found { + rerr = errors.New("account not found") + return + } + + accountProto := cttypes.NewCtAccount(&account) + pendingBalanceLo, err := accountProto.PendingBalanceLo.Marshal() + if err != nil { + rerr = err + return + } + + pendingBalanceHi, err := accountProto.PendingBalanceHi.Marshal() + if err != nil { + rerr = err + return + } + + availableBalance, err := accountProto.AvailableBalance.Marshal() + if err != nil { + rerr = err + return + } + + ctAccount := &CtAccount{ + PublicKey: accountProto.PublicKey, + PendingBalanceLo: pendingBalanceLo, + PendingBalanceHi: pendingBalanceHi, + PendingBalanceCreditCounter: accountProto.PendingBalanceCreditCounter, + AvailableBalance: availableBalance, + DecryptableAvailableBalance: accountProto.DecryptableAvailableBalance, + } + + ret, rerr = method.Outputs.Pack(ctAccount) + remainingGas = pcommon.GetRemainingGas(ctx, p.evmKeeper) + return +} diff --git a/precompiles/confidentialtransfers/ct_test.go b/precompiles/confidentialtransfers/ct_test.go index 31302fe44..b7ffc8bcb 100644 --- a/precompiles/confidentialtransfers/ct_test.go +++ b/precompiles/confidentialtransfers/ct_test.go @@ -25,7 +25,7 @@ import ( ) func TestPrecompileTransfer_Execute(t *testing.T) { - transferPrecompile, _ := confidentialtransfers.NewPrecompile(nil, nil) + transferPrecompile, _ := confidentialtransfers.NewPrecompile(nil, nil, nil) transferMethod, _ := transferPrecompile.ABI.MethodById(transferPrecompile.GetExecutor().(*confidentialtransfers.PrecompileExecutor).TransferID) expectedTrueResponse, _ := transferMethod.Outputs.Pack(true) var senderAddr, receiverAddr, otherSenderAddr sdk.AccAddress @@ -244,9 +244,11 @@ func TestPrecompileTransfer_Execute(t *testing.T) { err = ctKeeper.SetAccount(ctx, senderAddr.String(), testDenom, senderAccount) require.NoError(t, err) - receiverAddr, receiverEVMAddr, receiverPubKey, err = setUpCtAccount(k, ctx, testDenom) + var account *cttypes.Account + receiverAddr, receiverEVMAddr, account, err = setUpCtAccount(k, ctx, testDenom) require.NoError(t, err) - p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + receiverPubKey = &account.PublicKey + p, err := confidentialtransfers.NewPrecompile(k.CtKeeper(), ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) require.Nil(t, err) statedb := state.NewDBImpl(ctx, k, true) evm := vm.EVM{ @@ -329,7 +331,7 @@ func TestPrecompileTransfer_Execute(t *testing.T) { func TestPrecompileTransferWithAuditor_Execute(t *testing.T) { var auditorOneAddr, auditorTwoAddr sdk.AccAddress - transferPrecompile, _ := confidentialtransfers.NewPrecompile(nil, nil) + transferPrecompile, _ := confidentialtransfers.NewPrecompile(nil, nil, nil) transferMethod, _ := transferPrecompile.ABI.MethodById(transferPrecompile.GetExecutor().(*confidentialtransfers.PrecompileExecutor).TransferID) expectedTrueResponse, _ := transferMethod.Outputs.Pack(true) @@ -516,9 +518,11 @@ func TestPrecompileTransferWithAuditor_Execute(t *testing.T) { err = ctKeeper.SetAccount(ctx, senderAddr.String(), testDenom, senderAccount) require.NoError(t, err) - receiverAddr, receiverEVMAddr, receiverPubKey, err := setUpCtAccount(k, ctx, testDenom) + var receiverAccount *cttypes.Account + receiverAddr, receiverEVMAddr, receiverAccount, err := setUpCtAccount(k, ctx, testDenom) + require.NoError(t, err) - p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + p, err := confidentialtransfers.NewPrecompile(k.CtKeeper(), ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) require.Nil(t, err) statedb := state.NewDBImpl(ctx, k, true) evm := vm.EVM{ @@ -526,8 +530,13 @@ func TestPrecompileTransferWithAuditor_Execute(t *testing.T) { TxContext: vm.TxContext{Origin: senderEVMAddr}, } var auditorOenPubKey, auditorTwoPubKey *curves.Point - auditorOneAddr, _, auditorOenPubKey, err = setUpCtAccount(k, ctx, testDenom) - auditorTwoAddr, _, auditorTwoPubKey, err = setUpCtAccount(k, ctx, testDenom) + var auditorOneAccount, auditorTwoAccount *cttypes.Account + auditorOneAddr, _, auditorOneAccount, err = setUpCtAccount(k, ctx, testDenom) + require.NoError(t, err) + auditorTwoAddr, _, auditorTwoAccount, err = setUpCtAccount(k, ctx, testDenom) + require.NoError(t, err) + auditorOenPubKey = &auditorOneAccount.PublicKey + auditorTwoPubKey = &auditorTwoAccount.PublicKey transferWithAuditorsMethod, err := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).TransferWithAuditorsID) @@ -552,7 +561,7 @@ func TestPrecompileTransferWithAuditor_Execute(t *testing.T) { senderAccount.DecryptableAvailableBalance, senderAccount.AvailableBalance, 100, - receiverPubKey, + &receiverAccount.PublicKey, auditorsInput) trProto := cttypes.NewMsgTransferProto(tr) @@ -639,7 +648,7 @@ func TestPrecompileTransferWithAuditor_Execute(t *testing.T) { } } -func setUpCtAccount(k *evmkeeper.Keeper, ctx sdk.Context, testDenom string) (sdk.AccAddress, common.Address, *curves.Point, error) { +func setUpCtAccount(k *evmkeeper.Keeper, ctx sdk.Context, testDenom string) (sdk.AccAddress, common.Address, *cttypes.Account, error) { privateKey := testkeeper.MockPrivateKey() addr, EVMAddr := testkeeper.PrivateKeyToAddresses(privateKey) k.SetAddressMapping(ctx, addr, EVMAddr) @@ -649,7 +658,7 @@ func setUpCtAccount(k *evmkeeper.Keeper, ctx sdk.Context, testDenom string) (sdk if err != nil { return nil, common.Address{}, nil, err } - account := cttypes.Account{ + account := &cttypes.Account{ PublicKey: *initializeAccount.Pubkey, PendingBalanceLo: initializeAccount.PendingBalanceLo, PendingBalanceHi: initializeAccount.PendingBalanceHi, @@ -657,11 +666,11 @@ func setUpCtAccount(k *evmkeeper.Keeper, ctx sdk.Context, testDenom string) (sdk AvailableBalance: initializeAccount.AvailableBalance, DecryptableAvailableBalance: initializeAccount.DecryptableBalance, } - err = k.CtKeeper().SetAccount(ctx, addr.String(), testDenom, account) + err = k.CtKeeper().SetAccount(ctx, addr.String(), testDenom, *account) if err != nil { return nil, common.Address{}, nil, err } - return addr, EVMAddr, initializeAccount.Pubkey, nil + return addr, EVMAddr, account, nil } func TestPrecompileInitializeAccount_Execute(t *testing.T) { @@ -692,7 +701,7 @@ func TestPrecompileInitializeAccount_Execute(t *testing.T) { TxContext: vm.TxContext{Origin: userEVMAddr}, } - p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + p, err := confidentialtransfers.NewPrecompile(k.CtKeeper(), ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) require.Nil(t, err) initAccount, err := cttypes.NewInitializeAccount( @@ -939,7 +948,7 @@ func TestPrecompileDeposit_Execute(t *testing.T) { require.NoError(t, err) - p, err := confidentialtransfers.NewPrecompile(ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + p, err := confidentialtransfers.NewPrecompile(k.CtKeeper(), ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) require.Nil(t, err) DepositMethod, _ := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).DepositID) expectedTrueResponse, _ := DepositMethod.Outputs.Pack(true) @@ -1044,3 +1053,120 @@ func TestPrecompileDeposit_Execute(t *testing.T) { }) } } + +func TestPrecompileAccount_Execute(t *testing.T) { + testDenom := "usei" + testApp := testkeeper.EVMTestApp + ctx := testApp.NewContext(false, tmtypes.Header{}).WithBlockHeight(2) + k := &testApp.EvmKeeper + + userAddr, userEVMAddr, account, _ := setUpCtAccount(k, ctx, testDenom) + + k.SetAddressMapping(ctx, userAddr, userEVMAddr) + + //notAssociatedUserPrivateKey := testkeeper.MockPrivateKey() + //_, notAssociatedEVMAddr := testkeeper.PrivateKeyToAddresses(notAssociatedUserPrivateKey) + + otherUserPrivateKey := testkeeper.MockPrivateKey() + otherUserAddr, otherUserEVMAddr := testkeeper.PrivateKeyToAddresses(otherUserPrivateKey) + k.SetAddressMapping(ctx, otherUserAddr, otherUserEVMAddr) + + 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, userAddr, sdk.NewCoins(sdk.NewCoin(testDenom, sdk.NewInt(10000000)))) + require.Nil(t, err) + + statedb := state.NewDBImpl(ctx, k, true) + evm := vm.EVM{ + StateDB: statedb, + TxContext: vm.TxContext{Origin: userEVMAddr}, + } + + require.NoError(t, err) + + p, err := confidentialtransfers.NewPrecompile(k.CtKeeper(), ctkeeper.NewMsgServerImpl(k.CtKeeper()), k) + require.Nil(t, err) + AccountMethod, _ := p.ABI.MethodById(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).AccountID) + accountProto := cttypes.NewCtAccount(account) + pendingBalanceLo, _ := accountProto.PendingBalanceLo.Marshal() + pendingBalanceHi, _ := accountProto.PendingBalanceHi.Marshal() + availableBalance, _ := accountProto.AvailableBalance.Marshal() + + ctAccount := &confidentialtransfers.CtAccount{ + PublicKey: accountProto.PublicKey, + PendingBalanceLo: pendingBalanceLo, + PendingBalanceHi: pendingBalanceHi, + PendingBalanceCreditCounter: accountProto.PendingBalanceCreditCounter, + AvailableBalance: availableBalance, + DecryptableAvailableBalance: accountProto.DecryptableAvailableBalance, + } + + expectedResponse, _ := AccountMethod.Outputs.Pack(ctAccount) + + type inputs struct { + Account string + Denom string + } + + type args struct { + caller common.Address + 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 abi-encoded account if input is valid", + args: args{ + caller: userEVMAddr, + }, + wantRet: expectedResponse, + wantRemainingGas: 0x1e7908, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + in := inputs{ + Account: userAddr.String(), + Denom: testDenom, + } + if tt.args.setUp != nil { + in = tt.args.setUp(in) + } + + inputArgs, err := AccountMethod.Inputs.Pack(in.Account, in.Denom) + require.Nil(t, err) + + resp, remainingGas, err := p.RunAndCalculateGas( + &evm, + tt.args.caller, + common.Address{}, + append(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).AccountID, 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/precompiles/setup.go b/precompiles/setup.go index e20ec2b71..a079e435c 100644 --- a/precompiles/setup.go +++ b/precompiles/setup.go @@ -57,6 +57,7 @@ func InitializePrecompiles( connectionKeeper common.ConnectionKeeper, channelKeeper common.ChannelKeeper, accountKeeper common.AccountKeeper, + ctViewKeeper common.ConfidentialTransfersViewKeeper, ctKeeper common.ConfidentialTransfersKeeper, ) error { SetupMtx.Lock() @@ -108,7 +109,7 @@ func InitializePrecompiles( if err != nil { return err } - ctpr, err := confidentialtransfers.NewPrecompile(ctKeeper, evmKeeper) + ctpr, err := confidentialtransfers.NewPrecompile(ctViewKeeper, ctKeeper, evmKeeper) if err != nil { return err } @@ -145,7 +146,7 @@ func InitializePrecompiles( func GetPrecompileInfo(name string) PrecompileInfo { if !Initialized { // Precompile Info does not require any keeper state - _ = InitializePrecompiles(true, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) + _ = InitializePrecompiles(true, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) } i, ok := PrecompileNamesToInfo[name] if !ok { From 31f0d8bdb63bcec37682eb198860bedb24fe64b4 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 14 Jan 2025 10:50:57 -0800 Subject: [PATCH 4/5] unit tests --- precompiles/confidentialtransfers/ct.go | 4 +- precompiles/confidentialtransfers/ct_test.go | 113 ++++++++++++++++++- 2 files changed, 110 insertions(+), 7 deletions(-) diff --git a/precompiles/confidentialtransfers/ct.go b/precompiles/confidentialtransfers/ct.go index 01e005ec2..19a061b12 100644 --- a/precompiles/confidentialtransfers/ct.go +++ b/precompiles/confidentialtransfers/ct.go @@ -105,7 +105,7 @@ func (p PrecompileExecutor) Execute(ctx sdk.Context, method *abi.Method, caller } return p.transferWithAuditors(ctx, method, caller, args) case AccountMethod: - return p.account(ctx, method, caller, args) + return p.account(ctx, method, args) } return } @@ -552,7 +552,7 @@ type CtAccount struct { DecryptableAvailableBalance string } -func (p PrecompileExecutor) account(ctx sdk.Context, method *abi.Method, caller common.Address, args []interface{}) (ret []byte, remainingGas uint64, rerr error) { +func (p PrecompileExecutor) account(ctx sdk.Context, method *abi.Method, args []interface{}) (ret []byte, remainingGas uint64, rerr error) { defer func() { if err := recover(); err != nil { ret = nil diff --git a/precompiles/confidentialtransfers/ct_test.go b/precompiles/confidentialtransfers/ct_test.go index b7ffc8bcb..b46f4f078 100644 --- a/precompiles/confidentialtransfers/ct_test.go +++ b/precompiles/confidentialtransfers/ct_test.go @@ -1064,8 +1064,8 @@ func TestPrecompileAccount_Execute(t *testing.T) { k.SetAddressMapping(ctx, userAddr, userEVMAddr) - //notAssociatedUserPrivateKey := testkeeper.MockPrivateKey() - //_, notAssociatedEVMAddr := testkeeper.PrivateKeyToAddresses(notAssociatedUserPrivateKey) + notAssociatedUserPrivateKey := testkeeper.MockPrivateKey() + _, notAssociatedEVMAddr := testkeeper.PrivateKeyToAddresses(notAssociatedUserPrivateKey) otherUserPrivateKey := testkeeper.MockPrivateKey() otherUserAddr, otherUserEVMAddr := testkeeper.PrivateKeyToAddresses(otherUserPrivateKey) @@ -1111,7 +1111,6 @@ func TestPrecompileAccount_Execute(t *testing.T) { } type args struct { - caller common.Address isReadOnly bool isFromDelegateCall bool value *big.Int @@ -1128,12 +1127,116 @@ func TestPrecompileAccount_Execute(t *testing.T) { { name: "precompile should return abi-encoded account if input is valid", args: args{ - caller: userEVMAddr, + isReadOnly: true, }, wantRet: expectedResponse, wantRemainingGas: 0x1e7908, wantErr: false, }, + { + name: "precompile should return abi-encoded account if input is valid and EVM address is used", + args: args{ + isReadOnly: true, + setUp: func(in inputs) inputs { + in.Account = userEVMAddr.String() + return in + }, + }, + wantRet: expectedResponse, + wantRemainingGas: 0x1e74a5, + wantErr: false, + }, + { + name: "precompile should return abi-encoded account if input is valid and the call is not read-only", + args: args{ + isReadOnly: false, + }, + wantRet: expectedResponse, + wantRemainingGas: 0x1e7908, + wantErr: false, + }, + { + name: "precompile should return error if account not found", + args: args{ + isReadOnly: true, + setUp: func(in inputs) inputs { + in.Account = otherUserAddr.String() + return in + }, + }, + wantErr: true, + wantErrMsg: "account not found", + }, + { + name: "precompile should return error if address is empty", + args: args{ + isReadOnly: true, + setUp: func(in inputs) inputs { + in.Account = "" + return in + }, + }, + wantErr: true, + wantErrMsg: "invalid address", + }, + { + name: "precompile should return error if address is invalid", + args: args{ + setUp: func(in inputs) inputs { + in.Account = "invalid" + return in + }, + }, + wantErr: true, + wantErrMsg: "invalid address invalid: decoding bech32 failed: invalid bech32 string length 7", + }, + { + name: "precompile should return error if address is not associated", + args: args{ + setUp: func(in inputs) inputs { + in.Account = notAssociatedEVMAddr.String() + return in + }, + }, + wantErr: true, + wantErrMsg: fmt.Sprintf("address %s is not associated", notAssociatedEVMAddr.String()), + }, + { + name: "precompile should return error if denom is empty", + args: args{ + isReadOnly: true, + setUp: func(in inputs) inputs { + in.Denom = "" + return in + }, + }, + wantErr: true, + wantErrMsg: "invalid denom", + }, + { + name: "precompile should return error if denom is invalid", + args: args{ + isReadOnly: true, + setUp: func(in inputs) inputs { + in.Denom = "invalid" + return in + }, + }, + wantErr: true, + wantErrMsg: "account not found", + }, + { + name: "precompile should return error if called from delegate call", + args: args{isFromDelegateCall: true}, + wantErr: true, + wantErrMsg: "cannot delegatecall ct", + }, + { + 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 { t.Run(tt.name, func(t *testing.T) { @@ -1150,7 +1253,7 @@ func TestPrecompileAccount_Execute(t *testing.T) { resp, remainingGas, err := p.RunAndCalculateGas( &evm, - tt.args.caller, + common.Address{}, common.Address{}, append(p.GetExecutor().(*confidentialtransfers.PrecompileExecutor).AccountID, inputArgs...), 2000000, From 53217b2e41b4234a9e138c49f4fcdfc03e041a48 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 14 Jan 2025 15:42:39 -0800 Subject: [PATCH 5/5] integration test --- .../ct_precompile_queries.yaml | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 integration_test/confidential_transfers_module/ct_precompile_queries.yaml diff --git a/integration_test/confidential_transfers_module/ct_precompile_queries.yaml b/integration_test/confidential_transfers_module/ct_precompile_queries.yaml new file mode 100644 index 000000000..6ee9a2b7f --- /dev/null +++ b/integration_test/confidential_transfers_module/ct_precompile_queries.yaml @@ -0,0 +1,53 @@ +# Tests for querying ct account via precompile. These tests depend on initialize_account_tests running and passing. +- name: Test querying confidential token account via precompile + inputs: + # Setup test account + - cmd: printf "12345678\n" | seid keys add ctquerytest + - cmd: printf "12345678\n" | seid keys show -a admin + env: ADMIN_ADDR + - cmd: printf "12345678\n" | seid keys show -a ctquerytest + env: TEST_ADDR + - cmd: printf "12345678\n" | seid tx bank send $ADMIN_ADDR $TEST_ADDR 100000000uatom -b block --fees 2000usei --chain-id sei -y --output json | jq -r ".code" + - cmd: printf "12345678\n" | seid tx bank send $ADMIN_ADDR $TEST_ADDR 1sei -b block --fees 2000usei --chain-id sei -y --output json | jq -r ".code" + + # Initialize confidential token account for denom uatom for admin + - cmd: printf "12345678\n" | seid tx ct init-account uatom --from ctquerytest --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 500000uatom --from ctquerytest --fees 4000usei --chain-id sei -b block -y --output json | jq -r ".code" + env: DEPOSIT_CODE + + # Query the account state + - cmd: printf "12345678\n" | seid q ct account uatom $TEST_ADDR --output json + env: ACCOUNT_STATE + - cmd: echo $ACCOUNT_STATE | jq -r ".pending_balance_credit_counter" + env: PENDING_BALANCE_COUNTER + - cmd: echo $ACCOUNT_STATE | jq -r ".decryptable_available_balance" + env: AVAILABLE_BALANCE_CIPHER + + # Query the account to verify the new account state via precompile + - cmd: cast call 0x0000000000000000000000000000000000001010 "account(string,string)((bytes,bytes,bytes,uint32,bytes,string))" $TEST_ADDR uatom + env: ACCOUNT_STATE_PRECOMPILE + - cmd: echo $ACCOUNT_STATE_PRECOMPILE | jq -R -r "split(\", \")[3]" + env: PENDING_BALANCE_COUNTER_PRECOMPILE + - cmd: echo $ACCOUNT_STATE_PRECOMPILE| jq -R -r "split(\", \")[5]"| sed -e s/\"\)//g -e s/\"// + env: AVAILABLE_BALANCE_CIPHER_PRECOMPILE + 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 pending balance counter is the same in both queries + - type: eval + expr: PENDING_BALANCE_COUNTER == PENDING_BALANCE_COUNTER_PRECOMPILE + + # Verify that the available balance is the same in both queries + - type: eval + expr: AVAILABLE_BALANCE_CIPHER == AVAILABLE_BALANCE_CIPHER_PRECOMPILE + +