From 90a86d2ae8b01297cd1154c84b5f47312c7fd2b6 Mon Sep 17 00:00:00 2001 From: Francisco de Borja Aranda Castillejo Date: Mon, 7 Oct 2024 17:33:37 +0200 Subject: [PATCH] feat: add lockZRC20 capability to fungible keeper --- precompiles/bank/method_deposit.go | 75 ++++++-------- precompiles/bank/method_test.go | 12 +-- x/fungible/keeper/lock_zrc20_bank.go | 144 +++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 55 deletions(-) create mode 100644 x/fungible/keeper/lock_zrc20_bank.go diff --git a/precompiles/bank/method_deposit.go b/precompiles/bank/method_deposit.go index 2f57944723..9eba3c94e4 100644 --- a/precompiles/bank/method_deposit.go +++ b/precompiles/bank/method_deposit.go @@ -107,33 +107,33 @@ func (c *Contract) deposit( // Check for enough bank's allowance. // function allowance(address owner, address spender) public view virtual override returns (uint256) - resAllowance, err := c.CallContract( - ctx, - &c.fungibleKeeper, - c.zrc20ABI, - zrc20Addr, - "allowance", - []interface{}{caller, ContractAddress}, - ) - if err != nil { - return nil, &ptypes.ErrUnexpected{ - When: "allowance", - Got: err.Error(), - } - } - - allowance, ok := resAllowance[0].(*big.Int) - if !ok { - return nil, &ptypes.ErrUnexpected{ - Got: "ZRC20 allowance returned an unexpected type", - } - } - - if allowance.Cmp(amount) < 0 || allowance.Cmp(big.NewInt(0)) <= 0 { - return nil, &ptypes.ErrInvalidAmount{ - Got: allowance.String(), - } - } + // resAllowance, err := c.CallContract( + // ctx, + // &c.fungibleKeeper, + // c.zrc20ABI, + // zrc20Addr, + // "allowance", + // []interface{}{caller, ContractAddress}, + // ) + // if err != nil { + // return nil, &ptypes.ErrUnexpected{ + // When: "allowance", + // Got: err.Error(), + // } + // } + + // allowance, ok := resAllowance[0].(*big.Int) + // if !ok { + // return nil, &ptypes.ErrUnexpected{ + // Got: "ZRC20 allowance returned an unexpected type", + // } + // } + + // if allowance.Cmp(amount) < 0 || allowance.Cmp(big.NewInt(0)) <= 0 { + // return nil, &ptypes.ErrInvalidAmount{ + // Got: allowance.String(), + // } + // } // The process of creating a new cosmos coin is: // - Generate the new coin denom using ZRC20 address, @@ -146,30 +146,13 @@ func (c *Contract) deposit( } // 2. Effect: subtract balance. - // function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) - resTransferFrom, err := c.CallContract( - ctx, - &c.fungibleKeeper, - c.zrc20ABI, - zrc20Addr, - "transferFrom", - []interface{}{caller, ContractAddress, amount}, - ) - if err != nil { + if err := c.fungibleKeeper.LockZRC20InBank(ctx, c.zrc20ABI, zrc20Addr, caller, amount); err != nil { return nil, &ptypes.ErrUnexpected{ - When: "transferFrom", + When: "LockZRC20InBank", Got: err.Error(), } } - transferred, ok := resTransferFrom[0].(bool) - if !ok || !transferred { - return nil, &ptypes.ErrUnexpected{ - When: "transferFrom", - Got: "transaction not successful", - } - } - // 3. Interactions: create cosmos coin and send. if err := c.bankKeeper.MintCoins(ctx, types.ModuleName, coinSet); err != nil { return nil, &ptypes.ErrUnexpected{ diff --git a/precompiles/bank/method_test.go b/precompiles/bank/method_test.go index 912c41b40c..9ce21732ae 100644 --- a/precompiles/bank/method_test.go +++ b/precompiles/bank/method_test.go @@ -137,8 +137,8 @@ func Test_Methods(t *testing.T) { require.Error(t, err) require.ErrorAs( t, - ptypes.ErrInvalidAmount{ - Got: "0", + ptypes.ErrInvalidCoin{ + Empty: true, }, err, ) @@ -173,13 +173,7 @@ func Test_Methods(t *testing.T) { success, err := ts.bankContract.Run(ts.mockEVM, ts.mockVMContract, false) require.Error(t, err) - require.ErrorAs( - t, - ptypes.ErrInvalidAmount{ - Got: "500", - }, - err, - ) + require.Contains(t, err.Error(), "unexpected error in LockZRC20InBank: invalid allowance, got: 500") res, err := ts.bankABI.Methods[DepositMethodName].Outputs.Unpack(success) require.NoError(t, err) diff --git a/x/fungible/keeper/lock_zrc20_bank.go b/x/fungible/keeper/lock_zrc20_bank.go new file mode 100644 index 0000000000..e2f745d544 --- /dev/null +++ b/x/fungible/keeper/lock_zrc20_bank.go @@ -0,0 +1,144 @@ +package keeper + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +const ( + transferFrom = "transferFrom" + allowance = "allowance" +) + +var ( + bankAddress common.Address = common.HexToAddress("0x0000000000000000000000000000000000000067") + zeroAddress common.Address = common.HexToAddress("0x0000000000000000000000000000000000000000") +) + +// LockZRC20InBank locks ZRC20 tokens in the bank contract. +// The caller must have approved the bank contract to spend the amount of ZRC20 tokens. +func (k Keeper) LockZRC20InBank( + ctx sdk.Context, + zrc20ABI *abi.ABI, + zrc20Address, from common.Address, + amount *big.Int, +) error { + accAddress := sdk.AccAddress(bankAddress.Bytes()) + if k.GetAuthKeeper().GetAccount(ctx, accAddress) == nil { + k.GetAuthKeeper().SetAccount(ctx, authtypes.NewBaseAccount(accAddress, nil, 0, 0)) + } + + if amount.Sign() <= 0 { + return fmt.Errorf("amount must be positive") + } + + if from == zeroAddress { + return fmt.Errorf("from address cannot be zero") + } + + if zrc20Address == zeroAddress { + return fmt.Errorf("zrc20 address cannot be zero") + } + + if err := k.checkBankAllowance(ctx, zrc20ABI, from, zrc20Address, amount); err != nil { + return err + } + + args := []interface{}{from, bankAddress, amount} + res, err := k.CallEVM( + ctx, + *zrc20ABI, + bankAddress, + zrc20Address, + big.NewInt(0), + nil, + true, + true, + transferFrom, + args..., + ) + if err != nil { + return err + } + + if res.VmError != "" { + return fmt.Errorf("%s", res.VmError) + } + + ret, err := zrc20ABI.Methods[transferFrom].Outputs.Unpack(res.Ret) + if err != nil { + return err + } + + transferred, ok := ret[0].(bool) + if !ok { + return fmt.Errorf("transferFrom returned an unexpected value") + } + + if !transferred { + return fmt.Errorf("transferFrom not successful") + } + + return nil +} + +func (k Keeper) checkBankAllowance( + ctx sdk.Context, + zrc20ABI *abi.ABI, + from, zrc20Address common.Address, + amount *big.Int, +) error { + if amount.Sign() <= 0 { + return fmt.Errorf("amount must be positive") + } + + if from == zeroAddress { + return fmt.Errorf("from address cannot be zero") + } + + if zrc20Address == zeroAddress { + return fmt.Errorf("zrc20 address cannot be zero") + } + + args := []interface{}{from, bankAddress} + res, err := k.CallEVM( + ctx, + *zrc20ABI, + bankAddress, + zrc20Address, + big.NewInt(0), + nil, + true, + true, + allowance, + args..., + ) + if err != nil { + return err + } + + if res.VmError != "" { + return fmt.Errorf("%s", res.VmError) + } + + ret, err := zrc20ABI.Methods[allowance].Outputs.Unpack(res.Ret) + if err != nil { + return err + } + + allowance, ok := ret[0].(*big.Int) + if !ok { + return fmt.Errorf("ZRC20 allowance returned an unexpected type") + } + + if allowance.Cmp(amount) < 0 || allowance.Cmp(big.NewInt(0)) <= 0 { + return fmt.Errorf("invalid allowance, got: %s", allowance.String()) + } + + return nil +}