Skip to content

Commit

Permalink
Merge branch 'develop' into erc20-auth-calls
Browse files Browse the repository at this point in the history
  • Loading branch information
skosito committed Oct 17, 2024
2 parents e59716c + 923b936 commit ce3df52
Show file tree
Hide file tree
Showing 19 changed files with 2,387 additions and 935 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ jobs:
core.setOutput('SOLANA_TESTS', true);
core.setOutput('TON_TESTS', true);
core.setOutput('V2_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627)
core.setOutput('V2_MIGRATION_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627)
} else if (context.eventName === 'schedule') {
core.setOutput('DEFAULT_TESTS', true);
core.setOutput('UPGRADE_TESTS', true);
Expand All @@ -102,7 +101,6 @@ jobs:
core.setOutput('SOLANA_TESTS', true);
core.setOutput('TON_TESTS', true);
core.setOutput('V2_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627)
core.setOutput('V2_MIGRATION_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627)
} else if (context.eventName === 'workflow_dispatch') {
const makeTargets = context.payload.inputs['make-targets'].split(',');
core.setOutput('DEFAULT_TESTS', makeTargets.includes('default-test'));
Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* [2957](https://github.com/zeta-chain/node/pull/2957) - enable Bitcoin inscription support on testnet
* [2896](https://github.com/zeta-chain/node/pull/2896) - add TON inbound observation
* [2987](https://github.com/zeta-chain/node/pull/2987) - add non-EVM standard inbound memo package
* [2979](https://github.com/zeta-chain/node/pull/2979) - add fungible keeper ability to lock/unlock ZRC20 tokens
* [3012](https://github.com/zeta-chain/node/pull/3012) - integrate authenticated calls erc20 smart contract functionality into protocol

### Refactor
Expand Down
3 changes: 2 additions & 1 deletion cmd/zetae2e/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,8 @@ func localE2ETest(cmd *cobra.Command, _ []string) {
e2etests.TestPrecompilesPrototypeName,
e2etests.TestPrecompilesPrototypeThroughContractName,
e2etests.TestPrecompilesStakingName,
e2etests.TestPrecompilesStakingThroughContractName,
// Disabled until further notice, check https://github.com/zeta-chain/node/issues/3005.
// e2etests.TestPrecompilesStakingThroughContractName,
e2etests.TestPrecompilesBankName,
e2etests.TestPrecompilesBankFailName,
e2etests.TestPrecompilesBankThroughContractName,
Expand Down
2 changes: 1 addition & 1 deletion e2e/e2etests/e2etests.go
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,7 @@ var AllE2ETests = []runner.E2ETest{
TestPrecompilesStakingName,
"test stateful precompiled contracts staking",
[]runner.ArgDefinition{},
TestPrecompilesStaking,
TestPrecompilesStakingIsDisabled,
),
runner.NewE2ETest(
TestPrecompilesStakingThroughContractName,
Expand Down
19 changes: 10 additions & 9 deletions e2e/e2etests/test_precompiles_bank.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func TestPrecompilesBank(r *runner.E2ERunner, args []string) {
higherBalanceAmount := big.NewInt(1001)
higherAllowanceAmount := big.NewInt(501)
spender := r.EVMAddress()
bankAddress := bank.ContractAddress

// Increase the gasLimit. It's required because of the gas consumed by precompiled functions.
previousGasLimit := r.ZEVMAuth.GasLimit
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, bankAddress, 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, bankAddress, depositAmount)
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, bankAddress, 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}, bankAddress)
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}, bankAddress)
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}, bankAddress)
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, bankAddress := r.EVMAddress(), bank.ContractAddress

// Create a bank contract caller.
bankContract, err := bank.NewIBank(bank.ContractAddress, r.ZEVMClient)
Expand All @@ -179,13 +180,13 @@ func TestPrecompilesBankNonZRC20(r *runner.E2ERunner, args []string) {
)

// Allow the bank contract to spend 25 WZeta tokens.
tx, err := r.WZeta.Approve(r.ZEVMAuth, bankAddr, big.NewInt(25))
tx, err := r.WZeta.Approve(r.ZEVMAuth, bankAddress, big.NewInt(25))
require.NoError(r, err, "Error approving allowance for bank contract")
receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
require.EqualValues(r, uint64(1), receipt.Status, "approve allowance tx failed")

// Check the allowance of the bank in WZeta tokens. Should be 25.
allowance, err := r.WZeta.Allowance(&bind.CallOpts{Context: r.Ctx}, spender, bankAddr)
allowance, err := r.WZeta.Allowance(&bind.CallOpts{Context: r.Ctx}, spender, bankAddress)
require.NoError(r, err, "Error retrieving bank allowance")
require.EqualValues(r, uint64(25), allowance.Uint64(), "Error allowance for bank contract")

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 @@ -18,6 +18,7 @@ func TestPrecompilesBankThroughContract(r *runner.E2ERunner, args []string) {
require.Len(r, args, 0, "No arguments expected")

spender := r.EVMAddress()
bankAddress := bank.ContractAddress
zrc20Address := r.ERC20ZRC20Addr
oneThousand := big.NewInt(1e3)
oneThousandOne := big.NewInt(1001)
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, bankAddress))

// 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, bankAddress))

// Allow 500 ZRC20 to bank precompile.
approveAllowance(r, bank.ContractAddress, fiveHundred)
approveAllowance(r, bankAddress, 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, bankAddress))

// Allow 1000 ZRC20 to bank precompile.
approveAllowance(r, bank.ContractAddress, oneThousand)
approveAllowance(r, bankAddress, 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, bankAddress))

// 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, bankAddress))

// 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, bankAddress))

// 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, bankAddress))

// Check the withdraw event.
eventWithdraw, err := bankPrecompileCaller.ParseWithdraw(*receipt.Logs[0])
Expand Down
52 changes: 52 additions & 0 deletions e2e/e2etests/test_precompiles_staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,58 @@ import (
"github.com/zeta-chain/node/precompiles/staking"
)

func TestPrecompilesStakingIsDisabled(r *runner.E2ERunner, args []string) {
require.Len(r, args, 0, "No arguments expected")

stakingContract, err := staking.NewIStaking(staking.ContractAddress, r.ZEVMClient)
require.NoError(r, err, "Failed to create staking contract caller")

previousGasLimit := r.ZEVMAuth.GasLimit
r.ZEVMAuth.GasLimit = 10000000
defer func() {
r.ZEVMAuth.GasLimit = previousGasLimit
}()

validators, err := stakingContract.GetAllValidators(&bind.CallOpts{})
require.NoError(r, err)
require.GreaterOrEqual(r, len(validators), 2)

CleanValidatorDelegations(r, stakingContract, validators)

// shares are 0 for both validators at the start
sharesBeforeVal1, err := stakingContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validators[0].OperatorAddress)
require.NoError(r, err)
require.Equal(r, int64(0), sharesBeforeVal1.Int64())

sharesBeforeVal2, err := stakingContract.GetShares(&bind.CallOpts{}, r.ZEVMAuth.From, validators[1].OperatorAddress)
require.NoError(r, err)
require.Equal(r, int64(0), sharesBeforeVal2.Int64())

// stake 3 to validator1
tx, err := stakingContract.Stake(r.ZEVMAuth, r.ZEVMAuth.From, validators[0].OperatorAddress, big.NewInt(3))
require.NoError(r, err)
receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
utils.RequiredTxFailed(r, receipt)

// unstake 1 from validator1
tx, err = stakingContract.Unstake(r.ZEVMAuth, r.ZEVMAuth.From, validators[0].OperatorAddress, big.NewInt(1))
require.NoError(r, err)
receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
utils.RequiredTxFailed(r, receipt)

// move 1 stake from validator1 to validator2
tx, err = stakingContract.MoveStake(
r.ZEVMAuth,
r.ZEVMAuth.From,
validators[0].OperatorAddress,
validators[1].OperatorAddress,
big.NewInt(1),
)
require.NoError(r, err)
receipt = utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout)
utils.RequiredTxFailed(r, receipt)
}

func TestPrecompilesStaking(r *runner.E2ERunner, args []string) {
require.Len(r, args, 0, "No arguments expected")

Expand Down
75 changes: 6 additions & 69 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 bank precompile 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,71 +89,24 @@ 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".
// - Mint coins.
// - Send coins to the caller.
// - Mint coins to the fungible module.
// - Send coins from fungible to the caller.
coinSet, err := createCoinSet(ZRC20ToCosmosDenom(zrc20Addr), amount)
if err != nil {
return nil, err
}

// 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, c.Address(), caller, c.Address(), 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{
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 == nil || amount.Sign() <= 0 {
return common.Address{}, nil, &ptypes.ErrInvalidAmount{
Got: amount.String(),
}
Expand Down
Loading

0 comments on commit ce3df52

Please sign in to comment.