Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add fungible keeper ability to lock/unlock ZRC20 tokens #2979

Merged
merged 11 commits into from
Oct 17, 2024
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* [2904](https://github.com/zeta-chain/node/pull/2904) - integrate authenticated calls smart contract functionality into protocol
* [2919](https://github.com/zeta-chain/node/pull/2919) - add inbound sender to revert context
* [2957](https://github.com/zeta-chain/node/pull/2957) - enable Bitcoin inscription support on testnet
* [2979](https://github.com/zeta-chain/node/pull/2979) - add fungible keeper ability to lock/unlock ZRC20 tokens

### Refactor

Expand Down
15 changes: 8 additions & 7 deletions e2e/e2etests/test_precompiles_bank.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
"github.com/zeta-chain/node/precompiles/bank"
fungibletypes "github.com/zeta-chain/node/x/fungible/types"
)

func TestPrecompilesBank(r *runner.E2ERunner, args []string) {
Expand All @@ -29,7 +30,7 @@ func TestPrecompilesBank(r *runner.E2ERunner, args []string) {

// Reset the allowance to 0; this is needed when running upgrade tests where
// this test runs twice.
tx, err := r.ERC20ZRC20.Approve(r.ZEVMAuth, bank.ContractAddress, big.NewInt(0))
tx, err := r.ERC20ZRC20.Approve(r.ZEVMAuth, fungibletypes.ModuleAddressZEVM, big.NewInt(0))
require.NoError(r, err)
receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
utils.RequireTxSuccessful(r, receipt, "Resetting allowance failed")
Expand Down Expand Up @@ -59,7 +60,7 @@ func TestPrecompilesBank(r *runner.E2ERunner, args []string) {
require.Equal(r, uint64(0), cosmosBalance.Uint64(), "spender cosmos coin balance should be 0")

// Approve allowance of 500 ERC20ZRC20 tokens for the bank contract. Should pass.
tx, err := r.ERC20ZRC20.Approve(r.ZEVMAuth, bank.ContractAddress, depositAmount)
tx, err := r.ERC20ZRC20.Approve(r.ZEVMAuth, fungibletypes.ModuleAddressZEVM, depositAmount)
fbac marked this conversation as resolved.
Show resolved Hide resolved
require.NoError(r, err)
receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
utils.RequireTxSuccessful(r, receipt, "Approve ETHZRC20 bank allowance tx failed")
Expand All @@ -72,7 +73,7 @@ func TestPrecompilesBank(r *runner.E2ERunner, args []string) {
utils.RequiredTxFailed(r, receipt, "Depositting an amount higher than allowed should fail")

// Approve allowance of 1000 ERC20ZRC20 tokens.
tx, err = r.ERC20ZRC20.Approve(r.ZEVMAuth, bank.ContractAddress, big.NewInt(1e3))
tx, err = r.ERC20ZRC20.Approve(r.ZEVMAuth, fungibletypes.ModuleAddressZEVM, big.NewInt(1e3))
require.NoError(r, err)
receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
utils.RequireTxSuccessful(r, receipt, "Approve ETHZRC20 bank allowance tx failed")
Expand Down Expand Up @@ -103,7 +104,7 @@ func TestPrecompilesBank(r *runner.E2ERunner, args []string) {
require.Equal(r, uint64(500), cosmosBalance.Uint64(), "spender cosmos coin balance should be 500")

// Bank: ERC20ZRC20 balance should be 500 tokens locked.
bankZRC20Balance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{Context: r.Ctx}, bank.ContractAddress)
bankZRC20Balance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{Context: r.Ctx}, fungibletypes.ModuleAddressZEVM)
require.NoError(r, err, "Call ERC20ZRC20.BalanceOf")
require.Equal(r, uint64(500), bankZRC20Balance.Uint64(), "bank ERC20ZRC20 balance should be 500")

Expand All @@ -115,7 +116,7 @@ func TestPrecompilesBank(r *runner.E2ERunner, args []string) {

// Bank: ERC20ZRC20 balance should be 500 tokens locked after a failed withdraw.
// No tokens should be unlocked with a failed withdraw.
bankZRC20Balance, err = r.ERC20ZRC20.BalanceOf(&bind.CallOpts{Context: r.Ctx}, bank.ContractAddress)
bankZRC20Balance, err = r.ERC20ZRC20.BalanceOf(&bind.CallOpts{Context: r.Ctx}, fungibletypes.ModuleAddressZEVM)
require.NoError(r, err, "Call ERC20ZRC20.BalanceOf")
require.Equal(r, uint64(500), bankZRC20Balance.Uint64(), "bank ERC20ZRC20 balance should be 500")

Expand Down Expand Up @@ -143,7 +144,7 @@ func TestPrecompilesBank(r *runner.E2ERunner, args []string) {
require.Equal(r, uint64(1000), zrc20Balance.Uint64(), "spender ERC20ZRC20 balance should be 1000")

// Bank: ERC20ZRC20 balance should be 0 tokens locked.
bankZRC20Balance, err = r.ERC20ZRC20.BalanceOf(&bind.CallOpts{Context: r.Ctx}, bank.ContractAddress)
bankZRC20Balance, err = r.ERC20ZRC20.BalanceOf(&bind.CallOpts{Context: r.Ctx}, fungibletypes.ModuleAddressZEVM)
require.NoError(r, err, "Call ERC20ZRC20.BalanceOf")
require.Equal(r, uint64(0), bankZRC20Balance.Uint64(), "bank ERC20ZRC20 balance should be 0")
}
Expand All @@ -158,7 +159,7 @@ func TestPrecompilesBankNonZRC20(r *runner.E2ERunner, args []string) {
r.ZEVMAuth.GasLimit = previousGasLimit
}()

spender, bankAddr := r.EVMAddress(), bank.ContractAddress
spender, bankAddr := r.EVMAddress(), fungibletypes.ModuleAddressZEVM

// Create a bank contract caller.
bankContract, err := bank.NewIBank(bank.ContractAddress, r.ZEVMClient)
Expand Down
19 changes: 10 additions & 9 deletions e2e/e2etests/test_precompiles_bank_through_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/zeta-chain/node/e2e/runner"
"github.com/zeta-chain/node/e2e/utils"
"github.com/zeta-chain/node/precompiles/bank"
fungibletypes "github.com/zeta-chain/node/x/fungible/types"
)

func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) {
Expand Down Expand Up @@ -59,7 +60,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) {
// Check initial balances.
balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender))
balanceShouldBe(r, 1000, checkZRC20Balance(r, spender))
balanceShouldBe(r, 0, checkZRC20Balance(r, bank.ContractAddress))
balanceShouldBe(r, 0, checkZRC20Balance(r, fungibletypes.ModuleAddressZEVM))

// Deposit without previous alllowance should fail.
receipt = depositThroughTestBank(r, testBank, zrc20Address, oneThousand)
Expand All @@ -68,10 +69,10 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) {
// Check balances, should be the same.
balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender))
balanceShouldBe(r, 1000, checkZRC20Balance(r, spender))
balanceShouldBe(r, 0, checkZRC20Balance(r, bank.ContractAddress))
balanceShouldBe(r, 0, checkZRC20Balance(r, fungibletypes.ModuleAddressZEVM))

// Allow 500 ZRC20 to bank precompile.
approveAllowance(r, bank.ContractAddress, fiveHundred)
approveAllowance(r, fungibletypes.ModuleAddressZEVM, fiveHundred)

// Deposit 501 ERC20ZRC20 tokens to the bank contract, through TestBank.
// It's higher than allowance but lower than balance, should fail.
Expand All @@ -81,10 +82,10 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) {
// Balances shouldn't change.
balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender))
balanceShouldBe(r, 1000, checkZRC20Balance(r, spender))
balanceShouldBe(r, 0, checkZRC20Balance(r, bank.ContractAddress))
balanceShouldBe(r, 0, checkZRC20Balance(r, fungibletypes.ModuleAddressZEVM))

// Allow 1000 ZRC20 to bank precompile.
approveAllowance(r, bank.ContractAddress, oneThousand)
approveAllowance(r, fungibletypes.ModuleAddressZEVM, oneThousand)

// Deposit 1001 ERC20ZRC20 tokens to the bank contract.
// It's higher than spender balance but within approved allowance, should fail.
Expand All @@ -94,7 +95,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) {
// Balances shouldn't change.
balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender))
balanceShouldBe(r, 1000, checkZRC20Balance(r, spender))
balanceShouldBe(r, 0, checkZRC20Balance(r, bank.ContractAddress))
balanceShouldBe(r, 0, checkZRC20Balance(r, fungibletypes.ModuleAddressZEVM))

// Deposit 500 ERC20ZRC20 tokens to the bank contract, it's within allowance and balance. Should pass.
receipt = depositThroughTestBank(r, testBank, zrc20Address, fiveHundred)
Expand All @@ -103,7 +104,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) {
// Balances should be transferred. Bank now locks 500 ZRC20 tokens.
balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender))
balanceShouldBe(r, 500, checkZRC20Balance(r, spender))
balanceShouldBe(r, 500, checkZRC20Balance(r, bank.ContractAddress))
balanceShouldBe(r, 500, checkZRC20Balance(r, fungibletypes.ModuleAddressZEVM))

// Check the deposit event.
eventDeposit, err := bankPrecompileCaller.ParseDeposit(*receipt.Logs[0])
Expand All @@ -119,7 +120,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) {
// Balances shouldn't change.
balanceShouldBe(r, 500, checkCosmosBalance(r, testBank, zrc20Address, spender))
balanceShouldBe(r, 500, checkZRC20Balance(r, spender))
balanceShouldBe(r, 500, checkZRC20Balance(r, bank.ContractAddress))
balanceShouldBe(r, 500, checkZRC20Balance(r, fungibletypes.ModuleAddressZEVM))

// Try to withdraw 500 ERC20ZRC20 tokens. Should pass.
receipt = withdrawThroughTestBank(r, testBank, zrc20Address, fiveHundred)
Expand All @@ -128,7 +129,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) {
// Balances should be reverted to initial state.
balanceShouldBe(r, 0, checkCosmosBalance(r, testBank, zrc20Address, spender))
balanceShouldBe(r, 1000, checkZRC20Balance(r, spender))
balanceShouldBe(r, 0, checkZRC20Balance(r, bank.ContractAddress))
balanceShouldBe(r, 0, checkZRC20Balance(r, fungibletypes.ModuleAddressZEVM))

// Check the withdraw event.
eventWithdraw, err := bankPrecompileCaller.ParseWithdraw(*receipt.Logs[0])
Expand Down
71 changes: 4 additions & 67 deletions precompiles/bank/method_deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
// The caller cosmos address will be calculated from the EVM caller address. by executing toAddr := sdk.AccAddress(addr.Bytes()).
// This function can be think of a permissionless way of minting cosmos coins.
// This is how deposit works:
// - The caller has to allow the bank contract to spend a certain amount ZRC20 token coins on its behalf. This is mandatory.
// - The caller has to allow the Fungible ZEVM address to spend a certain amount ZRC20 token coins on its behalf. This is mandatory.
// - Then, the caller calls deposit(ZRC20 address, amount), to deposit the amount and receive cosmos coins.
// - The bank will check there's enough balance, the caller is not a blocked address, and the token is a not paused ZRC20.
// - Then the cosmos coins "zrc20/0x12345" will be minted and sent to the caller's cosmos address.
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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".
fbac marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -146,30 +100,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.LockZRC20(ctx, c.zrc20ABI, zrc20Addr, caller, amount); err != nil {
fbac marked this conversation as resolved.
Show resolved Hide resolved
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{
Expand Down Expand Up @@ -205,7 +142,7 @@ func unpackDepositArgs(args []interface{}) (zrc20Addr common.Address, amount *bi
}

amount, ok = args[1].(*big.Int)
if !ok || amount.Sign() < 0 || amount == nil || amount == new(big.Int) {
if !ok || amount.Sign() <= 0 || amount == nil || amount == new(big.Int) {
fbac marked this conversation as resolved.
Show resolved Hide resolved
return common.Address{}, nil, &ptypes.ErrInvalidAmount{
Got: amount.String(),
}
Expand Down
Loading
Loading