diff --git a/precompiles/bank/method_deposit.go b/precompiles/bank/method_deposit.go index 9eba3c94e4..37fdd53da4 100644 --- a/precompiles/bank/method_deposit.go +++ b/precompiles/bank/method_deposit.go @@ -59,22 +59,6 @@ func (c *Contract) deposit( return nil, err } - // Safety check: token has to be a valid whitelisted ZRC20 and not be paused. - t, found := c.fungibleKeeper.GetForeignCoins(ctx, zrc20Addr.String()) - if !found { - return nil, &ptypes.ErrInvalidToken{ - Got: zrc20Addr.String(), - Reason: "token is not a whitelisted ZRC20", - } - } - - if t.Paused { - return nil, &ptypes.ErrInvalidToken{ - Got: zrc20Addr.String(), - Reason: "token is paused", - } - } - // Check for enough balance. // function balanceOf(address account) public view virtual override returns (uint256) resBalanceOf, err := c.CallContract( @@ -105,36 +89,6 @@ 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(), - // } - // } - // The process of creating a new cosmos coin is: // - Generate the new coin denom using ZRC20 address, // this way we map ZRC20 addresses to cosmos denoms "zevm/0x12345". @@ -146,7 +100,7 @@ func (c *Contract) deposit( } // 2. Effect: subtract balance. - if err := c.fungibleKeeper.LockZRC20InBank(ctx, c.zrc20ABI, zrc20Addr, caller, amount); err != nil { + if err := c.fungibleKeeper.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, caller, amount); err != nil { return nil, &ptypes.ErrUnexpected{ When: "LockZRC20InBank", Got: err.Error(), diff --git a/precompiles/bank/method_withdraw.go b/precompiles/bank/method_withdraw.go index 99cb4f763c..66df0ca05e 100644 --- a/precompiles/bank/method_withdraw.go +++ b/precompiles/bank/method_withdraw.go @@ -56,18 +56,10 @@ func (c *Contract) withdraw( } // Safety check: token has to be a valid whitelisted ZRC20 and not be paused. - t, found := c.fungibleKeeper.GetForeignCoins(ctx, zrc20Addr.String()) - if !found { + if err := c.fungibleKeeper.IsValidZRC20(ctx, zrc20Addr); err != nil { return nil, &ptypes.ErrInvalidToken{ Got: zrc20Addr.String(), - Reason: "token is not a whitelisted ZRC20", - } - } - - if t.Paused { - return nil, &ptypes.ErrInvalidToken{ - Got: zrc20Addr.String(), - Reason: "token is paused", + Reason: err.Error(), } } @@ -92,36 +84,14 @@ func (c *Contract) withdraw( return nil, err } - // Check for bank's ZRC20 balance. - // function balanceOf(address account) public view virtual override returns (uint256) - resBalanceOf, err := c.CallContract( - ctx, - &c.fungibleKeeper, - c.zrc20ABI, - zrc20Addr, - "balanceOf", - []interface{}{ContractAddress}, - ) - if err != nil { + // Check if fungible address has enough ZRC20 balance. + if err := c.fungibleKeeper.CheckFungibleZRC20Balance(ctx, c.zrc20ABI, zrc20Addr, amount); err != nil { return nil, &ptypes.ErrUnexpected{ When: "balanceOf", Got: err.Error(), } } - balance, ok := resBalanceOf[0].(*big.Int) - if !ok { - return nil, &ptypes.ErrUnexpected{ - Got: "ZRC20 balanceOf returned an unexpected type", - } - } - - if balance.Cmp(amount) == -1 { - return nil, &ptypes.ErrInvalidAmount{ - Got: balance.String(), - } - } - // 2. Effect: burn cosmos coin balance. if err := c.bankKeeper.SendCoinsFromAccountToModule(ctx, fromAddr, types.ModuleName, coinSet); err != nil { return nil, &ptypes.ErrUnexpected{ @@ -137,45 +107,21 @@ func (c *Contract) withdraw( } } - if err := c.addEventLog(ctx, evm.StateDB, WithdrawEventName, eventData{caller, zrc20Addr, fromAddr.String(), coinSet.Denoms()[0], amount}); err != nil { + // 3. Interactions: send ZRC20. + if err := c.fungibleKeeper.UnlockZRC20(ctx, c.zrc20ABI, zrc20Addr, caller, amount); err != nil { return nil, &ptypes.ErrUnexpected{ - When: "AddWithdrawLog", + When: "UnlockZRC20InBank", Got: err.Error(), } } - // 3. Interactions: send to module and burn. - - // function transfer(address recipient, uint256 amount) public virtual override returns (bool) - resTransfer, err := c.CallContract( - ctx, - &c.fungibleKeeper, - c.zrc20ABI, - zrc20Addr, - "transfer", - []interface{}{caller /* sender */, amount}, - ) - if err != nil { + if err := c.addEventLog(ctx, evm.StateDB, WithdrawEventName, eventData{caller, zrc20Addr, fromAddr.String(), coinSet.Denoms()[0], amount}); err != nil { return nil, &ptypes.ErrUnexpected{ - When: "transfer", + When: "AddWithdrawLog", Got: err.Error(), } } - transferred, ok := resTransfer[0].(bool) - if !ok { - return nil, &ptypes.ErrUnexpected{ - Got: "ZRC20 transfer returned an unexpected type", - } - } - - if !transferred { - return nil, &ptypes.ErrUnexpected{ - When: "transfer", - Got: "transaction not successful", - } - } - return method.Outputs.Pack(true) } diff --git a/x/fungible/keeper/lock_zrc20_bank.go b/x/fungible/keeper/lock_zrc20_bank.go deleted file mode 100644 index e2f745d544..0000000000 --- a/x/fungible/keeper/lock_zrc20_bank.go +++ /dev/null @@ -1,144 +0,0 @@ -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 -} diff --git a/x/fungible/keeper/zrc20_check_allowance.go b/x/fungible/keeper/zrc20_check_allowance.go new file mode 100644 index 0000000000..19a9846817 --- /dev/null +++ b/x/fungible/keeper/zrc20_check_allowance.go @@ -0,0 +1,72 @@ +package keeper + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +const allowance = "allowance" + +// CheckFungibleZRC20Allowance checks if the allowance of ZRC20 tokens, +// is equal or greater than the provided amount. +func (k Keeper) CheckFungibleZRC20Allowance( + 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, got: %s", amount.String()) + } + + 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, fungibletypes.ModuleAddressEVM} + res, err := k.CallEVM( + ctx, + *zrc20ABI, + fungibletypes.ModuleAddressEVM, + 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 +} diff --git a/x/fungible/keeper/zrc20_check_balance.go b/x/fungible/keeper/zrc20_check_balance.go new file mode 100644 index 0000000000..a9f0272c99 --- /dev/null +++ b/x/fungible/keeper/zrc20_check_balance.go @@ -0,0 +1,67 @@ +package keeper + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +const balanceOf = "balanceOf" + +// CheckFungibleZRC20Balance checks if the balance of ZRC20 tokens, +// is equal or greater than the provided amount. +func (k Keeper) CheckFungibleZRC20Balance( + ctx sdk.Context, + zrc20ABI *abi.ABI, + zrc20Address common.Address, + amount *big.Int, +) error { + if amount.Sign() <= 0 { + return fmt.Errorf("amount must be positive, got: %s", amount.String()) + } + + if zrc20Address == zeroAddress { + return fmt.Errorf("zrc20 address cannot be zero") + } + + res, err := k.CallEVM( + ctx, + *zrc20ABI, + fungibletypes.ModuleAddressEVM, + zrc20Address, + big.NewInt(0), + nil, + true, + true, + balanceOf, + fungibletypes.ModuleAddressEVM, + ) + if err != nil { + return err + } + + if res.VmError != "" { + return fmt.Errorf("%s", res.VmError) + } + + ret, err := zrc20ABI.Methods[balanceOf].Outputs.Unpack(res.Ret) + if err != nil { + return err + } + + balance, ok := ret[0].(*big.Int) + if !ok { + return fmt.Errorf("ZRC20 balanceOf returned an unexpected type") + } + + if balance.Cmp(amount) == -1 { + return fmt.Errorf("invalid balance, got: %s", balance.String()) + } + + return nil +} diff --git a/x/fungible/keeper/zrc20_check_token_validity.go b/x/fungible/keeper/zrc20_check_token_validity.go new file mode 100644 index 0000000000..bd4bf21c5b --- /dev/null +++ b/x/fungible/keeper/zrc20_check_token_validity.go @@ -0,0 +1,26 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" +) + +// IsValidZRC20 returns an error whenever a ZRC20 is not whitelisted or paused. +func (k Keeper) IsValidZRC20(ctx sdk.Context, zrc20Address common.Address) error { + if zrc20Address == zeroAddress { + return fmt.Errorf("zrc20 address cannot be zero") + } + + t, found := k.GetForeignCoins(ctx, zrc20Address.String()) + if !found { + return fmt.Errorf("ZRC20 is not whitelisted, address: %s", zrc20Address.String()) + } + + if t.Paused { + return fmt.Errorf("ZRC20 is paused, address: %s", zrc20Address.String()) + } + + return nil +} diff --git a/x/fungible/keeper/zrc20_lock_token.go b/x/fungible/keeper/zrc20_lock_token.go new file mode 100644 index 0000000000..8d79843ef6 --- /dev/null +++ b/x/fungible/keeper/zrc20_lock_token.go @@ -0,0 +1,82 @@ +package keeper + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +const transferFrom = "transferFrom" + +var zeroAddress common.Address = common.HexToAddress("0x0000000000000000000000000000000000000000") + +// LockZRC20 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) LockZRC20( + ctx sdk.Context, + zrc20ABI *abi.ABI, + zrc20Address, from common.Address, + amount *big.Int, +) error { + if amount.Sign() <= 0 { + return fmt.Errorf("amount must be positive, got: %s", amount.String()) + } + + 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.IsValidZRC20(ctx, zrc20Address); err != nil { + return err + } + + if err := k.CheckFungibleZRC20Allowance(ctx, zrc20ABI, from, zrc20Address, amount); err != nil { + return err + } + + args := []interface{}{from, fungibletypes.ModuleAddressEVM, amount} + res, err := k.CallEVM( + ctx, + *zrc20ABI, + fungibletypes.ModuleAddressEVM, + 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 +} diff --git a/x/fungible/keeper/zrc20_unlock_token.go b/x/fungible/keeper/zrc20_unlock_token.go new file mode 100644 index 0000000000..d932e1bcd7 --- /dev/null +++ b/x/fungible/keeper/zrc20_unlock_token.go @@ -0,0 +1,71 @@ +package keeper + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + fungibletypes "github.com/zeta-chain/node/x/fungible/types" +) + +const transfer = "transfer" + +// UnlockZRC20 unlocks ZRC20 tokens and sends them to the "to" address. +func (k Keeper) UnlockZRC20( + ctx sdk.Context, + zrc20ABI *abi.ABI, + zrc20Address, to common.Address, + amount *big.Int, +) error { + if amount.Sign() <= 0 { + return fmt.Errorf("amount must be positive, got: %s", amount.String()) + } + + if to == zeroAddress { + return fmt.Errorf("from address cannot be zero") + } + + if zrc20Address == zeroAddress { + return fmt.Errorf("zrc20 address cannot be zero") + } + + args := []interface{}{to, amount} + res, err := k.CallEVM( + ctx, + *zrc20ABI, + fungibletypes.ModuleAddressEVM, + zrc20Address, + big.NewInt(0), + nil, + true, + true, + transfer, + 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("transfer returned an unexpected value") + } + + if !transferred { + return fmt.Errorf("transfer not successful") + } + + return nil +}