diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index af9bb95f4a..6010eb826e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature Request about: Suggest an idea for this project title: '' -labels: 'feature' +labels: 'feature:idea' assignees: '' --- diff --git a/.github/workflows/ci-nightly-performance-testing.yaml b/.github/workflows/ci-nightly-performance-testing.yaml index d4676b5271..500ba8f443 100644 --- a/.github/workflows/ci-nightly-performance-testing.yaml +++ b/.github/workflows/ci-nightly-performance-testing.yaml @@ -24,7 +24,7 @@ jobs: - name: "INSTALL:NODEJS" uses: actions/setup-node@v4 with: - node-version: 16 + node-version: 20 - name: "START:LOCAL:NET:WITH:STATE" run: | @@ -102,7 +102,7 @@ jobs: artillery report report.json --output artillery_report.html - name: "UPLOAD:REPORT" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: always() with: name: artillery-report diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0056022622..f5030b1152 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -49,6 +49,10 @@ on: type: boolean required: false default: false + v2-test: + type: boolean + required: false + default: false concurrency: group: e2e-${{ github.head_ref || github.sha }} @@ -69,6 +73,7 @@ jobs: STATEFUL_DATA_TESTS: ${{ steps.matrix-conditionals.outputs.STATEFUL_DATA_TESTS }} TSS_MIGRATION_TESTS: ${{ steps.matrix-conditionals.outputs.TSS_MIGRATION_TESTS }} SOLANA_TESTS: ${{ steps.matrix-conditionals.outputs.SOLANA_TESTS }} + V2_TESTS: ${{ steps.matrix-conditionals.outputs.V2_TESTS }} steps: # use api rather than event context to avoid race conditions (label added after push) - id: matrix-conditionals @@ -92,8 +97,10 @@ jobs: core.setOutput('STATEFUL_DATA_TESTS', labels.includes('STATEFUL_DATA_TESTS')); core.setOutput('TSS_MIGRATION_TESTS', labels.includes('TSS_MIGRATION_TESTS')); core.setOutput('SOLANA_TESTS', labels.includes('SOLANA_TESTS')); + core.setOutput('V2_TESTS', labels.includes('V2_TESTS')); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } else if (context.eventName === 'merge_group') { core.setOutput('DEFAULT_TESTS', true); + core.setOutput('UPGRADE_LIGHT_TESTS', true); } else if (context.eventName === 'push' && context.ref === 'refs/heads/develop') { core.setOutput('DEFAULT_TESTS', true); } else if (context.eventName === 'push' && context.ref.startsWith('refs/heads/release/')) { @@ -104,6 +111,7 @@ jobs: core.setOutput('ADMIN_TESTS', true); core.setOutput('PERFORMANCE_TESTS', true); core.setOutput('STATEFUL_DATA_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) } else if (context.eventName === 'schedule') { core.setOutput('DEFAULT_TESTS', true); core.setOutput('UPGRADE_TESTS', true); @@ -112,7 +120,9 @@ jobs: core.setOutput('ADMIN_TESTS', true); core.setOutput('PERFORMANCE_TESTS', true); core.setOutput('STATEFUL_DATA_TESTS', true); + core.setOutput('TSS_MIGRATION_TESTS', true); core.setOutput('SOLANA_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) } else if (context.eventName === 'workflow_dispatch') { core.setOutput('DEFAULT_TESTS', context.payload.inputs['default-test']); core.setOutput('UPGRADE_TESTS', context.payload.inputs['upgrade-test']); @@ -123,6 +133,7 @@ jobs: core.setOutput('STATEFUL_DATA_TESTS', context.payload.inputs['stateful-data-test']); core.setOutput('TSS_MIGRATION_TESTS', context.payload.inputs['tss-migration-test']); core.setOutput('SOLANA_TESTS', context.payload.inputs['solana-test']); + core.setOutput('V2_TESTS', context.payload.inputs['v2-test']); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } e2e: @@ -160,6 +171,9 @@ jobs: - make-target: "start-solana-test" runs-on: ubuntu-20.04 run: ${{ needs.matrix-conditionals.outputs.SOLANA_TESTS == 'true' }} + - make-target: "start-v2-test" + runs-on: ubuntu-20.04 + run: ${{ needs.matrix-conditionals.outputs.V2_TESTS == 'true' }} name: ${{ matrix.make-target }} uses: ./.github/workflows/reusable-e2e.yml with: @@ -197,6 +211,7 @@ jobs: const cleanName = job.name.split("/")[0]; return `${icon} ${cleanName}`; }); + e2eResults.sort(); const overallResultStr = '${{ needs.e2e.result }}'; const overallResultPassing = overallResultStr === 'success' || overallResultStr === 'skipped'; diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 3e848ff66c..f37a74c1b1 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -32,11 +32,11 @@ jobs: echo "${{ github.ref }}" check-goreleaser: - needs: - - check-branch - runs-on: ubuntu-22.04 + if: ${{ github.event.inputs.skip_checks != 'true' }} + runs-on: ${{ vars.RELEASE_RUNNER }} steps: - - name: Branch + - uses: actions/checkout@v4 + - name: Release build dry-run run: | make release-dry-run @@ -124,7 +124,8 @@ jobs: - check-changelog - check-upgrade-handler-updated - check-branch - runs-on: ubuntu-22.04 + - check-goreleaser + runs-on: ${{ vars.RELEASE_RUNNER }} timeout-minutes: 60 environment: release steps: diff --git a/Dockerfile b/Dockerfile index 570a08ec4d..0f9804d087 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build Stage -FROM golang:1.20-alpine3.18 AS builder +FROM golang:1.22-alpine3.18 AS builder ENV GOPATH /go ENV GOOS=linux diff --git a/Makefile b/Makefile index 6f0267ba32..077eb18bc9 100644 --- a/Makefile +++ b/Makefile @@ -279,6 +279,11 @@ start-solana-test: zetanode solana export E2E_ARGS="--skip-regular --test-solana" && \ cd contrib/localnet/ && $(DOCKER_COMPOSE) --profile solana -f docker-compose.yml up -d +start-v2-test: zetanode + @echo "--> Starting e2e smart contracts v2 test" + export E2E_ARGS="--skip-regular --test-v2" && \ + cd contrib/localnet/ && $(DOCKER_COMPOSE) -f docker-compose.yml up -d + ############################################################################### ### Upgrade Tests ### ############################################################################### diff --git a/app/ante/authz.go b/app/ante/authz.go index 5c8a58c534..d310943771 100644 --- a/app/ante/authz.go +++ b/app/ante/authz.go @@ -34,7 +34,7 @@ func (ald AuthzLimiterDecorator) AnteHandle( next sdk.AnteHandler, ) (newCtx sdk.Context, err error) { if err := ald.checkDisabledMsgs(tx.GetMsgs(), false, 1); err != nil { - return ctx, errorsmod.Wrapf(errortypes.ErrUnauthorized, err.Error()) + return ctx, errorsmod.Wrap(errortypes.ErrUnauthorized, err.Error()) } return next(ctx, tx, simulate) } diff --git a/changelog.md b/changelog.md index fcd82cffe7..0bb8ab0180 100644 --- a/changelog.md +++ b/changelog.md @@ -4,24 +4,33 @@ ### Features +* [2578](https://github.com/zeta-chain/node/pull/2578) - add Gateway address in protocol contract list +* [2630](https://github.com/zeta-chain/node/pull/2630) - implement `MsgMigrateERC20CustodyFunds` to migrate the funds from the ERC20Custody to a new contracts (to be used for the new ERC20Custody contract for smart contract V2) * [2578](https://github.com/zeta-chain/node/pull/2578) - Add Gateway address in protocol contract list +* [2594](https://github.com/zeta-chain/node/pull/2594) - Integrate Protocol Contracts V2 in the protocol * [2634](https://github.com/zeta-chain/node/pull/2634) - add support for EIP-1559 gas fees * [2597](https://github.com/zeta-chain/node/pull/2597) - Add generic rpc metrics to zetaclient * [2538](https://github.com/zeta-chain/node/pull/2538) - add background worker routines to shutdown zetaclientd when needed for tss migration +* [2681](https://github.com/zeta-chain/node/pull/2681) - implement `MsgUpdateERC20CustodyPauseStatus` to pause or unpause ERC20 Custody contract (to be used for the migration process for smart contract V2) +* [2644](https://github.com/zeta-chain/node/pull/2644) - add created_timestamp to cctx status +* [2673](https://github.com/zeta-chain/node/pull/2673) - add relayer key importer, encryption and decryption * [2633](https://github.com/zeta-chain/node/pull/2633) - support for stateful precompiled contracts. ### Refactor * [2615](https://github.com/zeta-chain/node/pull/2615) - Refactor cleanup of outbound trackers - -### Fixes - -* [2654](https://github.com/zeta-chain/node/pull/2654) - add validation for authorization list in when validating genesis state for authorization module +* [2749](https://github.com/zeta-chain/node/pull/2749) - fix all lint errors from govet ### Tests +* [2726](https://github.com/zeta-chain/node/pull/2726) - add e2e tests for deposit and call, deposit and revert * [2703](https://github.com/zeta-chain/node/pull/2703) - add e2e tests for stateful precompiled contracts +### Fixes + +* [2654](https://github.com/zeta-chain/node/pull/2654) - add validation for authorization list in when validating genesis state for authorization module +* [2672](https://github.com/zeta-chain/node/pull/2672) - check observer set for duplicates when adding a new observer or updating an existing one + ## v19.0.0 ### Breaking Changes @@ -136,6 +145,7 @@ * [2481](https://github.com/zeta-chain/node/pull/2481) - increase gas limit inbound and outbound vote message to 500k * [2545](https://github.com/zeta-chain/node/pull/2545) - check solana minimum rent exempt to avoid outbound failure * [2547](https://github.com/zeta-chain/node/pull/2547) - limit max txs in priority mempool +* [2628](https://github.com/zeta-chain/node/pull/2628) - avoid submitting invalid hashes to outbound tracker ### CI diff --git a/cmd/zetaclientd-supervisor/lib.go b/cmd/zetaclientd-supervisor/lib.go index e65782c6b1..b6f57b5295 100644 --- a/cmd/zetaclientd-supervisor/lib.go +++ b/cmd/zetaclientd-supervisor/lib.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "context" "encoding/json" "errors" @@ -10,7 +9,6 @@ import ( "os" "path" "runtime" - "strings" "sync" "time" @@ -67,7 +65,6 @@ type zetaclientdSupervisor struct { upgradesDir string upgradePlanName string enableAutoDownload bool - restartChan chan os.Signal } func newZetaclientdSupervisor( @@ -83,15 +80,12 @@ func newZetaclientdSupervisor( if err != nil { return nil, fmt.Errorf("grpc dial: %w", err) } - // these signals will result in the supervisor process only restarting zetaclientd - restartChan := make(chan os.Signal, 1) return &zetaclientdSupervisor{ zetacoredConn: conn, logger: logger, reloadSignals: make(chan bool, 1), upgradesDir: defaultUpgradesDir, enableAutoDownload: enableAutoDownload, - restartChan: restartChan, }, nil } @@ -260,23 +254,3 @@ func (s *zetaclientdSupervisor) downloadZetaclientd(ctx context.Context, plan *u } return nil } - -func promptPasswords() (string, string, error) { - reader := bufio.NewReader(os.Stdin) - fmt.Print("HotKey Password: ") - hotKeyPass, err := reader.ReadString('\n') - if err != nil { - return "", "", err - } - fmt.Print("TSS Password: ") - tssKeyPass, err := reader.ReadString('\n') - if err != nil { - return "", "", err - } - - //trim delimiters - hotKeyPass = strings.TrimSuffix(hotKeyPass, "\n") - tssKeyPass = strings.TrimSuffix(tssKeyPass, "\n") - - return hotKeyPass, tssKeyPass, err -} diff --git a/cmd/zetaclientd-supervisor/main.go b/cmd/zetaclientd-supervisor/main.go index d7179d6948..756a9138da 100644 --- a/cmd/zetaclientd-supervisor/main.go +++ b/cmd/zetaclientd-supervisor/main.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "os/signal" + "strings" "syscall" "time" @@ -14,6 +15,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/zeta-chain/zetacore/app" + zetaos "github.com/zeta-chain/zetacore/pkg/os" "github.com/zeta-chain/zetacore/zetaclient/config" ) @@ -37,7 +39,9 @@ func main() { shutdownChan := make(chan os.Signal, 1) signal.Notify(shutdownChan, syscall.SIGINT, syscall.SIGTERM) - hotkeyPassword, tssPassword, err := promptPasswords() + // prompt for all necessary passwords + titles := []string{"HotKey", "TSS", "Solana Relayer Key"} + passwords, err := zetaos.PromptPasswords(titles) if err != nil { logger.Error().Err(err).Msg("unable to get passwords") os.Exit(1) @@ -50,8 +54,6 @@ func main() { os.Exit(1) } supervisor.Start(ctx) - // listen for SIGHUP to trigger a restart of zetaclientd - signal.Notify(supervisor.restartChan, syscall.SIGHUP) shouldRestart := true for shouldRestart { @@ -66,7 +68,7 @@ func main() { cmd.Stderr = os.Stderr // must reset the passwordInputBuffer every iteration because reads are stateful (seek to end) passwordInputBuffer := bytes.Buffer{} - passwordInputBuffer.Write([]byte(hotkeyPassword + "\n" + tssPassword + "\n")) + passwordInputBuffer.Write([]byte(strings.Join(passwords, "\n") + "\n")) cmd.Stdin = &passwordInputBuffer eg, ctx := errgroup.WithContext(ctx) @@ -81,6 +83,7 @@ func main() { }) eg.Go(func() error { supervisor.WaitForReloadSignal(ctx) + cancel() return nil }) eg.Go(func() error { @@ -88,8 +91,6 @@ func main() { select { case <-ctx.Done(): return nil - case sig := <-supervisor.restartChan: - logger.Info().Msgf("got signal %d, sending SIGINT to zetaclientd", sig) case sig := <-shutdownChan: logger.Info().Msgf("got signal %d, shutting down", sig) shouldRestart = false diff --git a/cmd/zetaclientd/debug.go b/cmd/zetaclientd/debug.go index 6fc46f71f9..9d7ece9a0c 100644 --- a/cmd/zetaclientd/debug.go +++ b/cmd/zetaclientd/debug.go @@ -80,7 +80,7 @@ func debugCmd(_ *cobra.Command, args []string) error { return err } - appContext := zctx.New(cfg, zerolog.Nop()) + appContext := zctx.New(cfg, nil, zerolog.Nop()) ctx := zctx.WithAppContext(context.Background(), appContext) if err := client.UpdateAppContext(ctx, appContext, zerolog.Nop()); err != nil { diff --git a/cmd/zetaclientd/encrypt_tss.go b/cmd/zetaclientd/encrypt_tss.go index 6fca9064cb..99322c0ecd 100644 --- a/cmd/zetaclientd/encrypt_tss.go +++ b/cmd/zetaclientd/encrypt_tss.go @@ -1,17 +1,14 @@ package main import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "crypto/sha256" "encoding/json" - "errors" - "io" "os" "path/filepath" + "github.com/pkg/errors" "github.com/spf13/cobra" + + "github.com/zeta-chain/zetacore/pkg/crypto" ) var encTssCmd = &cobra.Command{ @@ -25,9 +22,10 @@ func init() { RootCmd.AddCommand(encTssCmd) } +// EncryptTSSFile encrypts the given file with the given secret key func EncryptTSSFile(_ *cobra.Command, args []string) error { filePath := args[0] - secretKey := args[1] + password := args[1] filePath = filepath.Clean(filePath) data, err := os.ReadFile(filePath) @@ -39,29 +37,11 @@ func EncryptTSSFile(_ *cobra.Command, args []string) error { return errors.New("file does not contain valid json, may already be encrypted") } - block, err := aes.NewCipher(getFragmentSeed(secretKey)) - if err != nil { - return err - } - - // Creating GCM mode - gcm, err := cipher.NewGCM(block) + // encrypt the data + cipherText, err := crypto.EncryptAES256GCM(data, password) if err != nil { - return err - } - // Generating random nonce - nonce := make([]byte, gcm.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return err + return errors.Wrap(err, "failed to encrypt data") } - cipherText := gcm.Seal(nonce, nonce, data, nil) return os.WriteFile(filePath, cipherText, 0o600) } - -func getFragmentSeed(password string) []byte { - h := sha256.New() - h.Write([]byte(password)) - seed := h.Sum(nil) - return seed -} diff --git a/cmd/zetaclientd/import_relayer_keys.go b/cmd/zetaclientd/import_relayer_keys.go new file mode 100644 index 0000000000..caf2db9538 --- /dev/null +++ b/cmd/zetaclientd/import_relayer_keys.go @@ -0,0 +1,153 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/crypto" + zetaos "github.com/zeta-chain/zetacore/pkg/os" + "github.com/zeta-chain/zetacore/zetaclient/keys" +) + +var CmdImportRelayerKey = &cobra.Command{ + Use: "import-relayer-key --network= --private-key= --password= --relayer-key-path=", + Short: "Import a relayer private key", + Example: `zetaclientd import-relayer-key --network=7 --private-key= --password=`, + RunE: ImportRelayerKey, +} + +var CmdRelayerAddress = &cobra.Command{ + Use: "relayer-address --network= --password= --relayer-key-path=", + Short: "Show the relayer address", + Example: `zetaclientd relayer-address --network=7 --password=my_password`, + RunE: ShowRelayerAddress, +} + +var importArgs = importRelayerKeyArguments{} +var addressArgs = relayerAddressArguments{} + +// importRelayerKeyArguments is the struct that holds the arguments for the import command +type importRelayerKeyArguments struct { + network int32 + privateKey string + password string + relayerKeyPath string +} + +// relayerAddressArguments is the struct that holds the arguments for the show command +type relayerAddressArguments struct { + network int32 + password string + relayerKeyPath string +} + +func init() { + RootCmd.AddCommand(CmdImportRelayerKey) + RootCmd.AddCommand(CmdRelayerAddress) + + // resolve default relayer key path + defaultRelayerKeyPath := "~/.zetacored/relayer-keys" + defaultRelayerKeyPath, err := zetaos.ExpandHomeDir(defaultRelayerKeyPath) + if err != nil { + log.Fatal().Err(err).Msg("failed to resolve default relayer key path") + } + + CmdImportRelayerKey.Flags().Int32Var(&importArgs.network, "network", 7, "network id, (7: solana)") + CmdImportRelayerKey.Flags(). + StringVar(&importArgs.privateKey, "private-key", "", "the relayer private key to import") + CmdImportRelayerKey.Flags(). + StringVar(&importArgs.password, "password", "", "the password to encrypt the relayer private key") + CmdImportRelayerKey.Flags(). + StringVar(&importArgs.relayerKeyPath, "relayer-key-path", defaultRelayerKeyPath, "path to relayer keys") + + CmdRelayerAddress.Flags().Int32Var(&addressArgs.network, "network", 7, "network id, (7:solana)") + CmdRelayerAddress.Flags(). + StringVar(&addressArgs.password, "password", "", "the password to decrypt the relayer private key") + CmdRelayerAddress.Flags(). + StringVar(&addressArgs.relayerKeyPath, "relayer-key-path", defaultRelayerKeyPath, "path to relayer keys") +} + +// ImportRelayerKey imports a relayer private key +func ImportRelayerKey(_ *cobra.Command, _ []string) error { + // validate private key and password + if importArgs.privateKey == "" { + return errors.New("must provide a private key") + } + if importArgs.password == "" { + return errors.New("must provide a password") + } + if !keys.IsRelayerPrivateKeyValid(importArgs.privateKey, chains.Network(importArgs.network)) { + return errors.New("invalid private key") + } + + // resolve the relayer key file path + fileName, err := keys.ResolveRelayerKeyFile(importArgs.relayerKeyPath, chains.Network(importArgs.network)) + if err != nil { + return errors.Wrap(err, "failed to resolve relayer key file path") + } + + // create path (owner `rwx` permissions) if it does not exist + keyPath := filepath.Dir(fileName) + if _, err := os.Stat(keyPath); os.IsNotExist(err) { + if err := os.MkdirAll(keyPath, 0o700); err != nil { + return errors.Wrapf(err, "failed to create relayer key path: %s", keyPath) + } + } + + // avoid overwriting existing key file + if zetaos.FileExists(fileName) { + return errors.Errorf( + "relayer key %s already exists, please backup and remove it before importing a new key", + fileName, + ) + } + + // encrypt the private key + ciphertext, err := crypto.EncryptAES256GCMBase64(importArgs.privateKey, importArgs.password) + if err != nil { + return errors.Wrap(err, "private key encryption failed") + } + + // create the relayer key file + err = keys.WriteRelayerKeyToFile(fileName, keys.RelayerKey{PrivateKey: ciphertext}) + if err != nil { + return errors.Wrapf(err, "failed to create relayer key file: %s", fileName) + } + fmt.Printf("successfully imported relayer key: %s\n", fileName) + + return nil +} + +// ShowRelayerAddress shows the relayer address +func ShowRelayerAddress(_ *cobra.Command, _ []string) error { + // try loading the relayer key if present + network := chains.Network(addressArgs.network) + relayerKey, err := keys.LoadRelayerKey(addressArgs.relayerKeyPath, network, addressArgs.password) + if err != nil { + return errors.Wrap(err, "failed to load relayer key") + } + + // relayer key does not exist, return error + if relayerKey == nil { + return fmt.Errorf( + "relayer key not found for network %d in path: %s", + addressArgs.network, + addressArgs.relayerKeyPath, + ) + } + + // resolve the relayer address + networkName, address, err := relayerKey.ResolveAddress(network) + if err != nil { + return errors.Wrap(err, "failed to resolve relayer address") + } + fmt.Printf("relayer address (%s): %s\n", networkName, address) + + return nil +} diff --git a/cmd/zetaclientd/init.go b/cmd/zetaclientd/init.go index 1b58265f90..2d3e67d698 100644 --- a/cmd/zetaclientd/init.go +++ b/cmd/zetaclientd/init.go @@ -1,8 +1,6 @@ package main import ( - "path" - "github.com/rs/zerolog" "github.com/spf13/cobra" @@ -38,7 +36,7 @@ type initArguments struct { KeyringBackend string HsmMode bool HsmHotKey string - SolanaKey string + RelayerKeyPath string } func init() { @@ -72,7 +70,8 @@ func init() { InitCmd.Flags().BoolVar(&initArgs.HsmMode, "hsm-mode", false, "enable hsm signer, default disabled") InitCmd.Flags(). StringVar(&initArgs.HsmHotKey, "hsm-hotkey", "hsm-hotkey", "name of hotkey associated with hardware security module") - InitCmd.Flags().StringVar(&initArgs.SolanaKey, "solana-key", "solana-key.json", "solana key file name") + InitCmd.Flags(). + StringVar(&initArgs.RelayerKeyPath, "relayer-key-path", "~/.zetacored/relayer-keys", "path to relayer keys") } func Initialize(_ *cobra.Command, _ []string) error { @@ -110,16 +109,9 @@ func Initialize(_ *cobra.Command, _ []string) error { configData.KeyringBackend = config.KeyringBackend(initArgs.KeyringBackend) configData.HsmMode = initArgs.HsmMode configData.HsmHotKey = initArgs.HsmHotKey - configData.SolanaKeyFile = initArgs.SolanaKey + configData.RelayerKeyPath = initArgs.RelayerKeyPath configData.ComplianceConfig = testutils.ComplianceConfigTest() - // Save solana test fee payer key file - keyFile := path.Join(rootArgs.zetaCoreHome, initArgs.SolanaKey) - err = createSolanaTestKeyFile(keyFile) - if err != nil { - return err - } - // Save config file return config.Save(&configData, rootArgs.zetaCoreHome) } diff --git a/cmd/zetaclientd/solana_test_key.go b/cmd/zetaclientd/solana_test_key.go deleted file mode 100644 index 12a266dd9d..0000000000 --- a/cmd/zetaclientd/solana_test_key.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "encoding/json" - "os" -) - -// solanaTestKey is a local test private key for Solana -// TODO: use separate keys for each zetaclient in Solana E2E tests -// https://github.com/zeta-chain/node/issues/2614 -var solanaTestKey = []uint8{ - 199, 16, 63, 28, 125, 103, 131, 13, 6, 94, 68, 109, 13, 68, 132, 17, - 71, 33, 216, 51, 49, 103, 146, 241, 245, 162, 90, 228, 71, 177, 32, 199, - 31, 128, 124, 2, 23, 207, 48, 93, 141, 113, 91, 29, 196, 95, 24, 137, - 170, 194, 90, 4, 124, 113, 12, 222, 166, 209, 119, 19, 78, 20, 99, 5, -} - -// createSolanaTestKeyFile creates a solana test key json file -func createSolanaTestKeyFile(keyFile string) error { - // marshal the byte array to JSON - keyBytes, err := json.Marshal(solanaTestKey) - if err != nil { - return err - } - - // create file (or overwrite if it already exists) - // #nosec G304 -- for E2E testing purposes only - file, err := os.Create(keyFile) - if err != nil { - return err - } - defer file.Close() - - // write the key bytes to the file - _, err = file.Write(keyBytes) - return err -} diff --git a/cmd/zetaclientd/start.go b/cmd/zetaclientd/start.go index 13c23b2bac..0c0bbd6585 100644 --- a/cmd/zetaclientd/start.go +++ b/cmd/zetaclientd/start.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "context" "encoding/json" "fmt" @@ -21,7 +20,9 @@ import ( "github.com/spf13/cobra" "github.com/zeta-chain/zetacore/pkg/authz" + "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/constant" + zetaos "github.com/zeta-chain/zetacore/pkg/os" observerTypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/config" @@ -50,10 +51,15 @@ func start(_ *cobra.Command, _ []string) error { SetupConfigForTest() - //Prompt for Hotkey and TSS key-share passwords - hotkeyPass, tssKeyPass, err := promptPasswords() + // Prompt for Hotkey, TSS key-share and relayer key passwords + titles := []string{"HotKey", "TSS", "Solana Relayer Key"} + passwords, err := zetaos.PromptPasswords(titles) if err != nil { - return err + return errors.Wrap(err, "unable to get passwords") + } + hotkeyPass, tssKeyPass, solanaKeyPass := passwords[0], passwords[1], passwords[2] + relayerKeyPasswords := map[string]string{ + chains.Network_solana.String(): solanaKeyPass, } //Load Config file given path @@ -77,7 +83,7 @@ func start(_ *cobra.Command, _ []string) error { masterLogger := logger.Std startLogger := logger.Std.With().Str("module", "startup").Logger() - appContext := zctx.New(cfg, masterLogger) + appContext := zctx.New(cfg, relayerKeyPasswords, masterLogger) ctx := zctx.WithAppContext(context.Background(), appContext) // Wait until zetacore is up @@ -163,7 +169,7 @@ func start(_ *cobra.Command, _ []string) error { startLogger.Debug().Msgf("hotkeyPk %s", hotkeyPk.String()) if len(hotkeyPk.Bytes()) != 32 { errMsg := fmt.Sprintf("key bytes len %d != 32", len(hotkeyPk.Bytes())) - log.Error().Msgf(errMsg) + log.Error().Msg(errMsg) return errors.New(errMsg) } priKey := secp256k1.PrivKey(hotkeyPk.Bytes()[:32]) @@ -407,29 +413,6 @@ func initPreParams(path string) { } } -// promptPasswords() This function will prompt for passwords which will be used to decrypt two key files: -// 1. HotKey -// 2. TSS key-share -func promptPasswords() (string, string, error) { - reader := bufio.NewReader(os.Stdin) - fmt.Print("HotKey Password: ") - hotKeyPass, err := reader.ReadString('\n') - if err != nil { - return "", "", err - } - fmt.Print("TSS Password: ") - TSSKeyPass, err := reader.ReadString('\n') - if err != nil { - return "", "", err - } - - //trim delimiters - hotKeyPass = strings.TrimSuffix(hotKeyPass, "\n") - TSSKeyPass = strings.TrimSuffix(TSSKeyPass, "\n") - - return hotKeyPass, TSSKeyPass, err -} - // isObserverNode checks whether THIS node is an observer node. func isObserverNode(ctx context.Context, client *zetacore.Client) (bool, error) { observers, err := client.GetObserverList(ctx) diff --git a/cmd/zetaclientd/version.go b/cmd/zetaclientd/version.go index 817d2752f7..15886d4b80 100644 --- a/cmd/zetaclientd/version.go +++ b/cmd/zetaclientd/version.go @@ -15,6 +15,6 @@ var VersionCmd = &cobra.Command{ } func Version(_ *cobra.Command, _ []string) error { - fmt.Printf(constant.Version) + fmt.Print(constant.Version) return nil } diff --git a/cmd/zetae2e/config/config.go b/cmd/zetae2e/config/config.go index 8a91bc1f86..ce1b03717b 100644 --- a/cmd/zetae2e/config/config.go +++ b/cmd/zetae2e/config/config.go @@ -88,5 +88,13 @@ func ExportContractsFromRunner(r *runner.E2ERunner, conf config.Config) config.C conf.Contracts.ZEVM.ContextAppAddr = config.DoubleQuotedString(r.ContextAppAddr.Hex()) conf.Contracts.ZEVM.TestDappAddr = config.DoubleQuotedString(r.ZevmTestDAppAddr.Hex()) + // v2 + conf.Contracts.EVM.Gateway = config.DoubleQuotedString(r.GatewayEVMAddr.Hex()) + conf.Contracts.EVM.ERC20CustodyNew = config.DoubleQuotedString(r.ERC20CustodyV2Addr.Hex()) + conf.Contracts.EVM.TestDAppV2Addr = config.DoubleQuotedString(r.TestDAppV2EVMAddr.Hex()) + + conf.Contracts.ZEVM.Gateway = config.DoubleQuotedString(r.GatewayZEVMAddr.Hex()) + conf.Contracts.ZEVM.TestDAppV2Addr = config.DoubleQuotedString(r.TestDAppV2ZEVMAddr.Hex()) + return conf } diff --git a/cmd/zetae2e/config/contracts.go b/cmd/zetae2e/config/contracts.go index aa1e541957..0d554ef480 100644 --- a/cmd/zetae2e/config/contracts.go +++ b/cmd/zetae2e/config/contracts.go @@ -4,21 +4,25 @@ import ( "fmt" "github.com/gagliardetto/solana-go" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - zetaeth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zeta.eth.sol" - zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" - connectorzevm "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zetaconnectorzevm.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" - "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" - uniswapv2router "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + zetaeth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zeta.eth.sol" + zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/wzeta.sol" + connectorzevm "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/zetaconnectorzevm.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" + uniswapv2router "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + erc20custodyv2 "github.com/zeta-chain/protocol-contracts/v2/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/e2e/config" "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" "github.com/zeta-chain/zetacore/e2e/contracts/erc20" "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/pkg/contracts/testdappv2" ) // setContractsFromConfig get EVM contracts from config @@ -211,5 +215,62 @@ func setContractsFromConfig(r *runner.E2ERunner, conf config.Config) error { } } + // v2 contracts + + if c := conf.Contracts.EVM.Gateway; c != "" { + r.GatewayEVMAddr, err = c.AsEVMAddress() + if err != nil { + return fmt.Errorf("invalid GatewayAddr: %w", err) + } + r.GatewayEVM, err = gatewayevm.NewGatewayEVM(r.GatewayEVMAddr, r.EVMClient) + if err != nil { + return err + } + } + + if c := conf.Contracts.EVM.ERC20CustodyNew; c != "" { + r.ERC20CustodyV2Addr, err = c.AsEVMAddress() + if err != nil { + return fmt.Errorf("invalid ERC20CustodyV2Addr: %w", err) + } + r.ERC20CustodyV2, err = erc20custodyv2.NewERC20Custody(r.ERC20CustodyV2Addr, r.EVMClient) + if err != nil { + return err + } + } + + if c := conf.Contracts.EVM.TestDAppV2Addr; c != "" { + r.TestDAppV2EVMAddr, err = c.AsEVMAddress() + if err != nil { + return fmt.Errorf("invalid TestDAppV2Addr: %w", err) + } + r.TestDAppV2EVM, err = testdappv2.NewTestDAppV2(r.TestDAppV2EVMAddr, r.EVMClient) + if err != nil { + return err + } + } + + if c := conf.Contracts.ZEVM.Gateway; c != "" { + r.GatewayZEVMAddr, err = c.AsEVMAddress() + if err != nil { + return fmt.Errorf("invalid GatewayAddr: %w", err) + } + r.GatewayZEVM, err = gatewayzevm.NewGatewayZEVM(r.GatewayZEVMAddr, r.ZEVMClient) + if err != nil { + return err + } + } + + if c := conf.Contracts.ZEVM.TestDAppV2Addr; c != "" { + r.TestDAppV2ZEVMAddr, err = c.AsEVMAddress() + if err != nil { + return fmt.Errorf("invalid TestDAppV2Addr: %w", err) + } + r.TestDAppV2ZEVM, err = testdappv2.NewTestDAppV2(r.TestDAppV2ZEVMAddr, r.EVMClient) + if err != nil { + return err + } + } + return nil } diff --git a/cmd/zetae2e/config/local.yml b/cmd/zetae2e/config/local.yml index 54534124bd..a0205ee000 100644 --- a/cmd/zetae2e/config/local.yml +++ b/cmd/zetae2e/config/local.yml @@ -27,9 +27,11 @@ contracts: connector_zevm: "0x239e96c8f17C85c30100AC26F635Ea15f23E9c67" wzeta: "0x5F0b1a82749cb4E2278EC87F8BF6B618dC71a8bf" test_dapp: "0xA8D5060feb6B456e886F023709A2795373691E63" + gateway: "0xa825eAa55b497AF892faca73a3797046C10B7c23" evm: zeta_eth: "0x733aB8b06DDDEf27Eaa72294B0d7c9cEF7f12db9" connector_eth: "0xD28D6A0b8189305551a0A8bd247a6ECa9CE781Ca" custody: "0xff3135df4F2775f4091b81f4c7B6359CfA07862a" erc20: "0xbD1e64A22B9F92D9Ce81aA9B4b0fFacd80215564" - test_dapp: "0xBFF76e77D56B3C1202107f059425D56f0AEF87Ed" \ No newline at end of file + test_dapp: "0xBFF76e77D56B3C1202107f059425D56f0AEF87Ed" + gateway: "0xF0deebCB0E9C829519C4baa794c5445171973826" \ No newline at end of file diff --git a/cmd/zetae2e/config/localnet.yml b/cmd/zetae2e/config/localnet.yml index 220fb15255..7f7e8c6c73 100644 --- a/cmd/zetae2e/config/localnet.yml +++ b/cmd/zetae2e/config/localnet.yml @@ -58,6 +58,12 @@ policy_accounts: bech32_address: "zeta142ds9x7raljv2qz9euys93e64gjmgdfnc47dwq" evm_address: "0xAa9b029BC3EFe4c50045Cf0902c73aAa25b43533" private_key: "0595CB0CD9BF5264A85A603EC8E43C30ADBB5FD2D9E2EF84C374EA4A65BB616C" +observer_relayer_accounts: + relayer_accounts: + - solana_address: "2qBVcNBZCubcnSR3NyCnFjCfkCVUB3G7ECPoaW5rxVjx" + solana_private_key: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ" + - solana_address: "4kkCV8H38xirwQTkE5kL6FHNtYGHnMQQ7SkCjAxibHFK" + solana_private_key: "5SSv7jWzamtjWNKGiKf3gvCPHcq9mE5x6LhYgzJCKNSxoQ83gFpmMgmg2JS2zdKcBEdwy7y9bvWgX4LBiUpvnrPf" rpcs: zevm: "http://zetacore0:8545" evm: "http://eth:8545" diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index ae7af95374..356a899a9a 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -41,6 +41,8 @@ const ( flagTestTSSMigration = "test-tss-migration" flagSkipBitcoinSetup = "skip-bitcoin-setup" flagSkipHeaderProof = "skip-header-proof" + flagTestV2 = "test-v2" + flagSkipTrackerCheck = "skip-tracker-check" ) var ( @@ -73,6 +75,8 @@ func NewLocalCmd() *cobra.Command { cmd.Flags().Bool(flagSkipBitcoinSetup, false, "set to true to skip bitcoin wallet setup") cmd.Flags().Bool(flagSkipHeaderProof, false, "set to true to skip header proof tests") cmd.Flags().Bool(flagTestTSSMigration, false, "set to true to include a migration test at the end") + cmd.Flags().Bool(flagTestV2, false, "set to true to run tests for v2 contracts") + cmd.Flags().Bool(flagSkipTrackerCheck, false, "set to true to skip tracker check at the end of the tests") return cmd } @@ -94,7 +98,9 @@ func localE2ETest(cmd *cobra.Command, _ []string) { skipSetup = must(cmd.Flags().GetBool(flagSkipSetup)) skipBitcoinSetup = must(cmd.Flags().GetBool(flagSkipBitcoinSetup)) skipHeaderProof = must(cmd.Flags().GetBool(flagSkipHeaderProof)) + skipTrackerCheck = must(cmd.Flags().GetBool(flagSkipTrackerCheck)) testTSSMigration = must(cmd.Flags().GetBool(flagTestTSSMigration)) + testV2 = must(cmd.Flags().GetBool(flagTestV2)) ) logger := runner.NewLogger(verbose, color.FgWhite, "setup") @@ -182,13 +188,26 @@ func localE2ETest(cmd *cobra.Command, _ []string) { startTime := time.Now() deployerRunner.SetupEVM(contractsDeployed, true) - deployerRunner.SetZEVMContracts() + + if testV2 { + deployerRunner.SetupEVMV2() + } + + deployerRunner.SetZEVMSystemContracts() + + if testV2 { + // NOTE: v2 (gateway) setup called here because system contract needs to be set first, then gateway, then zrc20 + deployerRunner.SetZEVMContractsV2() + } + + deployerRunner.SetZEVMZRC20s() + if testSolana { deployerRunner.SetSolanaContracts(conf.AdditionalAccounts.UserSolana.SolanaPrivateKey.String()) } noError(deployerRunner.FundEmissionsPool()) - deployerRunner.MintERC20OnEvm(10000) + deployerRunner.MintERC20OnEvm(1000000) logger.Print("✅ setup completed in %s", time.Since(startTime)) } @@ -297,12 +316,19 @@ func localE2ETest(cmd *cobra.Command, _ []string) { if testAdmin { eg.Go(adminTestRoutine(conf, deployerRunner, verbose, e2etests.TestWhitelistERC20Name, - e2etests.TestRateLimiterName, e2etests.TestPauseZRC20Name, e2etests.TestUpdateBytecodeZRC20Name, e2etests.TestUpdateBytecodeConnectorName, e2etests.TestDepositEtherLiquidityCapName, e2etests.TestCriticalAdminTransactionsName, + e2etests.TestPauseERC20CustodyName, + e2etests.TestMigrateERC20CustodyFundsName, + + // Test the rate limiter functionalities + // this test is currently incomplete and takes 10m to run + // TODO: define assertion, and make more optimized + // https://github.com/zeta-chain/node/issues/2090 + //e2etests.TestRateLimiterName, // TestMigrateChainSupportName tests EVM chain migration. Currently this test doesn't work with Anvil because pre-EIP1559 txs are not supported // See issue below for details @@ -326,9 +352,71 @@ func localE2ETest(cmd *cobra.Command, _ []string) { solanaTests := []string{ e2etests.TestSolanaDepositName, e2etests.TestSolanaWithdrawName, + e2etests.TestSolanaDepositAndCallName, + e2etests.TestSolanaDepositAndCallRefundName, } eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, solanaTests...)) } + if testV2 { + // update the ERC20 custody contract for v2 tests + deployerRunner.UpdateChainParamsERC20CustodyContract() + + //// Test happy paths for gas token workflow + eg.Go(v2TestRoutine(conf, "eth", conf.AdditionalAccounts.UserEther, color.FgHiGreen, deployerRunner, verbose, + e2etests.TestV2ETHDepositName, + e2etests.TestV2ETHDepositAndCallName, + e2etests.TestV2ETHWithdrawName, + e2etests.TestV2ETHWithdrawAndCallName, + e2etests.TestV2ZEVMToEVMCallName, + e2etests.TestV2EVMToZEVMCallName, + )) + + //// Test happy paths for erc20 token workflow + eg.Go(v2TestRoutine(conf, "erc20", conf.AdditionalAccounts.UserERC20, color.FgHiBlue, deployerRunner, verbose, + e2etests.TestV2ETHDepositName, // necessary to pay fees on ZEVM + e2etests.TestV2ERC20DepositName, + e2etests.TestV2ERC20DepositAndCallName, + e2etests.TestV2ERC20WithdrawName, + e2etests.TestV2ERC20WithdrawAndCallName, + )) + + // Test revert cases for gas token workflow + eg.Go( + v2TestRoutine( + conf, + "eth-revert", + conf.AdditionalAccounts.UserZetaTest, + color.FgHiYellow, + deployerRunner, + verbose, + e2etests.TestV2ETHDepositName, // necessary to pay fees on ZEVM and withdraw + e2etests.TestV2ETHDepositAndCallRevertName, + e2etests.TestV2ETHDepositAndCallRevertWithCallName, + e2etests.TestV2ETHWithdrawAndCallRevertName, + e2etests.TestV2ETHWithdrawAndCallRevertWithCallName, + ), + ) + + // Test revert cases for erc20 token workflow + eg.Go( + v2TestRoutine( + conf, + "erc20-revert", + conf.AdditionalAccounts.UserBitcoin, + color.FgHiRed, + deployerRunner, + verbose, + e2etests.TestV2ETHDepositName, // necessary to pay fees on ZEVM + e2etests.TestV2ERC20DepositName, // necessary to have assets to withdraw + e2etests.TestOperationAddLiquidityETHName, // liquidity with gas and ERC20 are necessary for reverts + e2etests.TestOperationAddLiquidityERC20Name, + e2etests.TestV2ERC20DepositAndCallRevertName, + e2etests.TestV2ERC20DepositAndCallRevertWithCallName, + e2etests.TestV2ERC20WithdrawAndCallRevertName, + e2etests.TestV2ERC20WithdrawAndCallRevertWithCallName, + ), + ) + } // while tests are executed, monitor blocks in parallel to check if system txs are on top and they have biggest priority txPriorityErrCh := make(chan error, 1) @@ -358,7 +446,9 @@ func localE2ETest(cmd *cobra.Command, _ []string) { runTSSMigrationTest(deployerRunner, logger, verbose, conf) } // Verify that there are no trackers left over after tests complete - deployerRunner.EnsureNoTrackers() + if !skipTrackerCheck { + deployerRunner.EnsureNoTrackers() + } // print and validate report networkReport, err := deployerRunner.GenerateNetworkReport() if err != nil { diff --git a/cmd/zetae2e/local/v2.go b/cmd/zetae2e/local/v2.go new file mode 100644 index 0000000000..e87785de31 --- /dev/null +++ b/cmd/zetae2e/local/v2.go @@ -0,0 +1,67 @@ +package local + +import ( + "fmt" + "time" + + "github.com/fatih/color" + + "github.com/zeta-chain/zetacore/e2e/config" + "github.com/zeta-chain/zetacore/e2e/e2etests" + "github.com/zeta-chain/zetacore/e2e/runner" +) + +// erc20TestRoutine runs v2 related e2e tests +// TODO: this routine will be broken down in the future and will replace most current tests +// we keep a single routine for v2 for simplicity +// https://github.com/zeta-chain/node/issues/2554 +func v2TestRoutine( + conf config.Config, + name string, + account config.Account, + color color.Attribute, + deployerRunner *runner.E2ERunner, + verbose bool, + testNames ...string, +) func() error { + return func() (err error) { + name = "v2-" + name + + // initialize runner for erc20 test + v2Runner, err := initTestRunner( + name, + conf, + deployerRunner, + account, + runner.NewLogger(verbose, color, name), + runner.WithZetaTxServer(deployerRunner.ZetaTxServer), + ) + if err != nil { + return err + } + + v2Runner.Logger.Print("🏃 starting %s tests", name) + startTime := time.Now() + + // funding the account + txERC20Send := deployerRunner.SendERC20OnEvm(account.EVMAddress(), 10000) + v2Runner.WaitForTxReceiptOnEvm(txERC20Send) + + // run erc20 test + testsToRun, err := v2Runner.GetE2ETestsToRunByName( + e2etests.AllE2ETests, + testNames..., + ) + if err != nil { + return fmt.Errorf("%s tests failed: %v", name, err) + } + + if err := v2Runner.RunE2ETests(testsToRun); err != nil { + return fmt.Errorf("%s tests failed: %v", name, err) + } + + v2Runner.Logger.Print("🍾 %s tests completed in %s", name, time.Since(startTime).String()) + + return err + } +} diff --git a/cmd/zetae2e/stress.go b/cmd/zetae2e/stress.go index b1d3a41bfc..e7b0c169a0 100644 --- a/cmd/zetae2e/stress.go +++ b/cmd/zetae2e/stress.go @@ -15,7 +15,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/fatih/color" "github.com/spf13/cobra" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "google.golang.org/grpc" "github.com/zeta-chain/zetacore/app" @@ -141,7 +141,8 @@ func StressTest(cmd *cobra.Command, _ []string) { switch stressTestArgs.network { case "LOCAL": // deploy and set zevm contract - e2eTest.SetZEVMContracts() + e2eTest.SetZEVMSystemContracts() + e2eTest.SetZEVMZRC20s() // deposit on ZetaChain e2eTest.DepositEther() diff --git a/cmd/zetatool/filterdeposit/evm.go b/cmd/zetatool/filterdeposit/evm.go index 427e0c421c..2fa39b8c90 100644 --- a/cmd/zetatool/filterdeposit/evm.go +++ b/cmd/zetatool/filterdeposit/evm.go @@ -13,8 +13,8 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/nanmu42/etherscan-api" "github.com/spf13/cobra" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.non-eth.sol" "github.com/zeta-chain/zetacore/cmd/zetatool/config" "github.com/zeta-chain/zetacore/pkg/constant" diff --git a/contrib/docker-scripts/start.sh b/contrib/docker-scripts/start.sh index 6e2cebaf1f..6d79effa57 100644 --- a/contrib/docker-scripts/start.sh +++ b/contrib/docker-scripts/start.sh @@ -10,7 +10,7 @@ function load_defaults { export DAEMON_HOME=${DAEMON_HOME:=/root/.zetacored} export NETWORK=${NETWORK:=mainnet} export RESTORE_TYPE=${RESTORE_TYPE:=statesync} - export SNAPSHOT_API=${SNAPSHOT_API:=https://snapshots.zetachain.com} + export SNAPSHOT_API=${SNAPSHOT_API:=https://snapshots.rpc.zetachain.com} export TRUST_HEIGHT_DIFFERENCE_STATE_SYNC=${TRUST_HEIGHT_DIFFERENCE_STATE_SYNC:=40000} export COSMOVISOR_VERSION=${COSMOVISOR_VERSION:=v1.5.0} export CHAIN_ID=${CHAIN_ID:=zetachain_7000-1} @@ -109,8 +109,8 @@ function setup_restore_type { elif [ "${RESTORE_TYPE}" == "snapshot" ]; then if [ "${NETWORK}" == "mainnet" ]; then logt "Get Latest Snapshot URL" - SNAPSHOT_URL=$(curl -s ${SNAPSHOT_API}/latest-snapshot?network=mainnet | jq -r .latest_snapshot) - SNAPSHOT_FILENAME=$(basename "${SNAPSHOT_URL}") + SNAPSHOT_URL=$(curl -s ${SNAPSHOT_API}/mainnet/fullnode/latest.json | jq -r '.snapshots[0].link') + SNAPSHOT_FILENAME=$(curl -s ${SNAPSHOT_API}/mainnet/fullnode/latest.json | jq -r '.snapshots[0].filename') SNAPSHOT_DIR=$(pwd) logt "Download Snapshot from url: ${SNAPSHOT_URL}" curl -o "${SNAPSHOT_FILENAME}" "${SNAPSHOT_URL}" @@ -120,8 +120,8 @@ function setup_restore_type { logt " Cleanup Snapshot" rm -rf ${SNAPSHOT_DIR}/${SNAPSHOT_FILENAME} elif [ "${NETWORK}" == "athens3" ]; then - SNAPSHOT_URL=$(curl -s ${SNAPSHOT_API}/latest-snapshot?network=athens3 | jq -r .latest_snapshot) - SNAPSHOT_FILENAME=$(basename "${SNAPSHOT_URL}") + SNAPSHOT_URL=$(curl -s ${SNAPSHOT_API}/testnet/fullnode/latest.json | jq -r '.snapshots[0].link') + SNAPSHOT_FILENAME=$(curl -s ${SNAPSHOT_API}/testnet/fullnode/latest.json | jq -r '.snapshots[0].filename') SNAPSHOT_DIR=$(pwd) logt "Download Snapshot from url: ${SNAPSHOT_URL}" curl -o "${SNAPSHOT_FILENAME}" "${SNAPSHOT_URL}" @@ -134,8 +134,8 @@ function setup_restore_type { elif [ "${RESTORE_TYPE}" == "snapshot-archive" ]; then if [ "${NETWORK}" == "mainnet" ]; then logt "Get Latest Snapshot URL" - SNAPSHOT_URL=$(curl -s ${SNAPSHOT_API}/latest-archive-snapshot?network=mainnet | jq -r .latest_snapshot) - SNAPSHOT_FILENAME=$(basename "${SNAPSHOT_URL}") + SNAPSHOT_URL=$(curl -s ${SNAPSHOT_API}/mainnet/archive/latest.json | jq -r '.snapshots[0].link') + SNAPSHOT_FILENAME=$(curl -s ${SNAPSHOT_API}/mainnet/archive/latest.json | jq -r '.snapshots[0].filename') SNAPSHOT_DIR=$(pwd) logt "Download Snapshot from url: ${SNAPSHOT_URL}" curl -o "${SNAPSHOT_FILENAME}" "${SNAPSHOT_URL}" @@ -145,8 +145,8 @@ function setup_restore_type { logt " Cleanup Snapshot" rm -rf ${SNAPSHOT_DIR}/${SNAPSHOT_FILENAME} elif [ "${NETWORK}" == "athens3" ]; then - SNAPSHOT_URL=$(curl -s ${SNAPSHOT_API}/latest-archive-snapshot?network=athens3 | jq -r .latest_snapshot) - SNAPSHOT_FILENAME=$(basename "${SNAPSHOT_URL}") + SNAPSHOT_URL=$(curl -s ${SNAPSHOT_API}/testnet/archive/latest.json | jq -r '.snapshots[0].link') + SNAPSHOT_FILENAME=$(curl -s ${SNAPSHOT_API}/testnet/archive/latest.json | jq -r '.snapshots[0].filename') SNAPSHOT_DIR=$(pwd) logt "Download Snapshot from url: ${SNAPSHOT_URL}" curl -o "${SNAPSHOT_FILENAME}" "${SNAPSHOT_URL}" @@ -332,4 +332,4 @@ else logt "Start Network" start_network -fi \ No newline at end of file +fi diff --git a/contrib/localnet/orchestrator/Dockerfile.fastbuild b/contrib/localnet/orchestrator/Dockerfile.fastbuild index 96f068cb31..d260d4a1f0 100644 --- a/contrib/localnet/orchestrator/Dockerfile.fastbuild +++ b/contrib/localnet/orchestrator/Dockerfile.fastbuild @@ -2,13 +2,15 @@ # check=error=true FROM zetanode:latest AS zeta FROM ghcr.io/zeta-chain/ethereum-client-go:v1.10.26 AS geth +FROM ghcr.io/zeta-chain/solana-docker:1.18.15 AS solana FROM ghcr.io/zeta-chain/golang:1.22.5-bookworm AS orchestrator RUN apt update && \ - apt install -yq jq yq curl tmux python3 openssh-server iputils-ping iproute2 && \ + apt install -yq jq yq curl tmux python3 openssh-server iputils-ping iproute2 bind9-host && \ rm -rf /var/lib/apt/lists/* COPY --from=geth /usr/local/bin/geth /usr/local/bin/ +COPY --from=solana /usr/bin/solana /usr/local/bin/ COPY --from=zeta /usr/local/bin/zetacored /usr/local/bin/zetaclientd /usr/local/bin/zetae2e /usr/local/bin/ COPY contrib/localnet/orchestrator/start-zetae2e.sh /work/ diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index dda5b12cd7..4ee8192c6d 100644 --- a/contrib/localnet/orchestrator/start-zetae2e.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -44,52 +44,66 @@ sleep 2 # unlock the default account account address=$(yq -r '.default_account.evm_address' config.yml) echo "funding deployer address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null # unlock erc20 tester accounts address=$(yq -r '.additional_accounts.user_erc20.evm_address' config.yml) echo "funding erc20 address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null # unlock zeta tester accounts address=$(yq -r '.additional_accounts.user_zeta_test.evm_address' config.yml) echo "funding zeta tester address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null # unlock zevm message passing tester accounts address=$(yq -r '.additional_accounts.user_zevm_mp_test.evm_address' config.yml) echo "funding zevm mp tester address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null # unlock bitcoin tester accounts address=$(yq -r '.additional_accounts.user_bitcoin.evm_address' config.yml) echo "funding bitcoin tester address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null # unlock solana tester accounts address=$(yq -r '.additional_accounts.user_solana.evm_address' config.yml) echo "funding solana tester address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null # unlock ethers tester accounts address=$(yq -r '.additional_accounts.user_ether.evm_address' config.yml) echo "funding ether tester address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null # unlock miscellaneous tests accounts address=$(yq -r '.additional_accounts.user_misc.evm_address' config.yml) echo "funding misc tester address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null # unlock admin erc20 tests accounts address=$(yq -r '.additional_accounts.user_admin.evm_address' config.yml) echo "funding admin tester address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null # unlock migration tests accounts address=$(yq -r '.additional_accounts.user_migration.evm_address' config.yml) echo "funding migration tester address ${address} with 10000 Ether" -geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 +geth --exec "eth.sendTransaction({from: eth.coinbase, to: '${address}', value: web3.toWei(10000,'ether')})" attach http://eth:8545 > /dev/null + +# unlock local solana relayer accounts +if host solana > /dev/null; then + solana_url=$(yq -r '.rpcs.solana' config.yml) + solana config set --url "$solana_url" > /dev/null + + relayer=$(yq -r '.observer_relayer_accounts.relayer_accounts[0].solana_address' config.yml) + echo "funding solana relayer address ${relayer} with 100 SOL" + solana airdrop 100 "$relayer" > /dev/null + + relayer=$(yq -r '.observer_relayer_accounts.relayer_accounts[1].solana_address' config.yml) + echo "funding solana relayer address ${relayer} with 100 SOL" + solana airdrop 100 "$relayer" > /dev/null +fi ### Run zetae2e command depending on the option passed @@ -99,9 +113,11 @@ if [ "$LOCALNET_MODE" == "upgrade" ]; then # set upgrade height to 225 by default UPGRADE_HEIGHT=${UPGRADE_HEIGHT:=225} + OLD_VERSION=$(get_zetacored_version) + COMMON_ARGS="--skip-header-proof --skip-tracker-check" if [[ ! -f deployed.yml ]]; then - zetae2e local $E2E_ARGS --setup-only --config config.yml --config-out deployed.yml --skip-header-proof + zetae2e local $E2E_ARGS --setup-only --config config.yml --config-out deployed.yml ${COMMON_ARGS} if [ $? -ne 0 ]; then echo "e2e setup failed" exit 1 @@ -115,7 +131,7 @@ if [ "$LOCALNET_MODE" == "upgrade" ]; then echo "running E2E command to setup the networks and populate the state..." # Use light flag to ensure tests can complete before the upgrade height - zetae2e local $E2E_ARGS --skip-setup --config deployed.yml --light --skip-header-proof + zetae2e local $E2E_ARGS --skip-setup --config deployed.yml --light ${COMMON_ARGS} if [ $? -ne 0 ]; then echo "first e2e failed" exit 1 @@ -123,9 +139,6 @@ if [ "$LOCALNET_MODE" == "upgrade" ]; then fi echo "Waiting for upgrade height..." - - OLD_VERSION=$(get_zetacored_version) - CURRENT_HEIGHT=0 WAIT_HEIGHT=$(( UPGRADE_HEIGHT - 1 )) # wait for upgrade height @@ -157,9 +170,9 @@ if [ "$LOCALNET_MODE" == "upgrade" ]; then # When the upgrade height is greater than 100 for upgrade test, the Bitcoin tests have been run once, therefore the Bitcoin wallet is already set up # Use light flag to skip advanced tests if [ "$UPGRADE_HEIGHT" -lt 100 ]; then - zetae2e local $E2E_ARGS --skip-setup --config deployed.yml --light --skip-header-proof + zetae2e local $E2E_ARGS --skip-setup --config deployed.yml --light ${COMMON_ARGS} else - zetae2e local $E2E_ARGS --skip-setup --config deployed.yml --skip-bitcoin-setup --light --skip-header-proof + zetae2e local $E2E_ARGS --skip-setup --config deployed.yml --skip-bitcoin-setup --light ${COMMON_ARGS} fi ZETAE2E_EXIT_CODE=$? diff --git a/contrib/localnet/scripts/password.file b/contrib/localnet/scripts/password.file index 96b3814661..efedb37b66 100644 --- a/contrib/localnet/scripts/password.file +++ b/contrib/localnet/scripts/password.file @@ -1,2 +1,3 @@ password pass2 +pass_relayerkey diff --git a/contrib/localnet/scripts/start-zetaclientd.sh b/contrib/localnet/scripts/start-zetaclientd.sh index 9250385853..71ca33f589 100755 --- a/contrib/localnet/scripts/start-zetaclientd.sh +++ b/contrib/localnet/scripts/start-zetaclientd.sh @@ -14,6 +14,15 @@ set_sepolia_endpoint() { jq '.EVMChainConfigs."11155111".Endpoint = "http://eth2:8545"' /root/.zetacored/config/zetaclient_config.json > tmp.json && mv tmp.json /root/.zetacored/config/zetaclient_config.json } +# import a relayer private key (e.g. Solana relayer key) +import_relayer_key() { + local num="$1" + + # import solana (network=7) relayer private key + privkey_solana=$(yq -r ".observer_relayer_accounts.relayer_accounts[${num}].solana_private_key" /root/config.yml) + zetaclientd import-relayer-key --network=7 --private-key="$privkey_solana" --password=pass_relayerkey +} + PREPARAMS_PATH="/root/preparams/${HOSTNAME}.json" if [[ -n "${ZETACLIENTD_GEN_PREPARAMS}" ]]; then # generate pre-params as early as possible @@ -54,6 +63,11 @@ done operator=$(cat $HOME/.zetacored/os.json | jq '.ObserverAddress' ) operatorAddress=$(echo "$operator" | tr -d '"') echo "operatorAddress: $operatorAddress" + +# create the path that holds observer relayer private keys (e.g. Solana relayer key) +RELAYER_KEY_PATH="$HOME/.zetacored/relayer-keys" +mkdir -p "${RELAYER_KEY_PATH}" + echo "Start zetaclientd" # skip initialization if the config file already exists (zetaclientd init has already been run) if [[ $HOSTNAME == "zetaclient0" && ! -f ~/.zetacored/config/zetaclient_config.json ]] @@ -61,6 +75,9 @@ then MYIP=$(/sbin/ip -o -4 addr list eth0 | awk '{print $4}' | cut -d/ -f1) zetaclientd init --zetacore-url zetacore0 --chain-id athens_101-1 --operator "$operatorAddress" --log-format=text --public-ip "$MYIP" --keyring-backend "$BACKEND" --pre-params "$PREPARAMS_PATH" + # import relayer private key for zetaclient0 + import_relayer_key 0 + # if eth2 is enabled, set the endpoint in the zetaclient_config.json # in this case, the additional evm is represented with the sepolia chain, we set manually the eth2 endpoint to the sepolia chain (11155111 -> http://eth2:8545) # in /root/.zetacored/config/zetaclient_config.json @@ -81,6 +98,9 @@ then done zetaclientd init --peer "/ip4/172.20.0.21/tcp/6668/p2p/${SEED}" --zetacore-url "$node" --chain-id athens_101-1 --operator "$operatorAddress" --log-format=text --public-ip "$MYIP" --log-level 1 --keyring-backend "$BACKEND" --pre-params "$PREPARAMS_PATH" + # import relayer private key for zetaclient{$num} + import_relayer_key "${num}" + # check if the option is additional-evm # in this case, the additional evm is represented with the sepolia chain, we set manually the eth2 endpoint to the sepolia chain (11155111 -> http://eth2:8545) # in /root/.zetacored/config/zetaclient_config.json diff --git a/contrib/rpc/zetacored/docker-compose.yml b/contrib/rpc/zetacored/docker-compose.yml index fa0f8caacb..50dbd0a731 100644 --- a/contrib/rpc/zetacored/docker-compose.yml +++ b/contrib/rpc/zetacored/docker-compose.yml @@ -8,7 +8,7 @@ services: DAEMON_HOME: "${DAEMON_HOME:-/root/.zetacored}" NETWORK: ${NETWORK:-mainnet} RESTORE_TYPE: "${RESTORE_TYPE:-snapshot}" - SNAPSHOT_API: ${SNAPSHOT_API:-https://snapshots.zetachain.com} + SNAPSHOT_API: ${SNAPSHOT_API:-https://snapshots.rpc.zetachain.com} TRUST_HEIGHT_DIFFERENCE_STATE_SYNC: ${TRUST_HEIGHT_DIFFERENCE_STATE_SYNC:-40000} CHAIN_ID: "${CHAIN_ID:-zetachain_7000-1}" VISOR_NAME: "${VISOR_NAME:-cosmovisor}" diff --git a/contrib/rpc/zetacored/networks/.athens3 b/contrib/rpc/zetacored/networks/.athens3 index d89a99033d..7de4cede3b 100644 --- a/contrib/rpc/zetacored/networks/.athens3 +++ b/contrib/rpc/zetacored/networks/.athens3 @@ -1,7 +1,7 @@ export DAEMON_HOME="/root/.zetacored" export NETWORK=athens3 export RESTORE_TYPE="snapshot" -export SNAPSHOT_API=https://snapshots.zetachain.com +export SNAPSHOT_API=https://snapshots.rpc.zetachain.com export TRUST_HEIGHT_DIFFERENCE_STATE_SYNC=40000 export CHAIN_ID="athens_7001-1" export VISOR_NAME="cosmovisor" @@ -11,4 +11,4 @@ export DAEMON_RESTART_AFTER_UPGRADE="true" export UNSAFE_SKIP_BACKUP="true" export MONIKER=testnet-docker-rpc export RE_DO_START_SEQUENCE="false" -export IS_LOCAL_DEVELOPMENT="false" \ No newline at end of file +export IS_LOCAL_DEVELOPMENT="false" diff --git a/contrib/rpc/zetacored/networks/.athens3-localbuild b/contrib/rpc/zetacored/networks/.athens3-localbuild index b8e0acb6ad..b79c14c220 100644 --- a/contrib/rpc/zetacored/networks/.athens3-localbuild +++ b/contrib/rpc/zetacored/networks/.athens3-localbuild @@ -1,7 +1,7 @@ export DAEMON_HOME="/root/.zetacored" export NETWORK=athens3 export RESTORE_TYPE="snapshot" -export SNAPSHOT_API=https://snapshots.zetachain.com +export SNAPSHOT_API=https://snapshots.rpc.zetachain.com export TRUST_HEIGHT_DIFFERENCE_STATE_SYNC=40000 export CHAIN_ID="athens_7001-1" export VISOR_NAME="cosmovisor" @@ -11,4 +11,4 @@ export DAEMON_RESTART_AFTER_UPGRADE="false" export UNSAFE_SKIP_BACKUP="true" export MONIKER=testnet-docker-rpc export RE_DO_START_SEQUENCE="false" -export IS_LOCAL_DEVELOPMENT="true" \ No newline at end of file +export IS_LOCAL_DEVELOPMENT="true" diff --git a/contrib/rpc/zetacored/networks/.mainnet b/contrib/rpc/zetacored/networks/.mainnet index 4b7420b539..ff260bb5ca 100644 --- a/contrib/rpc/zetacored/networks/.mainnet +++ b/contrib/rpc/zetacored/networks/.mainnet @@ -1,7 +1,7 @@ export DAEMON_HOME="/root/.zetacored" export NETWORK=mainnet export RESTORE_TYPE="snapshot" -export SNAPSHOT_API=https://snapshots.zetachain.com +export SNAPSHOT_API=https://snapshots.rpc.zetachain.com export TRUST_HEIGHT_DIFFERENCE_STATE_SYNC=40000 export CHAIN_ID="zetachain_7000-1" export VISOR_NAME="cosmovisor" diff --git a/contrib/rpc/zetacored/networks/.mainnet-localbuild b/contrib/rpc/zetacored/networks/.mainnet-localbuild index 371e6fe215..381c34bd6d 100644 --- a/contrib/rpc/zetacored/networks/.mainnet-localbuild +++ b/contrib/rpc/zetacored/networks/.mainnet-localbuild @@ -1,7 +1,7 @@ export DAEMON_HOME="/root/.zetacored" export NETWORK=mainnet export RESTORE_TYPE="snapshot" -export SNAPSHOT_API=https://snapshots.zetachain.com +export SNAPSHOT_API=https://snapshots.rpc.zetachain.com export TRUST_HEIGHT_DIFFERENCE_STATE_SYNC=40000 export CHAIN_ID="zetachain_7000-1" export VISOR_NAME="cosmovisor" diff --git a/contrib/rpctest/main.go b/contrib/rpctest/main.go index 227a2c9bc0..f0a5e0d868 100644 --- a/contrib/rpctest/main.go +++ b/contrib/rpctest/main.go @@ -16,8 +16,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" - zetaeth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zeta.eth.sol" - systemcontract "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" + zetaeth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zeta.eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" ) var ( diff --git a/docs/cli/zetacored/cli.md b/docs/cli/zetacored/cli.md index bb8ac9f077..6e8555c166 100644 --- a/docs/cli/zetacored/cli.md +++ b/docs/cli/zetacored/cli.md @@ -9422,7 +9422,7 @@ zetacored tx crosschain vote-gas-price [chain] [price] [priorityFee] [blockNumbe Broadcast message to vote an inbound ``` -zetacored tx crosschain vote-inbound [sender] [senderChainID] [txOrigin] [receiver] [receiverChainID] [amount] [message] [inboundHash] [inBlockHeight] [coinType] [asset] [eventIndex] [flags] +zetacored tx crosschain vote-inbound [sender] [senderChainID] [txOrigin] [receiver] [receiverChainID] [amount] [message] [inboundHash] [inBlockHeight] [coinType] [asset] [eventIndex] [protocolContractVersion] [flags] ``` ### Options diff --git a/docs/development/DEPLOY_NODES.md b/docs/development/DEPLOY_NODES.md index dcbd1f6d40..326407d491 100644 --- a/docs/development/DEPLOY_NODES.md +++ b/docs/development/DEPLOY_NODES.md @@ -54,7 +54,7 @@ The environment variables for both Zetacored and Bitcoin nodes are defined in th | `NETWORK` | Network identifier | `mainnet`, `athens3` | | `CHAIN_ID` | Chain ID for the network | `zetachain_7000-1`, `athens_7001-1` | | `RESTORE_TYPE` | Node restoration method | `snapshot`, `statesync` | -| `SNAPSHOT_API` | API URL for fetching snapshots | `https://snapshots.zetachain.com` | +| `SNAPSHOT_API` | API URL for fetching snapshots | `https://snapshots.rpc.zetachain.com` | #### Example Environment Variables for Bitcoin diff --git a/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml index a91158997d..fbbcfe6b18 100644 --- a/docs/openapi/openapi.swagger.yaml +++ b/docs/openapi/openapi.swagger.yaml @@ -57071,11 +57071,13 @@ definitions: - Gas - ERC20 - Cmd + - NoAssetCall default: Zeta title: |- - Gas: Ether, BNB, Matic, Klay, BTC, etc - ERC20: ERC20 token - - Cmd: not a real coin, rather a command + - Cmd: no asset, used for admin command + - NoAssetCall: no asset, used for contract call crosschainCctxStatus: type: string enum: @@ -57121,6 +57123,10 @@ definitions: items: type: object $ref: '#/definitions/crosschainOutboundParams' + protocol_contract_version: + $ref: '#/definitions/crosschainProtocolContractVersion' + revert_options: + $ref: '#/definitions/crosschainRevertOptions' crosschainGasPrice: type: object properties: @@ -57231,12 +57237,22 @@ definitions: is_removed: type: boolean title: if the tx was removed from the tracker due to no pending cctx + crosschainMsgMigrateERC20CustodyFundsResponse: + type: object + properties: + cctx_index: + type: string crosschainMsgMigrateTssFundsResponse: type: object crosschainMsgRefundAbortedCCTXResponse: type: object crosschainMsgRemoveOutboundTrackerResponse: type: object + crosschainMsgUpdateERC20CustodyPauseStatusResponse: + type: object + properties: + cctx_index: + type: string crosschainMsgUpdateRateLimiterFlagsResponse: type: object crosschainMsgUpdateTssAddressResponse: @@ -57315,6 +57331,15 @@ definitions: items: type: object $ref: '#/definitions/crosschainTxHash' + crosschainProtocolContractVersion: + type: string + enum: + - V1 + - V2 + default: V1 + title: |- + ProtocolContractVersion represents the version of the protocol contract used + for cctx workflow crosschainQueryAllCctxResponse: type: object properties: @@ -57537,6 +57562,21 @@ definitions: type: object $ref: '#/definitions/crosschainConversion' title: conversion in azeta per token + crosschainRevertOptions: + type: object + properties: + revert_address: + type: string + call_on_revert: + type: boolean + abort_address: + type: string + revert_message: + type: string + format: byte + revert_gas_limit: + type: string + title: RevertOptions represents the options for reverting a cctx crosschainTxFinalizationStatus: type: string enum: @@ -58421,6 +58461,10 @@ definitions: format: int64 isAbortRefunded: type: boolean + created_timestamp: + type: string + format: int64 + description: when the CCTX was created. only populated on new transactions. zetacoreemissionsParams: type: object properties: diff --git a/docs/spec/crosschain/messages.md b/docs/spec/crosschain/messages.md index feb02afc1f..f2fba7cd1d 100644 --- a/docs/spec/crosschain/messages.md +++ b/docs/spec/crosschain/messages.md @@ -187,6 +187,8 @@ message MsgVoteInbound { string tx_origin = 13; string asset = 14; uint64 event_index = 15; + ProtocolContractVersion protocol_contract_version = 16; + RevertOptions revert_options = 17; } ``` @@ -272,3 +274,29 @@ message MsgUpdateRateLimiterFlags { } ``` +## MsgMigrateERC20CustodyFunds + +MigrateERC20CustodyFunds migrates the funds from the current ERC20Custody contract to the new ERC20Custody contract + +```proto +message MsgMigrateERC20CustodyFunds { + string creator = 1; + int64 chain_id = 2; + string new_custody_address = 3; + string erc20_address = 4; + string amount = 5; +} +``` + +## MsgUpdateERC20CustodyPauseStatus + +UpdateERC20CustodyPauseStatus creates a admin cmd cctx to update the pause status of the ERC20 custody contract + +```proto +message MsgUpdateERC20CustodyPauseStatus { + string creator = 1; + int64 chain_id = 2; + bool pause = 3; +} +``` + diff --git a/e2e/config/config.go b/e2e/config/config.go index 2be878b444..699a616c3c 100644 --- a/e2e/config/config.go +++ b/e2e/config/config.go @@ -41,12 +41,13 @@ func (s DoubleQuotedString) AsEVMAddress() (ethcommon.Address, error) { // Config contains the configuration for the e2e test type Config struct { // Default account to use when running tests and running setup - DefaultAccount Account `yaml:"default_account"` - AdditionalAccounts AdditionalAccounts `yaml:"additional_accounts"` - PolicyAccounts PolicyAccounts `yaml:"policy_accounts"` - RPCs RPCs `yaml:"rpcs"` - Contracts Contracts `yaml:"contracts"` - ZetaChainID string `yaml:"zeta_chain_id"` + DefaultAccount Account `yaml:"default_account"` + AdditionalAccounts AdditionalAccounts `yaml:"additional_accounts"` + PolicyAccounts PolicyAccounts `yaml:"policy_accounts"` + ObserverRelayerAccounts ObserverRelayerAccounts `yaml:"observer_relayer_accounts"` + RPCs RPCs `yaml:"rpcs"` + Contracts Contracts `yaml:"contracts"` + ZetaChainID string `yaml:"zeta_chain_id"` } // Account contains configuration for an account @@ -54,6 +55,7 @@ type Account struct { RawBech32Address DoubleQuotedString `yaml:"bech32_address"` RawEVMAddress DoubleQuotedString `yaml:"evm_address"` RawPrivateKey DoubleQuotedString `yaml:"private_key"` + SolanaAddress DoubleQuotedString `yaml:"solana_address"` SolanaPrivateKey DoubleQuotedString `yaml:"solana_private_key"` } @@ -77,6 +79,12 @@ type PolicyAccounts struct { AdminPolicyAccount Account `yaml:"admin_policy_account"` } +// ObserverRelayerAccounts are the accounts used by the observers to interact with gateway contracts in non-EVM chains (e.g. Solana) +type ObserverRelayerAccounts struct { + // RelayerAccounts contains two relayer accounts used by zetaclient0 and zetaclient1 + RelayerAccounts [2]Account `yaml:"relayer_accounts"` +} + // RPCs contains the configuration for the RPC endpoints type RPCs struct { Zevm string `yaml:"zevm"` @@ -116,6 +124,9 @@ type EVM struct { CustodyAddr DoubleQuotedString `yaml:"custody"` ERC20 DoubleQuotedString `yaml:"erc20"` TestDappAddr DoubleQuotedString `yaml:"test_dapp"` + Gateway DoubleQuotedString `yaml:"gateway"` + ERC20CustodyNew DoubleQuotedString `yaml:"erc20_custody_new"` + TestDAppV2Addr DoubleQuotedString `yaml:"test_dapp_v2"` } // ZEVM contains the addresses of predeployed contracts on the zEVM chain @@ -132,6 +143,8 @@ type ZEVM struct { ZEVMSwapAppAddr DoubleQuotedString `yaml:"zevm_swap_app"` ContextAppAddr DoubleQuotedString `yaml:"context_app"` TestDappAddr DoubleQuotedString `yaml:"test_dapp"` + Gateway DoubleQuotedString `yaml:"gateway"` + TestDAppV2Addr DoubleQuotedString `yaml:"test_dapp_v2"` } // DefaultConfig returns the default config using values for localnet testing diff --git a/e2e/contracts/testzrc20/TestZRC20.abi b/e2e/contracts/testzrc20/TestZRC20.abi index ec8cc79f66..065326c8f7 100644 --- a/e2e/contracts/testzrc20/TestZRC20.abi +++ b/e2e/contracts/testzrc20/TestZRC20.abi @@ -431,6 +431,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "gatewayAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/e2e/contracts/testzrc20/TestZRC20.bin b/e2e/contracts/testzrc20/TestZRC20.bin index 9e74c2aaa3..1592405f01 100644 --- a/e2e/contracts/testzrc20/TestZRC20.bin +++ b/e2e/contracts/testzrc20/TestZRC20.bin @@ -1 +1 @@ -60c06040523480156200001157600080fd5b5060405162002566380380620025668339818101604052810190620000379190620000e1565b816080818152505080600281111562000055576200005462000128565b5b60a08160028111156200006d576200006c62000128565b5b81525050505062000157565b600080fd5b6000819050919050565b62000093816200007e565b81146200009f57600080fd5b50565b600081519050620000b38162000088565b92915050565b60038110620000c757600080fd5b50565b600081519050620000db81620000b9565b92915050565b60008060408385031215620000fb57620000fa62000079565b5b60006200010b85828601620000a2565b92505060206200011e85828601620000ca565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60805160a0516123db6200018b6000396000610aa40152600081816109ee01528181610f59015261107e01526123db6000f3fe608060405234801561001057600080fd5b50600436106101a95760003560e01c806385e1f4d0116100f9578063c701262611610097578063dd62ed3e11610071578063dd62ed3e146104ff578063eddeb1231461052f578063f2441b321461054b578063f687d12a14610569576101a9565b8063c701262614610494578063c835d7cc146104c4578063d9eeebed146104e0576101a9565b8063a457c2d7116100d3578063a457c2d7146103f8578063a7605f4514610428578063a9059cbb14610446578063b92894ba14610476576101a9565b806385e1f4d01461039e57806395d89b41146103bc578063a3413d03146103da576101a9565b8063395093511161016657806347e7ef241161014057806347e7ef24146103045780634d8943bb1461033457806370a0823114610352578063732bb0e414610382576101a9565b806339509351146102865780633ce4a5bc146102b657806342966c68146102d4576101a9565b806306fdde03146101ae578063091d2788146101cc578063095ea7b3146101ea57806318160ddd1461021a57806323b872dd14610238578063313ce56714610268575b600080fd5b6101b6610585565b6040516101c39190611b20565b60405180910390f35b6101d4610617565b6040516101e19190611b5b565b60405180910390f35b61020460048036038101906101ff9190611c14565b61061d565b6040516102119190611c6f565b60405180910390f35b61022261063b565b60405161022f9190611b5b565b60405180910390f35b610252600480360381019061024d9190611c8a565b610645565b60405161025f9190611c6f565b60405180910390f35b61027061073d565b60405161027d9190611cf9565b60405180910390f35b6102a0600480360381019061029b9190611c14565b610754565b6040516102ad9190611c6f565b60405180910390f35b6102be6107fa565b6040516102cb9190611d23565b60405180910390f35b6102ee60048036038101906102e99190611d3e565b610812565b6040516102fb9190611c6f565b60405180910390f35b61031e60048036038101906103199190611c14565b610827565b60405161032b9190611c6f565b60405180910390f35b61033c610993565b6040516103499190611b5b565b60405180910390f35b61036c60048036038101906103679190611d6b565b610999565b6040516103799190611b5b565b60405180910390f35b61039c60048036038101906103979190611d3e565b6109e2565b005b6103a66109ec565b6040516103b39190611b5b565b60405180910390f35b6103c4610a10565b6040516103d19190611b20565b60405180910390f35b6103e2610aa2565b6040516103ef9190611e0f565b60405180910390f35b610412600480360381019061040d9190611c14565b610ac6565b60405161041f9190611c6f565b60405180910390f35b610430610c29565b60405161043d9190611b5b565b60405180910390f35b610460600480360381019061045b9190611c14565b610c2f565b60405161046d9190611c6f565b60405180910390f35b61047e610c4d565b60405161048b9190611b20565b60405180910390f35b6104ae60048036038101906104a99190611f5f565b610cdb565b6040516104bb9190611c6f565b60405180910390f35b6104de60048036038101906104d99190611d6b565b610e22565b005b6104e8610f15565b6040516104f6929190611fbb565b60405180910390f35b61051960048036038101906105149190611fe4565b611162565b6040516105269190611b5b565b60405180910390f35b61054960048036038101906105449190611d3e565b6111e9565b005b6105536112a3565b6040516105609190611d23565b60405180910390f35b610583600480360381019061057e9190611d3e565b6112c7565b005b60606006805461059490612053565b80601f01602080910402602001604051908101604052809291908181526020018280546105c090612053565b801561060d5780601f106105e25761010080835404028352916020019161060d565b820191906000526020600020905b8154815290600101906020018083116105f057829003601f168201915b5050505050905090565b60015481565b600061063161062a611381565b8484611389565b6001905092915050565b6000600554905090565b6000610652848484611540565b6000600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600061069d611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610714576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61073185610720611381565b858461072c91906120b3565b611389565b60019150509392505050565b6000600860009054906101000a900460ff16905090565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006107a0611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107e991906120e7565b925050819055506001905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab81565b600061081e338361179a565b60019050919050565b600073735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156108c5575060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614155b156108fc576040517fddb5de5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6109068383611951565b8273ffffffffffffffffffffffffffffffffffffffff167f67fc7bdaed5b0ec550d8706b87d60568ab70c6b781263c70101d54cd1564aab373735b14bb79463307aacbed86daf3322b1e6226ab6040516020016109639190612163565b604051602081830303815290604052846040516109819291906121d3565b60405180910390a26001905092915050565b60025481565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b8060098190555050565b7f000000000000000000000000000000000000000000000000000000000000000081565b606060078054610a1f90612053565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4b90612053565b8015610a985780601f10610a6d57610100808354040283529160200191610a98565b820191906000526020600020905b815481529060010190602001808311610a7b57829003601f168201915b5050505050905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610b12611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610b85576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610bcf611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610c1891906120b3565b925050819055506001905092915050565b60095481565b6000610c43610c3c611381565b8484611540565b6001905092915050565b600a8054610c5a90612053565b80601f0160208091040260200160405190810160405280929190818152602001828054610c8690612053565b8015610cd35780601f10610ca857610100808354040283529160200191610cd3565b820191906000526020600020905b815481529060010190602001808311610cb657829003601f168201915b505050505081565b6000806000610ce8610f15565b915091508173ffffffffffffffffffffffffffffffffffffffff166323b872dd3373735b14bb79463307aacbed86daf3322b1e6226ab846040518463ffffffff1660e01b8152600401610d3d93929190612203565b6020604051808303816000875af1158015610d5c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d809190612266565b610db6576040517f0a7cd6d600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dc0338561179a565b3373ffffffffffffffffffffffffffffffffffffffff167f9ffbffc04a397460ee1dbe8c9503e098090567d6b7f4b3c02a8617d800b6d955868684600254604051610e0e9493929190612293565b60405180910390a260019250505092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e9b576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fd55614e962c5fd6ece71614f6348d702468a997a394dd5e5c1677950226d97ae81604051610f0a9190611d23565b60405180910390a150565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630be155477f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b8152600401610f949190611b5b565b602060405180830381865afa158015610fb1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fd591906122f4565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361103d576040517f78fff39600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d7fd7afb7f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b81526004016110b99190611b5b565b602060405180830381865afa1580156110d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110fa9190612336565b905060008103611136576040517fe661aed000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600254600154836111499190612363565b61115391906120e7565b90508281945094505050509091565b6000600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611262576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806002819055507fef13af88e424b5d15f49c77758542c1938b08b8b95b91ed0751f98ba99000d8f816040516112989190611b5b565b60405180910390a150565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611340576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001819055507fff5788270f43bfc1ca41c503606d2594aa3023a1a7547de403a3e2f146a4a80a816040516113769190611b5b565b60405180910390a150565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036113ef576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611455576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516115339190611b5b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036115a6576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361160c576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561168a576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161169691906120b3565b600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461172891906120e7565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161178c9190611b5b565b60405180910390a350505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611800576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561187e576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161188a91906120b3565b600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600560008282546118df91906120b3565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516119449190611b5b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036119b7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80600560008282546119c991906120e7565b9250508190555080600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611a1f91906120e7565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611a849190611b5b565b60405180910390a35050565b600081519050919050565b600082825260208201905092915050565b60005b83811015611aca578082015181840152602081019050611aaf565b60008484015250505050565b6000601f19601f8301169050919050565b6000611af282611a90565b611afc8185611a9b565b9350611b0c818560208601611aac565b611b1581611ad6565b840191505092915050565b60006020820190508181036000830152611b3a8184611ae7565b905092915050565b6000819050919050565b611b5581611b42565b82525050565b6000602082019050611b706000830184611b4c565b92915050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000611bb582611b8a565b9050919050565b611bc581611baa565b8114611bd057600080fd5b50565b600081359050611be281611bbc565b92915050565b611bf181611b42565b8114611bfc57600080fd5b50565b600081359050611c0e81611be8565b92915050565b60008060408385031215611c2b57611c2a611b80565b5b6000611c3985828601611bd3565b9250506020611c4a85828601611bff565b9150509250929050565b60008115159050919050565b611c6981611c54565b82525050565b6000602082019050611c846000830184611c60565b92915050565b600080600060608486031215611ca357611ca2611b80565b5b6000611cb186828701611bd3565b9350506020611cc286828701611bd3565b9250506040611cd386828701611bff565b9150509250925092565b600060ff82169050919050565b611cf381611cdd565b82525050565b6000602082019050611d0e6000830184611cea565b92915050565b611d1d81611baa565b82525050565b6000602082019050611d386000830184611d14565b92915050565b600060208284031215611d5457611d53611b80565b5b6000611d6284828501611bff565b91505092915050565b600060208284031215611d8157611d80611b80565b5b6000611d8f84828501611bd3565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60038110611dd857611dd7611d98565b5b50565b6000819050611de982611dc7565b919050565b6000611df982611ddb565b9050919050565b611e0981611dee565b82525050565b6000602082019050611e246000830184611e00565b92915050565b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b611e6c82611ad6565b810181811067ffffffffffffffff82111715611e8b57611e8a611e34565b5b80604052505050565b6000611e9e611b76565b9050611eaa8282611e63565b919050565b600067ffffffffffffffff821115611eca57611ec9611e34565b5b611ed382611ad6565b9050602081019050919050565b82818337600083830152505050565b6000611f02611efd84611eaf565b611e94565b905082815260208101848484011115611f1e57611f1d611e2f565b5b611f29848285611ee0565b509392505050565b600082601f830112611f4657611f45611e2a565b5b8135611f56848260208601611eef565b91505092915050565b60008060408385031215611f7657611f75611b80565b5b600083013567ffffffffffffffff811115611f9457611f93611b85565b5b611fa085828601611f31565b9250506020611fb185828601611bff565b9150509250929050565b6000604082019050611fd06000830185611d14565b611fdd6020830184611b4c565b9392505050565b60008060408385031215611ffb57611ffa611b80565b5b600061200985828601611bd3565b925050602061201a85828601611bd3565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061206b57607f821691505b60208210810361207e5761207d612024565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006120be82611b42565b91506120c983611b42565b92508282039050818111156120e1576120e0612084565b5b92915050565b60006120f282611b42565b91506120fd83611b42565b925082820190508082111561211557612114612084565b5b92915050565b60008160601b9050919050565b60006121338261211b565b9050919050565b600061214582612128565b9050919050565b61215d61215882611baa565b61213a565b82525050565b600061216f828461214c565b60148201915081905092915050565b600081519050919050565b600082825260208201905092915050565b60006121a58261217e565b6121af8185612189565b93506121bf818560208601611aac565b6121c881611ad6565b840191505092915050565b600060408201905081810360008301526121ed818561219a565b90506121fc6020830184611b4c565b9392505050565b60006060820190506122186000830186611d14565b6122256020830185611d14565b6122326040830184611b4c565b949350505050565b61224381611c54565b811461224e57600080fd5b50565b6000815190506122608161223a565b92915050565b60006020828403121561227c5761227b611b80565b5b600061228a84828501612251565b91505092915050565b600060808201905081810360008301526122ad818761219a565b90506122bc6020830186611b4c565b6122c96040830185611b4c565b6122d66060830184611b4c565b95945050505050565b6000815190506122ee81611bbc565b92915050565b60006020828403121561230a57612309611b80565b5b6000612318848285016122df565b91505092915050565b60008151905061233081611be8565b92915050565b60006020828403121561234c5761234b611b80565b5b600061235a84828501612321565b91505092915050565b600061236e82611b42565b915061237983611b42565b925082820261238781611b42565b9150828204841483151761239e5761239d612084565b5b509291505056fea264697066735822122011fda4fba218fc7f911b68b0418884c426f5ddb324eb1572e2e793fcb60edf6c64736f6c63430008150033 +60c06040523480156200001157600080fd5b5060405162002644380380620026448339818101604052810190620000379190620000aa565b8160808181525050806002811115620000555762000054620000fb565b5b60a08160028111156200006d576200006c620000fb565b5b60f81b8152505050506200015a565b6000815190506200008d816200012f565b92915050565b600081519050620000a48162000140565b92915050565b60008060408385031215620000c457620000c36200012a565b5b6000620000d48582860162000093565b9250506020620000e7858286016200007c565b9150509250929050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600080fd5b600381106200013d57600080fd5b50565b6200014b81620000f1565b81146200015757600080fd5b50565b60805160a05160f81c6124b3620001916000396000610b03015260008181610a2701528181610fc701526110fc01526124b36000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80638b851b95116100f9578063c701262611610097578063dd62ed3e11610071578063dd62ed3e14610538578063eddeb12314610568578063f2441b3214610584578063f687d12a146105a2576101c4565b8063c7012626146104cd578063c835d7cc146104fd578063d9eeebed14610519576101c4565b8063a457c2d7116100d3578063a457c2d714610431578063a7605f4514610461578063a9059cbb1461047f578063b92894ba146104af576101c4565b80638b851b95146103d757806395d89b41146103f5578063a3413d0314610413576101c4565b80633ce4a5bc116101665780634d8943bb116101405780634d8943bb1461034f57806370a082311461036d578063732bb0e41461039d57806385e1f4d0146103b9576101c4565b80633ce4a5bc146102d157806342966c68146102ef57806347e7ef241461031f576101c4565b806318160ddd116101a257806318160ddd1461023557806323b872dd14610253578063313ce5671461028357806339509351146102a1576101c4565b806306fdde03146101c9578063091d2788146101e7578063095ea7b314610205575b600080fd5b6101d16105be565b6040516101de9190612029565b60405180910390f35b6101ef610650565b6040516101fc919061204b565b60405180910390f35b61021f600480360381019061021a9190611cea565b610656565b60405161022c9190611f77565b60405180910390f35b61023d610674565b60405161024a919061204b565b60405180910390f35b61026d60048036038101906102689190611c97565b61067e565b60405161027a9190611f77565b60405180910390f35b61028b610776565b6040516102989190612066565b60405180910390f35b6102bb60048036038101906102b69190611cea565b61078d565b6040516102c89190611f77565b60405180910390f35b6102d9610833565b6040516102e69190611efc565b60405180910390f35b61030960048036038101906103049190611db3565b61084b565b6040516103169190611f77565b60405180910390f35b61033960048036038101906103349190611cea565b610860565b6040516103469190611f77565b60405180910390f35b6103576109cc565b604051610364919061204b565b60405180910390f35b61038760048036038101906103829190611bfd565b6109d2565b604051610394919061204b565b60405180910390f35b6103b760048036038101906103b29190611db3565b610a1b565b005b6103c1610a25565b6040516103ce919061204b565b60405180910390f35b6103df610a49565b6040516103ec9190611efc565b60405180910390f35b6103fd610a6f565b60405161040a9190612029565b60405180910390f35b61041b610b01565b604051610428919061200e565b60405180910390f35b61044b60048036038101906104469190611cea565b610b25565b6040516104589190611f77565b60405180910390f35b610469610c88565b604051610476919061204b565b60405180910390f35b61049960048036038101906104949190611cea565b610c8e565b6040516104a69190611f77565b60405180910390f35b6104b7610cac565b6040516104c49190612029565b60405180910390f35b6104e760048036038101906104e29190611d57565b610d3a565b6040516104f49190611f77565b60405180910390f35b61051760048036038101906105129190611bfd565b610e90565b005b610521610f83565b60405161052f929190611f4e565b60405180910390f35b610552600480360381019061054d9190611c57565b6111f0565b60405161055f919061204b565b60405180910390f35b610582600480360381019061057d9190611db3565b611277565b005b61058c611331565b6040516105999190611efc565b60405180910390f35b6105bc60048036038101906105b79190611db3565b611355565b005b6060600680546105cd906122af565b80601f01602080910402602001604051908101604052809291908181526020018280546105f9906122af565b80156106465780601f1061061b57610100808354040283529160200191610646565b820191906000526020600020905b81548152906001019060200180831161062957829003601f168201915b5050505050905090565b60015481565b600061066a61066361140f565b8484611417565b6001905092915050565b6000600554905090565b600061068b8484846115d0565b6000600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006106d661140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508281101561074d576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61076a8561075961140f565b858461076591906121bf565b611417565b60019150509392505050565b6000600860009054906101000a900460ff16905090565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006107d961140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610822919061210f565b925050819055506001905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab81565b6000610857338361182c565b60019050919050565b600073735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156108fe575060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614155b15610935576040517fddb5de5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093f83836119e4565b8273ffffffffffffffffffffffffffffffffffffffff167f67fc7bdaed5b0ec550d8706b87d60568ab70c6b781263c70101d54cd1564aab373735b14bb79463307aacbed86daf3322b1e6226ab60405160200161099c9190611ee1565b604051602081830303815290604052846040516109ba929190611f92565b60405180910390a26001905092915050565b60025481565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b8060098190555050565b7f000000000000000000000000000000000000000000000000000000000000000081565b600860019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b606060078054610a7e906122af565b80601f0160208091040260200160405190810160405280929190818152602001828054610aaa906122af565b8015610af75780601f10610acc57610100808354040283529160200191610af7565b820191906000526020600020905b815481529060010190602001808311610ada57829003601f168201915b5050505050905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610b7161140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610be4576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610c2e61140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610c7791906121bf565b925050819055506001905092915050565b60095481565b6000610ca2610c9b61140f565b84846115d0565b6001905092915050565b600a8054610cb9906122af565b80601f0160208091040260200160405190810160405280929190818152602001828054610ce5906122af565b8015610d325780601f10610d0757610100808354040283529160200191610d32565b820191906000526020600020905b815481529060010190602001808311610d1557829003601f168201915b505050505081565b6000806000610d47610f83565b915091508173ffffffffffffffffffffffffffffffffffffffff166323b872dd3373735b14bb79463307aacbed86daf3322b1e6226ab846040518463ffffffff1660e01b8152600401610d9c93929190611f17565b602060405180830381600087803b158015610db657600080fd5b505af1158015610dca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dee9190611d2a565b610e24576040517f0a7cd6d600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e2e338561182c565b3373ffffffffffffffffffffffffffffffffffffffff167f9ffbffc04a397460ee1dbe8c9503e098090567d6b7f4b3c02a8617d800b6d955868684600254604051610e7c9493929190611fc2565b60405180910390a260019250505092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610f09576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fd55614e962c5fd6ece71614f6348d702468a997a394dd5e5c1677950226d97ae81604051610f789190611efc565b60405180910390a150565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630be155477f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b8152600401611002919061204b565b60206040518083038186803b15801561101a57600080fd5b505afa15801561102e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110529190611c2a565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156110bb576040517f78fff39600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d7fd7afb7f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b8152600401611137919061204b565b60206040518083038186803b15801561114f57600080fd5b505afa158015611163573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111879190611de0565b905060008114156111c4576040517fe661aed000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600254600154836111d79190612165565b6111e1919061210f565b90508281945094505050509091565b6000600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146112f0576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806002819055507fef13af88e424b5d15f49c77758542c1938b08b8b95b91ed0751f98ba99000d8f81604051611326919061204b565b60405180910390a150565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146113ce576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001819055507fff5788270f43bfc1ca41c503606d2594aa3023a1a7547de403a3e2f146a4a80a81604051611404919061204b565b60405180910390a150565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561147e576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156114e5576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516115c3919061204b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611637576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561169e576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561171c576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161172891906121bf565b600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546117ba919061210f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161181e919061204b565b60405180910390a350505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611893576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611911576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161191d91906121bf565b600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816005600082825461197291906121bf565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516119d7919061204b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611a4b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060056000828254611a5d919061210f565b9250508190555080600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611ab3919061210f565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611b18919061204b565b60405180910390a35050565b6000611b37611b32846120a6565b612081565b905082815260208101848484011115611b5357611b526123f7565b5b611b5e84828561226d565b509392505050565b600081359050611b7581612438565b92915050565b600081519050611b8a81612438565b92915050565b600081519050611b9f8161244f565b92915050565b600082601f830112611bba57611bb96123f2565b5b8135611bca848260208601611b24565b91505092915050565b600081359050611be281612466565b92915050565b600081519050611bf781612466565b92915050565b600060208284031215611c1357611c12612401565b5b6000611c2184828501611b66565b91505092915050565b600060208284031215611c4057611c3f612401565b5b6000611c4e84828501611b7b565b91505092915050565b60008060408385031215611c6e57611c6d612401565b5b6000611c7c85828601611b66565b9250506020611c8d85828601611b66565b9150509250929050565b600080600060608486031215611cb057611caf612401565b5b6000611cbe86828701611b66565b9350506020611ccf86828701611b66565b9250506040611ce086828701611bd3565b9150509250925092565b60008060408385031215611d0157611d00612401565b5b6000611d0f85828601611b66565b9250506020611d2085828601611bd3565b9150509250929050565b600060208284031215611d4057611d3f612401565b5b6000611d4e84828501611b90565b91505092915050565b60008060408385031215611d6e57611d6d612401565b5b600083013567ffffffffffffffff811115611d8c57611d8b6123fc565b5b611d9885828601611ba5565b9250506020611da985828601611bd3565b9150509250929050565b600060208284031215611dc957611dc8612401565b5b6000611dd784828501611bd3565b91505092915050565b600060208284031215611df657611df5612401565b5b6000611e0484828501611be8565b91505092915050565b611e16816121f3565b82525050565b611e2d611e28826121f3565b612312565b82525050565b611e3c81612205565b82525050565b6000611e4d826120d7565b611e5781856120ed565b9350611e6781856020860161227c565b611e7081612406565b840191505092915050565b611e848161225b565b82525050565b6000611e95826120e2565b611e9f81856120fe565b9350611eaf81856020860161227c565b611eb881612406565b840191505092915050565b611ecc81612244565b82525050565b611edb8161224e565b82525050565b6000611eed8284611e1c565b60148201915081905092915050565b6000602082019050611f116000830184611e0d565b92915050565b6000606082019050611f2c6000830186611e0d565b611f396020830185611e0d565b611f466040830184611ec3565b949350505050565b6000604082019050611f636000830185611e0d565b611f706020830184611ec3565b9392505050565b6000602082019050611f8c6000830184611e33565b92915050565b60006040820190508181036000830152611fac8185611e42565b9050611fbb6020830184611ec3565b9392505050565b60006080820190508181036000830152611fdc8187611e42565b9050611feb6020830186611ec3565b611ff86040830185611ec3565b6120056060830184611ec3565b95945050505050565b60006020820190506120236000830184611e7b565b92915050565b600060208201905081810360008301526120438184611e8a565b905092915050565b60006020820190506120606000830184611ec3565b92915050565b600060208201905061207b6000830184611ed2565b92915050565b600061208b61209c565b905061209782826122e1565b919050565b6000604051905090565b600067ffffffffffffffff8211156120c1576120c06123c3565b5b6120ca82612406565b9050602081019050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600061211a82612244565b915061212583612244565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561215a57612159612336565b5b828201905092915050565b600061217082612244565b915061217b83612244565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156121b4576121b3612336565b5b828202905092915050565b60006121ca82612244565b91506121d583612244565b9250828210156121e8576121e7612336565b5b828203905092915050565b60006121fe82612224565b9050919050565b60008115159050919050565b600081905061221f82612424565b919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b600061226682612211565b9050919050565b82818337600083830152505050565b60005b8381101561229a57808201518184015260208101905061227f565b838111156122a9576000848401525b50505050565b600060028204905060018216806122c757607f821691505b602082108114156122db576122da612394565b5b50919050565b6122ea82612406565b810181811067ffffffffffffffff82111715612309576123086123c3565b5b80604052505050565b600061231d82612324565b9050919050565b600061232f82612417565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b60008160601b9050919050565b6003811061243557612434612365565b5b50565b612441816121f3565b811461244c57600080fd5b50565b61245881612205565b811461246357600080fd5b50565b61246f81612244565b811461247a57600080fd5b5056fea2646970667358221220b8278837e775bf149b356e2bff0f3e4931577c5c8ff848545ce78afb4e0ce8cb64736f6c63430008070033 diff --git a/e2e/contracts/testzrc20/TestZRC20.go b/e2e/contracts/testzrc20/TestZRC20.go index aef95ec0d1..e874699679 100644 --- a/e2e/contracts/testzrc20/TestZRC20.go +++ b/e2e/contracts/testzrc20/TestZRC20.go @@ -31,8 +31,8 @@ var ( // TestZRC20MetaData contains all meta data concerning the TestZRC20 contract. var TestZRC20MetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"chainid_\",\"type\":\"uint256\"},{\"internalType\":\"enumCoinType\",\"name\":\"coinType_\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"CallerIsNotFungibleModule\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"GasFeeTransferFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LowAllowance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LowBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroGasCoin\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroGasPrice\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"from\",\"type\":\"bytes\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"}],\"name\":\"UpdatedGasLimit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"protocolFlatFee\",\"type\":\"uint256\"}],\"name\":\"UpdatedProtocolFlatFee\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"systemContract\",\"type\":\"address\"}],\"name\":\"UpdatedSystemContract\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"to\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasfee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"protocolFlatFee\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"CHAIN_ID\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"COIN_TYPE\",\"outputs\":[{\"internalType\":\"enumCoinType\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"FUNGIBLE_MODULE_ADDRESS\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GAS_LIMIT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PROTOCOL_FLAT_FEE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SYSTEM_CONTRACT_ADDRESS\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"deposit\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newField\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newPublicField\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"}],\"name\":\"updateGasLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newField_\",\"type\":\"uint256\"}],\"name\":\"updateNewField\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"protocolFlatFee\",\"type\":\"uint256\"}],\"name\":\"updateProtocolFlatFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"updateSystemContractAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"to\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawGasFee\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", - Bin: "0x60c06040523480156200001157600080fd5b5060405162002566380380620025668339818101604052810190620000379190620000e1565b816080818152505080600281111562000055576200005462000128565b5b60a08160028111156200006d576200006c62000128565b5b81525050505062000157565b600080fd5b6000819050919050565b62000093816200007e565b81146200009f57600080fd5b50565b600081519050620000b38162000088565b92915050565b60038110620000c757600080fd5b50565b600081519050620000db81620000b9565b92915050565b60008060408385031215620000fb57620000fa62000079565b5b60006200010b85828601620000a2565b92505060206200011e85828601620000ca565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60805160a0516123db6200018b6000396000610aa40152600081816109ee01528181610f59015261107e01526123db6000f3fe608060405234801561001057600080fd5b50600436106101a95760003560e01c806385e1f4d0116100f9578063c701262611610097578063dd62ed3e11610071578063dd62ed3e146104ff578063eddeb1231461052f578063f2441b321461054b578063f687d12a14610569576101a9565b8063c701262614610494578063c835d7cc146104c4578063d9eeebed146104e0576101a9565b8063a457c2d7116100d3578063a457c2d7146103f8578063a7605f4514610428578063a9059cbb14610446578063b92894ba14610476576101a9565b806385e1f4d01461039e57806395d89b41146103bc578063a3413d03146103da576101a9565b8063395093511161016657806347e7ef241161014057806347e7ef24146103045780634d8943bb1461033457806370a0823114610352578063732bb0e414610382576101a9565b806339509351146102865780633ce4a5bc146102b657806342966c68146102d4576101a9565b806306fdde03146101ae578063091d2788146101cc578063095ea7b3146101ea57806318160ddd1461021a57806323b872dd14610238578063313ce56714610268575b600080fd5b6101b6610585565b6040516101c39190611b20565b60405180910390f35b6101d4610617565b6040516101e19190611b5b565b60405180910390f35b61020460048036038101906101ff9190611c14565b61061d565b6040516102119190611c6f565b60405180910390f35b61022261063b565b60405161022f9190611b5b565b60405180910390f35b610252600480360381019061024d9190611c8a565b610645565b60405161025f9190611c6f565b60405180910390f35b61027061073d565b60405161027d9190611cf9565b60405180910390f35b6102a0600480360381019061029b9190611c14565b610754565b6040516102ad9190611c6f565b60405180910390f35b6102be6107fa565b6040516102cb9190611d23565b60405180910390f35b6102ee60048036038101906102e99190611d3e565b610812565b6040516102fb9190611c6f565b60405180910390f35b61031e60048036038101906103199190611c14565b610827565b60405161032b9190611c6f565b60405180910390f35b61033c610993565b6040516103499190611b5b565b60405180910390f35b61036c60048036038101906103679190611d6b565b610999565b6040516103799190611b5b565b60405180910390f35b61039c60048036038101906103979190611d3e565b6109e2565b005b6103a66109ec565b6040516103b39190611b5b565b60405180910390f35b6103c4610a10565b6040516103d19190611b20565b60405180910390f35b6103e2610aa2565b6040516103ef9190611e0f565b60405180910390f35b610412600480360381019061040d9190611c14565b610ac6565b60405161041f9190611c6f565b60405180910390f35b610430610c29565b60405161043d9190611b5b565b60405180910390f35b610460600480360381019061045b9190611c14565b610c2f565b60405161046d9190611c6f565b60405180910390f35b61047e610c4d565b60405161048b9190611b20565b60405180910390f35b6104ae60048036038101906104a99190611f5f565b610cdb565b6040516104bb9190611c6f565b60405180910390f35b6104de60048036038101906104d99190611d6b565b610e22565b005b6104e8610f15565b6040516104f6929190611fbb565b60405180910390f35b61051960048036038101906105149190611fe4565b611162565b6040516105269190611b5b565b60405180910390f35b61054960048036038101906105449190611d3e565b6111e9565b005b6105536112a3565b6040516105609190611d23565b60405180910390f35b610583600480360381019061057e9190611d3e565b6112c7565b005b60606006805461059490612053565b80601f01602080910402602001604051908101604052809291908181526020018280546105c090612053565b801561060d5780601f106105e25761010080835404028352916020019161060d565b820191906000526020600020905b8154815290600101906020018083116105f057829003601f168201915b5050505050905090565b60015481565b600061063161062a611381565b8484611389565b6001905092915050565b6000600554905090565b6000610652848484611540565b6000600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600061069d611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610714576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61073185610720611381565b858461072c91906120b3565b611389565b60019150509392505050565b6000600860009054906101000a900460ff16905090565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006107a0611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107e991906120e7565b925050819055506001905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab81565b600061081e338361179a565b60019050919050565b600073735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156108c5575060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614155b156108fc576040517fddb5de5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6109068383611951565b8273ffffffffffffffffffffffffffffffffffffffff167f67fc7bdaed5b0ec550d8706b87d60568ab70c6b781263c70101d54cd1564aab373735b14bb79463307aacbed86daf3322b1e6226ab6040516020016109639190612163565b604051602081830303815290604052846040516109819291906121d3565b60405180910390a26001905092915050565b60025481565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b8060098190555050565b7f000000000000000000000000000000000000000000000000000000000000000081565b606060078054610a1f90612053565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4b90612053565b8015610a985780601f10610a6d57610100808354040283529160200191610a98565b820191906000526020600020905b815481529060010190602001808311610a7b57829003601f168201915b5050505050905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610b12611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610b85576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610bcf611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610c1891906120b3565b925050819055506001905092915050565b60095481565b6000610c43610c3c611381565b8484611540565b6001905092915050565b600a8054610c5a90612053565b80601f0160208091040260200160405190810160405280929190818152602001828054610c8690612053565b8015610cd35780601f10610ca857610100808354040283529160200191610cd3565b820191906000526020600020905b815481529060010190602001808311610cb657829003601f168201915b505050505081565b6000806000610ce8610f15565b915091508173ffffffffffffffffffffffffffffffffffffffff166323b872dd3373735b14bb79463307aacbed86daf3322b1e6226ab846040518463ffffffff1660e01b8152600401610d3d93929190612203565b6020604051808303816000875af1158015610d5c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d809190612266565b610db6576040517f0a7cd6d600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dc0338561179a565b3373ffffffffffffffffffffffffffffffffffffffff167f9ffbffc04a397460ee1dbe8c9503e098090567d6b7f4b3c02a8617d800b6d955868684600254604051610e0e9493929190612293565b60405180910390a260019250505092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e9b576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fd55614e962c5fd6ece71614f6348d702468a997a394dd5e5c1677950226d97ae81604051610f0a9190611d23565b60405180910390a150565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630be155477f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b8152600401610f949190611b5b565b602060405180830381865afa158015610fb1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fd591906122f4565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361103d576040517f78fff39600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d7fd7afb7f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b81526004016110b99190611b5b565b602060405180830381865afa1580156110d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110fa9190612336565b905060008103611136576040517fe661aed000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600254600154836111499190612363565b61115391906120e7565b90508281945094505050509091565b6000600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611262576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806002819055507fef13af88e424b5d15f49c77758542c1938b08b8b95b91ed0751f98ba99000d8f816040516112989190611b5b565b60405180910390a150565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611340576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001819055507fff5788270f43bfc1ca41c503606d2594aa3023a1a7547de403a3e2f146a4a80a816040516113769190611b5b565b60405180910390a150565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036113ef576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611455576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516115339190611b5b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036115a6576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361160c576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561168a576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161169691906120b3565b600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461172891906120e7565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161178c9190611b5b565b60405180910390a350505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611800576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561187e576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161188a91906120b3565b600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600560008282546118df91906120b3565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516119449190611b5b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036119b7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80600560008282546119c991906120e7565b9250508190555080600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611a1f91906120e7565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611a849190611b5b565b60405180910390a35050565b600081519050919050565b600082825260208201905092915050565b60005b83811015611aca578082015181840152602081019050611aaf565b60008484015250505050565b6000601f19601f8301169050919050565b6000611af282611a90565b611afc8185611a9b565b9350611b0c818560208601611aac565b611b1581611ad6565b840191505092915050565b60006020820190508181036000830152611b3a8184611ae7565b905092915050565b6000819050919050565b611b5581611b42565b82525050565b6000602082019050611b706000830184611b4c565b92915050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000611bb582611b8a565b9050919050565b611bc581611baa565b8114611bd057600080fd5b50565b600081359050611be281611bbc565b92915050565b611bf181611b42565b8114611bfc57600080fd5b50565b600081359050611c0e81611be8565b92915050565b60008060408385031215611c2b57611c2a611b80565b5b6000611c3985828601611bd3565b9250506020611c4a85828601611bff565b9150509250929050565b60008115159050919050565b611c6981611c54565b82525050565b6000602082019050611c846000830184611c60565b92915050565b600080600060608486031215611ca357611ca2611b80565b5b6000611cb186828701611bd3565b9350506020611cc286828701611bd3565b9250506040611cd386828701611bff565b9150509250925092565b600060ff82169050919050565b611cf381611cdd565b82525050565b6000602082019050611d0e6000830184611cea565b92915050565b611d1d81611baa565b82525050565b6000602082019050611d386000830184611d14565b92915050565b600060208284031215611d5457611d53611b80565b5b6000611d6284828501611bff565b91505092915050565b600060208284031215611d8157611d80611b80565b5b6000611d8f84828501611bd3565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60038110611dd857611dd7611d98565b5b50565b6000819050611de982611dc7565b919050565b6000611df982611ddb565b9050919050565b611e0981611dee565b82525050565b6000602082019050611e246000830184611e00565b92915050565b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b611e6c82611ad6565b810181811067ffffffffffffffff82111715611e8b57611e8a611e34565b5b80604052505050565b6000611e9e611b76565b9050611eaa8282611e63565b919050565b600067ffffffffffffffff821115611eca57611ec9611e34565b5b611ed382611ad6565b9050602081019050919050565b82818337600083830152505050565b6000611f02611efd84611eaf565b611e94565b905082815260208101848484011115611f1e57611f1d611e2f565b5b611f29848285611ee0565b509392505050565b600082601f830112611f4657611f45611e2a565b5b8135611f56848260208601611eef565b91505092915050565b60008060408385031215611f7657611f75611b80565b5b600083013567ffffffffffffffff811115611f9457611f93611b85565b5b611fa085828601611f31565b9250506020611fb185828601611bff565b9150509250929050565b6000604082019050611fd06000830185611d14565b611fdd6020830184611b4c565b9392505050565b60008060408385031215611ffb57611ffa611b80565b5b600061200985828601611bd3565b925050602061201a85828601611bd3565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061206b57607f821691505b60208210810361207e5761207d612024565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006120be82611b42565b91506120c983611b42565b92508282039050818111156120e1576120e0612084565b5b92915050565b60006120f282611b42565b91506120fd83611b42565b925082820190508082111561211557612114612084565b5b92915050565b60008160601b9050919050565b60006121338261211b565b9050919050565b600061214582612128565b9050919050565b61215d61215882611baa565b61213a565b82525050565b600061216f828461214c565b60148201915081905092915050565b600081519050919050565b600082825260208201905092915050565b60006121a58261217e565b6121af8185612189565b93506121bf818560208601611aac565b6121c881611ad6565b840191505092915050565b600060408201905081810360008301526121ed818561219a565b90506121fc6020830184611b4c565b9392505050565b60006060820190506122186000830186611d14565b6122256020830185611d14565b6122326040830184611b4c565b949350505050565b61224381611c54565b811461224e57600080fd5b50565b6000815190506122608161223a565b92915050565b60006020828403121561227c5761227b611b80565b5b600061228a84828501612251565b91505092915050565b600060808201905081810360008301526122ad818761219a565b90506122bc6020830186611b4c565b6122c96040830185611b4c565b6122d66060830184611b4c565b95945050505050565b6000815190506122ee81611bbc565b92915050565b60006020828403121561230a57612309611b80565b5b6000612318848285016122df565b91505092915050565b60008151905061233081611be8565b92915050565b60006020828403121561234c5761234b611b80565b5b600061235a84828501612321565b91505092915050565b600061236e82611b42565b915061237983611b42565b925082820261238781611b42565b9150828204841483151761239e5761239d612084565b5b509291505056fea264697066735822122011fda4fba218fc7f911b68b0418884c426f5ddb324eb1572e2e793fcb60edf6c64736f6c63430008150033", + ABI: "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"chainid_\",\"type\":\"uint256\"},{\"internalType\":\"enumCoinType\",\"name\":\"coinType_\",\"type\":\"uint8\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"CallerIsNotFungibleModule\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"GasFeeTransferFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidSender\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LowAllowance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"LowBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroGasCoin\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroGasPrice\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"from\",\"type\":\"bytes\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"}],\"name\":\"UpdatedGasLimit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"protocolFlatFee\",\"type\":\"uint256\"}],\"name\":\"UpdatedProtocolFlatFee\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"systemContract\",\"type\":\"address\"}],\"name\":\"UpdatedSystemContract\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"to\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasfee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"protocolFlatFee\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"CHAIN_ID\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"COIN_TYPE\",\"outputs\":[{\"internalType\":\"enumCoinType\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"FUNGIBLE_MODULE_ADDRESS\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GAS_LIMIT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PROTOCOL_FLAT_FEE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SYSTEM_CONTRACT_ADDRESS\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"deposit\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gatewayAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newField\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newPublicField\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"}],\"name\":\"updateGasLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newField_\",\"type\":\"uint256\"}],\"name\":\"updateNewField\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"protocolFlatFee\",\"type\":\"uint256\"}],\"name\":\"updateProtocolFlatFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"updateSystemContractAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"to\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawGasFee\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x60c06040523480156200001157600080fd5b5060405162002644380380620026448339818101604052810190620000379190620000aa565b8160808181525050806002811115620000555762000054620000fb565b5b60a08160028111156200006d576200006c620000fb565b5b60f81b8152505050506200015a565b6000815190506200008d816200012f565b92915050565b600081519050620000a48162000140565b92915050565b60008060408385031215620000c457620000c36200012a565b5b6000620000d48582860162000093565b9250506020620000e7858286016200007c565b9150509250929050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600080fd5b600381106200013d57600080fd5b50565b6200014b81620000f1565b81146200015757600080fd5b50565b60805160a05160f81c6124b3620001916000396000610b03015260008181610a2701528181610fc701526110fc01526124b36000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80638b851b95116100f9578063c701262611610097578063dd62ed3e11610071578063dd62ed3e14610538578063eddeb12314610568578063f2441b3214610584578063f687d12a146105a2576101c4565b8063c7012626146104cd578063c835d7cc146104fd578063d9eeebed14610519576101c4565b8063a457c2d7116100d3578063a457c2d714610431578063a7605f4514610461578063a9059cbb1461047f578063b92894ba146104af576101c4565b80638b851b95146103d757806395d89b41146103f5578063a3413d0314610413576101c4565b80633ce4a5bc116101665780634d8943bb116101405780634d8943bb1461034f57806370a082311461036d578063732bb0e41461039d57806385e1f4d0146103b9576101c4565b80633ce4a5bc146102d157806342966c68146102ef57806347e7ef241461031f576101c4565b806318160ddd116101a257806318160ddd1461023557806323b872dd14610253578063313ce5671461028357806339509351146102a1576101c4565b806306fdde03146101c9578063091d2788146101e7578063095ea7b314610205575b600080fd5b6101d16105be565b6040516101de9190612029565b60405180910390f35b6101ef610650565b6040516101fc919061204b565b60405180910390f35b61021f600480360381019061021a9190611cea565b610656565b60405161022c9190611f77565b60405180910390f35b61023d610674565b60405161024a919061204b565b60405180910390f35b61026d60048036038101906102689190611c97565b61067e565b60405161027a9190611f77565b60405180910390f35b61028b610776565b6040516102989190612066565b60405180910390f35b6102bb60048036038101906102b69190611cea565b61078d565b6040516102c89190611f77565b60405180910390f35b6102d9610833565b6040516102e69190611efc565b60405180910390f35b61030960048036038101906103049190611db3565b61084b565b6040516103169190611f77565b60405180910390f35b61033960048036038101906103349190611cea565b610860565b6040516103469190611f77565b60405180910390f35b6103576109cc565b604051610364919061204b565b60405180910390f35b61038760048036038101906103829190611bfd565b6109d2565b604051610394919061204b565b60405180910390f35b6103b760048036038101906103b29190611db3565b610a1b565b005b6103c1610a25565b6040516103ce919061204b565b60405180910390f35b6103df610a49565b6040516103ec9190611efc565b60405180910390f35b6103fd610a6f565b60405161040a9190612029565b60405180910390f35b61041b610b01565b604051610428919061200e565b60405180910390f35b61044b60048036038101906104469190611cea565b610b25565b6040516104589190611f77565b60405180910390f35b610469610c88565b604051610476919061204b565b60405180910390f35b61049960048036038101906104949190611cea565b610c8e565b6040516104a69190611f77565b60405180910390f35b6104b7610cac565b6040516104c49190612029565b60405180910390f35b6104e760048036038101906104e29190611d57565b610d3a565b6040516104f49190611f77565b60405180910390f35b61051760048036038101906105129190611bfd565b610e90565b005b610521610f83565b60405161052f929190611f4e565b60405180910390f35b610552600480360381019061054d9190611c57565b6111f0565b60405161055f919061204b565b60405180910390f35b610582600480360381019061057d9190611db3565b611277565b005b61058c611331565b6040516105999190611efc565b60405180910390f35b6105bc60048036038101906105b79190611db3565b611355565b005b6060600680546105cd906122af565b80601f01602080910402602001604051908101604052809291908181526020018280546105f9906122af565b80156106465780601f1061061b57610100808354040283529160200191610646565b820191906000526020600020905b81548152906001019060200180831161062957829003601f168201915b5050505050905090565b60015481565b600061066a61066361140f565b8484611417565b6001905092915050565b6000600554905090565b600061068b8484846115d0565b6000600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006106d661140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508281101561074d576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61076a8561075961140f565b858461076591906121bf565b611417565b60019150509392505050565b6000600860009054906101000a900460ff16905090565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006107d961140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610822919061210f565b925050819055506001905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab81565b6000610857338361182c565b60019050919050565b600073735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156108fe575060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614155b15610935576040517fddb5de5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093f83836119e4565b8273ffffffffffffffffffffffffffffffffffffffff167f67fc7bdaed5b0ec550d8706b87d60568ab70c6b781263c70101d54cd1564aab373735b14bb79463307aacbed86daf3322b1e6226ab60405160200161099c9190611ee1565b604051602081830303815290604052846040516109ba929190611f92565b60405180910390a26001905092915050565b60025481565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b8060098190555050565b7f000000000000000000000000000000000000000000000000000000000000000081565b600860019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b606060078054610a7e906122af565b80601f0160208091040260200160405190810160405280929190818152602001828054610aaa906122af565b8015610af75780601f10610acc57610100808354040283529160200191610af7565b820191906000526020600020905b815481529060010190602001808311610ada57829003601f168201915b5050505050905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610b7161140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610be4576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610c2e61140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610c7791906121bf565b925050819055506001905092915050565b60095481565b6000610ca2610c9b61140f565b84846115d0565b6001905092915050565b600a8054610cb9906122af565b80601f0160208091040260200160405190810160405280929190818152602001828054610ce5906122af565b8015610d325780601f10610d0757610100808354040283529160200191610d32565b820191906000526020600020905b815481529060010190602001808311610d1557829003601f168201915b505050505081565b6000806000610d47610f83565b915091508173ffffffffffffffffffffffffffffffffffffffff166323b872dd3373735b14bb79463307aacbed86daf3322b1e6226ab846040518463ffffffff1660e01b8152600401610d9c93929190611f17565b602060405180830381600087803b158015610db657600080fd5b505af1158015610dca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dee9190611d2a565b610e24576040517f0a7cd6d600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e2e338561182c565b3373ffffffffffffffffffffffffffffffffffffffff167f9ffbffc04a397460ee1dbe8c9503e098090567d6b7f4b3c02a8617d800b6d955868684600254604051610e7c9493929190611fc2565b60405180910390a260019250505092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610f09576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fd55614e962c5fd6ece71614f6348d702468a997a394dd5e5c1677950226d97ae81604051610f789190611efc565b60405180910390a150565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630be155477f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b8152600401611002919061204b565b60206040518083038186803b15801561101a57600080fd5b505afa15801561102e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110529190611c2a565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156110bb576040517f78fff39600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d7fd7afb7f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b8152600401611137919061204b565b60206040518083038186803b15801561114f57600080fd5b505afa158015611163573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111879190611de0565b905060008114156111c4576040517fe661aed000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600254600154836111d79190612165565b6111e1919061210f565b90508281945094505050509091565b6000600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146112f0576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806002819055507fef13af88e424b5d15f49c77758542c1938b08b8b95b91ed0751f98ba99000d8f81604051611326919061204b565b60405180910390a150565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146113ce576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001819055507fff5788270f43bfc1ca41c503606d2594aa3023a1a7547de403a3e2f146a4a80a81604051611404919061204b565b60405180910390a150565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561147e576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156114e5576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516115c3919061204b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611637576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561169e576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561171c576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161172891906121bf565b600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546117ba919061210f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161181e919061204b565b60405180910390a350505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611893576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611911576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161191d91906121bf565b600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816005600082825461197291906121bf565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516119d7919061204b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611a4b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060056000828254611a5d919061210f565b9250508190555080600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611ab3919061210f565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611b18919061204b565b60405180910390a35050565b6000611b37611b32846120a6565b612081565b905082815260208101848484011115611b5357611b526123f7565b5b611b5e84828561226d565b509392505050565b600081359050611b7581612438565b92915050565b600081519050611b8a81612438565b92915050565b600081519050611b9f8161244f565b92915050565b600082601f830112611bba57611bb96123f2565b5b8135611bca848260208601611b24565b91505092915050565b600081359050611be281612466565b92915050565b600081519050611bf781612466565b92915050565b600060208284031215611c1357611c12612401565b5b6000611c2184828501611b66565b91505092915050565b600060208284031215611c4057611c3f612401565b5b6000611c4e84828501611b7b565b91505092915050565b60008060408385031215611c6e57611c6d612401565b5b6000611c7c85828601611b66565b9250506020611c8d85828601611b66565b9150509250929050565b600080600060608486031215611cb057611caf612401565b5b6000611cbe86828701611b66565b9350506020611ccf86828701611b66565b9250506040611ce086828701611bd3565b9150509250925092565b60008060408385031215611d0157611d00612401565b5b6000611d0f85828601611b66565b9250506020611d2085828601611bd3565b9150509250929050565b600060208284031215611d4057611d3f612401565b5b6000611d4e84828501611b90565b91505092915050565b60008060408385031215611d6e57611d6d612401565b5b600083013567ffffffffffffffff811115611d8c57611d8b6123fc565b5b611d9885828601611ba5565b9250506020611da985828601611bd3565b9150509250929050565b600060208284031215611dc957611dc8612401565b5b6000611dd784828501611bd3565b91505092915050565b600060208284031215611df657611df5612401565b5b6000611e0484828501611be8565b91505092915050565b611e16816121f3565b82525050565b611e2d611e28826121f3565b612312565b82525050565b611e3c81612205565b82525050565b6000611e4d826120d7565b611e5781856120ed565b9350611e6781856020860161227c565b611e7081612406565b840191505092915050565b611e848161225b565b82525050565b6000611e95826120e2565b611e9f81856120fe565b9350611eaf81856020860161227c565b611eb881612406565b840191505092915050565b611ecc81612244565b82525050565b611edb8161224e565b82525050565b6000611eed8284611e1c565b60148201915081905092915050565b6000602082019050611f116000830184611e0d565b92915050565b6000606082019050611f2c6000830186611e0d565b611f396020830185611e0d565b611f466040830184611ec3565b949350505050565b6000604082019050611f636000830185611e0d565b611f706020830184611ec3565b9392505050565b6000602082019050611f8c6000830184611e33565b92915050565b60006040820190508181036000830152611fac8185611e42565b9050611fbb6020830184611ec3565b9392505050565b60006080820190508181036000830152611fdc8187611e42565b9050611feb6020830186611ec3565b611ff86040830185611ec3565b6120056060830184611ec3565b95945050505050565b60006020820190506120236000830184611e7b565b92915050565b600060208201905081810360008301526120438184611e8a565b905092915050565b60006020820190506120606000830184611ec3565b92915050565b600060208201905061207b6000830184611ed2565b92915050565b600061208b61209c565b905061209782826122e1565b919050565b6000604051905090565b600067ffffffffffffffff8211156120c1576120c06123c3565b5b6120ca82612406565b9050602081019050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600061211a82612244565b915061212583612244565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561215a57612159612336565b5b828201905092915050565b600061217082612244565b915061217b83612244565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156121b4576121b3612336565b5b828202905092915050565b60006121ca82612244565b91506121d583612244565b9250828210156121e8576121e7612336565b5b828203905092915050565b60006121fe82612224565b9050919050565b60008115159050919050565b600081905061221f82612424565b919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b600061226682612211565b9050919050565b82818337600083830152505050565b60005b8381101561229a57808201518184015260208101905061227f565b838111156122a9576000848401525b50505050565b600060028204905060018216806122c757607f821691505b602082108114156122db576122da612394565b5b50919050565b6122ea82612406565b810181811067ffffffffffffffff82111715612309576123086123c3565b5b80604052505050565b600061231d82612324565b9050919050565b600061232f82612417565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b60008160601b9050919050565b6003811061243557612434612365565b5b50565b612441816121f3565b811461244c57600080fd5b50565b61245881612205565b811461246357600080fd5b50565b61246f81612244565b811461247a57600080fd5b5056fea2646970667358221220b8278837e775bf149b356e2bff0f3e4931577c5c8ff848545ce78afb4e0ce8cb64736f6c63430008070033", } // TestZRC20ABI is the input ABI used to generate the binding from. @@ -481,6 +481,37 @@ func (_TestZRC20 *TestZRC20CallerSession) Decimals() (uint8, error) { return _TestZRC20.Contract.Decimals(&_TestZRC20.CallOpts) } +// GatewayAddress is a free data retrieval call binding the contract method 0x8b851b95. +// +// Solidity: function gatewayAddress() view returns(address) +func (_TestZRC20 *TestZRC20Caller) GatewayAddress(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _TestZRC20.contract.Call(opts, &out, "gatewayAddress") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GatewayAddress is a free data retrieval call binding the contract method 0x8b851b95. +// +// Solidity: function gatewayAddress() view returns(address) +func (_TestZRC20 *TestZRC20Session) GatewayAddress() (common.Address, error) { + return _TestZRC20.Contract.GatewayAddress(&_TestZRC20.CallOpts) +} + +// GatewayAddress is a free data retrieval call binding the contract method 0x8b851b95. +// +// Solidity: function gatewayAddress() view returns(address) +func (_TestZRC20 *TestZRC20CallerSession) GatewayAddress() (common.Address, error) { + return _TestZRC20.Contract.GatewayAddress(&_TestZRC20.CallOpts) +} + // Name is a free data retrieval call binding the contract method 0x06fdde03. // // Solidity: function name() view returns(string) diff --git a/e2e/contracts/testzrc20/TestZRC20.json b/e2e/contracts/testzrc20/TestZRC20.json index b4d7b68b26..4e28aac85b 100644 --- a/e2e/contracts/testzrc20/TestZRC20.json +++ b/e2e/contracts/testzrc20/TestZRC20.json @@ -432,6 +432,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "gatewayAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -669,5 +682,5 @@ "type": "function" } ], - "bin": "60c06040523480156200001157600080fd5b5060405162002566380380620025668339818101604052810190620000379190620000e1565b816080818152505080600281111562000055576200005462000128565b5b60a08160028111156200006d576200006c62000128565b5b81525050505062000157565b600080fd5b6000819050919050565b62000093816200007e565b81146200009f57600080fd5b50565b600081519050620000b38162000088565b92915050565b60038110620000c757600080fd5b50565b600081519050620000db81620000b9565b92915050565b60008060408385031215620000fb57620000fa62000079565b5b60006200010b85828601620000a2565b92505060206200011e85828601620000ca565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60805160a0516123db6200018b6000396000610aa40152600081816109ee01528181610f59015261107e01526123db6000f3fe608060405234801561001057600080fd5b50600436106101a95760003560e01c806385e1f4d0116100f9578063c701262611610097578063dd62ed3e11610071578063dd62ed3e146104ff578063eddeb1231461052f578063f2441b321461054b578063f687d12a14610569576101a9565b8063c701262614610494578063c835d7cc146104c4578063d9eeebed146104e0576101a9565b8063a457c2d7116100d3578063a457c2d7146103f8578063a7605f4514610428578063a9059cbb14610446578063b92894ba14610476576101a9565b806385e1f4d01461039e57806395d89b41146103bc578063a3413d03146103da576101a9565b8063395093511161016657806347e7ef241161014057806347e7ef24146103045780634d8943bb1461033457806370a0823114610352578063732bb0e414610382576101a9565b806339509351146102865780633ce4a5bc146102b657806342966c68146102d4576101a9565b806306fdde03146101ae578063091d2788146101cc578063095ea7b3146101ea57806318160ddd1461021a57806323b872dd14610238578063313ce56714610268575b600080fd5b6101b6610585565b6040516101c39190611b20565b60405180910390f35b6101d4610617565b6040516101e19190611b5b565b60405180910390f35b61020460048036038101906101ff9190611c14565b61061d565b6040516102119190611c6f565b60405180910390f35b61022261063b565b60405161022f9190611b5b565b60405180910390f35b610252600480360381019061024d9190611c8a565b610645565b60405161025f9190611c6f565b60405180910390f35b61027061073d565b60405161027d9190611cf9565b60405180910390f35b6102a0600480360381019061029b9190611c14565b610754565b6040516102ad9190611c6f565b60405180910390f35b6102be6107fa565b6040516102cb9190611d23565b60405180910390f35b6102ee60048036038101906102e99190611d3e565b610812565b6040516102fb9190611c6f565b60405180910390f35b61031e60048036038101906103199190611c14565b610827565b60405161032b9190611c6f565b60405180910390f35b61033c610993565b6040516103499190611b5b565b60405180910390f35b61036c60048036038101906103679190611d6b565b610999565b6040516103799190611b5b565b60405180910390f35b61039c60048036038101906103979190611d3e565b6109e2565b005b6103a66109ec565b6040516103b39190611b5b565b60405180910390f35b6103c4610a10565b6040516103d19190611b20565b60405180910390f35b6103e2610aa2565b6040516103ef9190611e0f565b60405180910390f35b610412600480360381019061040d9190611c14565b610ac6565b60405161041f9190611c6f565b60405180910390f35b610430610c29565b60405161043d9190611b5b565b60405180910390f35b610460600480360381019061045b9190611c14565b610c2f565b60405161046d9190611c6f565b60405180910390f35b61047e610c4d565b60405161048b9190611b20565b60405180910390f35b6104ae60048036038101906104a99190611f5f565b610cdb565b6040516104bb9190611c6f565b60405180910390f35b6104de60048036038101906104d99190611d6b565b610e22565b005b6104e8610f15565b6040516104f6929190611fbb565b60405180910390f35b61051960048036038101906105149190611fe4565b611162565b6040516105269190611b5b565b60405180910390f35b61054960048036038101906105449190611d3e565b6111e9565b005b6105536112a3565b6040516105609190611d23565b60405180910390f35b610583600480360381019061057e9190611d3e565b6112c7565b005b60606006805461059490612053565b80601f01602080910402602001604051908101604052809291908181526020018280546105c090612053565b801561060d5780601f106105e25761010080835404028352916020019161060d565b820191906000526020600020905b8154815290600101906020018083116105f057829003601f168201915b5050505050905090565b60015481565b600061063161062a611381565b8484611389565b6001905092915050565b6000600554905090565b6000610652848484611540565b6000600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600061069d611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905082811015610714576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61073185610720611381565b858461072c91906120b3565b611389565b60019150509392505050565b6000600860009054906101000a900460ff16905090565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006107a0611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546107e991906120e7565b925050819055506001905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab81565b600061081e338361179a565b60019050919050565b600073735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156108c5575060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614155b156108fc576040517fddb5de5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6109068383611951565b8273ffffffffffffffffffffffffffffffffffffffff167f67fc7bdaed5b0ec550d8706b87d60568ab70c6b781263c70101d54cd1564aab373735b14bb79463307aacbed86daf3322b1e6226ab6040516020016109639190612163565b604051602081830303815290604052846040516109819291906121d3565b60405180910390a26001905092915050565b60025481565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b8060098190555050565b7f000000000000000000000000000000000000000000000000000000000000000081565b606060078054610a1f90612053565b80601f0160208091040260200160405190810160405280929190818152602001828054610a4b90612053565b8015610a985780601f10610a6d57610100808354040283529160200191610a98565b820191906000526020600020905b815481529060010190602001808311610a7b57829003601f168201915b5050505050905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610b12611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610b85576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610bcf611381565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610c1891906120b3565b925050819055506001905092915050565b60095481565b6000610c43610c3c611381565b8484611540565b6001905092915050565b600a8054610c5a90612053565b80601f0160208091040260200160405190810160405280929190818152602001828054610c8690612053565b8015610cd35780601f10610ca857610100808354040283529160200191610cd3565b820191906000526020600020905b815481529060010190602001808311610cb657829003601f168201915b505050505081565b6000806000610ce8610f15565b915091508173ffffffffffffffffffffffffffffffffffffffff166323b872dd3373735b14bb79463307aacbed86daf3322b1e6226ab846040518463ffffffff1660e01b8152600401610d3d93929190612203565b6020604051808303816000875af1158015610d5c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d809190612266565b610db6576040517f0a7cd6d600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610dc0338561179a565b3373ffffffffffffffffffffffffffffffffffffffff167f9ffbffc04a397460ee1dbe8c9503e098090567d6b7f4b3c02a8617d800b6d955868684600254604051610e0e9493929190612293565b60405180910390a260019250505092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610e9b576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fd55614e962c5fd6ece71614f6348d702468a997a394dd5e5c1677950226d97ae81604051610f0a9190611d23565b60405180910390a150565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630be155477f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b8152600401610f949190611b5b565b602060405180830381865afa158015610fb1573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fd591906122f4565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff160361103d576040517f78fff39600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d7fd7afb7f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b81526004016110b99190611b5b565b602060405180830381865afa1580156110d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110fa9190612336565b905060008103611136576040517fe661aed000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600254600154836111499190612363565b61115391906120e7565b90508281945094505050509091565b6000600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611262576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806002819055507fef13af88e424b5d15f49c77758542c1938b08b8b95b91ed0751f98ba99000d8f816040516112989190611b5b565b60405180910390a150565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611340576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001819055507fff5788270f43bfc1ca41c503606d2594aa3023a1a7547de403a3e2f146a4a80a816040516113769190611b5b565b60405180910390a150565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036113ef576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611455576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516115339190611b5b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036115a6576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361160c576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561168a576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161169691906120b3565b600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461172891906120e7565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161178c9190611b5b565b60405180910390a350505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611800576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561187e576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161188a91906120b3565b600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600560008282546118df91906120b3565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516119449190611b5b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036119b7576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80600560008282546119c991906120e7565b9250508190555080600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611a1f91906120e7565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611a849190611b5b565b60405180910390a35050565b600081519050919050565b600082825260208201905092915050565b60005b83811015611aca578082015181840152602081019050611aaf565b60008484015250505050565b6000601f19601f8301169050919050565b6000611af282611a90565b611afc8185611a9b565b9350611b0c818560208601611aac565b611b1581611ad6565b840191505092915050565b60006020820190508181036000830152611b3a8184611ae7565b905092915050565b6000819050919050565b611b5581611b42565b82525050565b6000602082019050611b706000830184611b4c565b92915050565b6000604051905090565b600080fd5b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000611bb582611b8a565b9050919050565b611bc581611baa565b8114611bd057600080fd5b50565b600081359050611be281611bbc565b92915050565b611bf181611b42565b8114611bfc57600080fd5b50565b600081359050611c0e81611be8565b92915050565b60008060408385031215611c2b57611c2a611b80565b5b6000611c3985828601611bd3565b9250506020611c4a85828601611bff565b9150509250929050565b60008115159050919050565b611c6981611c54565b82525050565b6000602082019050611c846000830184611c60565b92915050565b600080600060608486031215611ca357611ca2611b80565b5b6000611cb186828701611bd3565b9350506020611cc286828701611bd3565b9250506040611cd386828701611bff565b9150509250925092565b600060ff82169050919050565b611cf381611cdd565b82525050565b6000602082019050611d0e6000830184611cea565b92915050565b611d1d81611baa565b82525050565b6000602082019050611d386000830184611d14565b92915050565b600060208284031215611d5457611d53611b80565b5b6000611d6284828501611bff565b91505092915050565b600060208284031215611d8157611d80611b80565b5b6000611d8f84828501611bd3565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60038110611dd857611dd7611d98565b5b50565b6000819050611de982611dc7565b919050565b6000611df982611ddb565b9050919050565b611e0981611dee565b82525050565b6000602082019050611e246000830184611e00565b92915050565b600080fd5b600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b611e6c82611ad6565b810181811067ffffffffffffffff82111715611e8b57611e8a611e34565b5b80604052505050565b6000611e9e611b76565b9050611eaa8282611e63565b919050565b600067ffffffffffffffff821115611eca57611ec9611e34565b5b611ed382611ad6565b9050602081019050919050565b82818337600083830152505050565b6000611f02611efd84611eaf565b611e94565b905082815260208101848484011115611f1e57611f1d611e2f565b5b611f29848285611ee0565b509392505050565b600082601f830112611f4657611f45611e2a565b5b8135611f56848260208601611eef565b91505092915050565b60008060408385031215611f7657611f75611b80565b5b600083013567ffffffffffffffff811115611f9457611f93611b85565b5b611fa085828601611f31565b9250506020611fb185828601611bff565b9150509250929050565b6000604082019050611fd06000830185611d14565b611fdd6020830184611b4c565b9392505050565b60008060408385031215611ffb57611ffa611b80565b5b600061200985828601611bd3565b925050602061201a85828601611bd3565b9150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000600282049050600182168061206b57607f821691505b60208210810361207e5761207d612024565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006120be82611b42565b91506120c983611b42565b92508282039050818111156120e1576120e0612084565b5b92915050565b60006120f282611b42565b91506120fd83611b42565b925082820190508082111561211557612114612084565b5b92915050565b60008160601b9050919050565b60006121338261211b565b9050919050565b600061214582612128565b9050919050565b61215d61215882611baa565b61213a565b82525050565b600061216f828461214c565b60148201915081905092915050565b600081519050919050565b600082825260208201905092915050565b60006121a58261217e565b6121af8185612189565b93506121bf818560208601611aac565b6121c881611ad6565b840191505092915050565b600060408201905081810360008301526121ed818561219a565b90506121fc6020830184611b4c565b9392505050565b60006060820190506122186000830186611d14565b6122256020830185611d14565b6122326040830184611b4c565b949350505050565b61224381611c54565b811461224e57600080fd5b50565b6000815190506122608161223a565b92915050565b60006020828403121561227c5761227b611b80565b5b600061228a84828501612251565b91505092915050565b600060808201905081810360008301526122ad818761219a565b90506122bc6020830186611b4c565b6122c96040830185611b4c565b6122d66060830184611b4c565b95945050505050565b6000815190506122ee81611bbc565b92915050565b60006020828403121561230a57612309611b80565b5b6000612318848285016122df565b91505092915050565b60008151905061233081611be8565b92915050565b60006020828403121561234c5761234b611b80565b5b600061235a84828501612321565b91505092915050565b600061236e82611b42565b915061237983611b42565b925082820261238781611b42565b9150828204841483151761239e5761239d612084565b5b509291505056fea264697066735822122011fda4fba218fc7f911b68b0418884c426f5ddb324eb1572e2e793fcb60edf6c64736f6c63430008150033" + "bin": "60c06040523480156200001157600080fd5b5060405162002644380380620026448339818101604052810190620000379190620000aa565b8160808181525050806002811115620000555762000054620000fb565b5b60a08160028111156200006d576200006c620000fb565b5b60f81b8152505050506200015a565b6000815190506200008d816200012f565b92915050565b600081519050620000a48162000140565b92915050565b60008060408385031215620000c457620000c36200012a565b5b6000620000d48582860162000093565b9250506020620000e7858286016200007c565b9150509250929050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600080fd5b600381106200013d57600080fd5b50565b6200014b81620000f1565b81146200015757600080fd5b50565b60805160a05160f81c6124b3620001916000396000610b03015260008181610a2701528181610fc701526110fc01526124b36000f3fe608060405234801561001057600080fd5b50600436106101c45760003560e01c80638b851b95116100f9578063c701262611610097578063dd62ed3e11610071578063dd62ed3e14610538578063eddeb12314610568578063f2441b3214610584578063f687d12a146105a2576101c4565b8063c7012626146104cd578063c835d7cc146104fd578063d9eeebed14610519576101c4565b8063a457c2d7116100d3578063a457c2d714610431578063a7605f4514610461578063a9059cbb1461047f578063b92894ba146104af576101c4565b80638b851b95146103d757806395d89b41146103f5578063a3413d0314610413576101c4565b80633ce4a5bc116101665780634d8943bb116101405780634d8943bb1461034f57806370a082311461036d578063732bb0e41461039d57806385e1f4d0146103b9576101c4565b80633ce4a5bc146102d157806342966c68146102ef57806347e7ef241461031f576101c4565b806318160ddd116101a257806318160ddd1461023557806323b872dd14610253578063313ce5671461028357806339509351146102a1576101c4565b806306fdde03146101c9578063091d2788146101e7578063095ea7b314610205575b600080fd5b6101d16105be565b6040516101de9190612029565b60405180910390f35b6101ef610650565b6040516101fc919061204b565b60405180910390f35b61021f600480360381019061021a9190611cea565b610656565b60405161022c9190611f77565b60405180910390f35b61023d610674565b60405161024a919061204b565b60405180910390f35b61026d60048036038101906102689190611c97565b61067e565b60405161027a9190611f77565b60405180910390f35b61028b610776565b6040516102989190612066565b60405180910390f35b6102bb60048036038101906102b69190611cea565b61078d565b6040516102c89190611f77565b60405180910390f35b6102d9610833565b6040516102e69190611efc565b60405180910390f35b61030960048036038101906103049190611db3565b61084b565b6040516103169190611f77565b60405180910390f35b61033960048036038101906103349190611cea565b610860565b6040516103469190611f77565b60405180910390f35b6103576109cc565b604051610364919061204b565b60405180910390f35b61038760048036038101906103829190611bfd565b6109d2565b604051610394919061204b565b60405180910390f35b6103b760048036038101906103b29190611db3565b610a1b565b005b6103c1610a25565b6040516103ce919061204b565b60405180910390f35b6103df610a49565b6040516103ec9190611efc565b60405180910390f35b6103fd610a6f565b60405161040a9190612029565b60405180910390f35b61041b610b01565b604051610428919061200e565b60405180910390f35b61044b60048036038101906104469190611cea565b610b25565b6040516104589190611f77565b60405180910390f35b610469610c88565b604051610476919061204b565b60405180910390f35b61049960048036038101906104949190611cea565b610c8e565b6040516104a69190611f77565b60405180910390f35b6104b7610cac565b6040516104c49190612029565b60405180910390f35b6104e760048036038101906104e29190611d57565b610d3a565b6040516104f49190611f77565b60405180910390f35b61051760048036038101906105129190611bfd565b610e90565b005b610521610f83565b60405161052f929190611f4e565b60405180910390f35b610552600480360381019061054d9190611c57565b6111f0565b60405161055f919061204b565b60405180910390f35b610582600480360381019061057d9190611db3565b611277565b005b61058c611331565b6040516105999190611efc565b60405180910390f35b6105bc60048036038101906105b79190611db3565b611355565b005b6060600680546105cd906122af565b80601f01602080910402602001604051908101604052809291908181526020018280546105f9906122af565b80156106465780601f1061061b57610100808354040283529160200191610646565b820191906000526020600020905b81548152906001019060200180831161062957829003601f168201915b5050505050905090565b60015481565b600061066a61066361140f565b8484611417565b6001905092915050565b6000600554905090565b600061068b8484846115d0565b6000600460008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006106d661140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508281101561074d576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61076a8561075961140f565b858461076591906121bf565b611417565b60019150509392505050565b6000600860009054906101000a900460ff16905090565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006107d961140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610822919061210f565b925050819055506001905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab81565b6000610857338361182c565b60019050919050565b600073735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156108fe575060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614155b15610935576040517fddb5de5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61093f83836119e4565b8273ffffffffffffffffffffffffffffffffffffffff167f67fc7bdaed5b0ec550d8706b87d60568ab70c6b781263c70101d54cd1564aab373735b14bb79463307aacbed86daf3322b1e6226ab60405160200161099c9190611ee1565b604051602081830303815290604052846040516109ba929190611f92565b60405180910390a26001905092915050565b60025481565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b8060098190555050565b7f000000000000000000000000000000000000000000000000000000000000000081565b600860019054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b606060078054610a7e906122af565b80601f0160208091040260200160405190810160405280929190818152602001828054610aaa906122af565b8015610af75780601f10610acc57610100808354040283529160200191610af7565b820191906000526020600020905b815481529060010190602001808311610ada57829003601f168201915b5050505050905090565b7f000000000000000000000000000000000000000000000000000000000000000081565b600081600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610b7161140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020541015610be4576040517f10bad14700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b81600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610c2e61140f565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610c7791906121bf565b925050819055506001905092915050565b60095481565b6000610ca2610c9b61140f565b84846115d0565b6001905092915050565b600a8054610cb9906122af565b80601f0160208091040260200160405190810160405280929190818152602001828054610ce5906122af565b8015610d325780601f10610d0757610100808354040283529160200191610d32565b820191906000526020600020905b815481529060010190602001808311610d1557829003601f168201915b505050505081565b6000806000610d47610f83565b915091508173ffffffffffffffffffffffffffffffffffffffff166323b872dd3373735b14bb79463307aacbed86daf3322b1e6226ab846040518463ffffffff1660e01b8152600401610d9c93929190611f17565b602060405180830381600087803b158015610db657600080fd5b505af1158015610dca573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610dee9190611d2a565b610e24576040517f0a7cd6d600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610e2e338561182c565b3373ffffffffffffffffffffffffffffffffffffffff167f9ffbffc04a397460ee1dbe8c9503e098090567d6b7f4b3c02a8617d800b6d955868684600254604051610e7c9493929190611fc2565b60405180910390a260019250505092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610f09576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fd55614e962c5fd6ece71614f6348d702468a997a394dd5e5c1677950226d97ae81604051610f789190611efc565b60405180910390a150565b60008060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630be155477f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b8152600401611002919061204b565b60206040518083038186803b15801561101a57600080fd5b505afa15801561102e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110529190611c2a565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614156110bb576040517f78fff39600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663d7fd7afb7f00000000000000000000000000000000000000000000000000000000000000006040518263ffffffff1660e01b8152600401611137919061204b565b60206040518083038186803b15801561114f57600080fd5b505afa158015611163573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111879190611de0565b905060008114156111c4576040517fe661aed000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600254600154836111d79190612165565b6111e1919061210f565b90508281945094505050509091565b6000600460008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146112f0576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806002819055507fef13af88e424b5d15f49c77758542c1938b08b8b95b91ed0751f98ba99000d8f81604051611326919061204b565b60405180910390a150565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b73735b14bb79463307aacbed86daf3322b1e6226ab73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146113ce576040517f2b2add3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b806001819055507fff5788270f43bfc1ca41c503606d2594aa3023a1a7547de403a3e2f146a4a80a81604051611404919061204b565b60405180910390a150565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561147e576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156114e5576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80600460008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040516115c3919061204b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611637576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561169e576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490508181101561171c576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161172891906121bf565b600360008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555081600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546117ba919061210f565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161181e919061204b565b60405180910390a350505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611893576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015611911576040517ffe382aa700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b818161191d91906121bf565b600360008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816005600082825461197291906121bf565b92505081905550600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516119d7919061204b565b60405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415611a4b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060056000828254611a5d919061210f565b9250508190555080600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254611ab3919061210f565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611b18919061204b565b60405180910390a35050565b6000611b37611b32846120a6565b612081565b905082815260208101848484011115611b5357611b526123f7565b5b611b5e84828561226d565b509392505050565b600081359050611b7581612438565b92915050565b600081519050611b8a81612438565b92915050565b600081519050611b9f8161244f565b92915050565b600082601f830112611bba57611bb96123f2565b5b8135611bca848260208601611b24565b91505092915050565b600081359050611be281612466565b92915050565b600081519050611bf781612466565b92915050565b600060208284031215611c1357611c12612401565b5b6000611c2184828501611b66565b91505092915050565b600060208284031215611c4057611c3f612401565b5b6000611c4e84828501611b7b565b91505092915050565b60008060408385031215611c6e57611c6d612401565b5b6000611c7c85828601611b66565b9250506020611c8d85828601611b66565b9150509250929050565b600080600060608486031215611cb057611caf612401565b5b6000611cbe86828701611b66565b9350506020611ccf86828701611b66565b9250506040611ce086828701611bd3565b9150509250925092565b60008060408385031215611d0157611d00612401565b5b6000611d0f85828601611b66565b9250506020611d2085828601611bd3565b9150509250929050565b600060208284031215611d4057611d3f612401565b5b6000611d4e84828501611b90565b91505092915050565b60008060408385031215611d6e57611d6d612401565b5b600083013567ffffffffffffffff811115611d8c57611d8b6123fc565b5b611d9885828601611ba5565b9250506020611da985828601611bd3565b9150509250929050565b600060208284031215611dc957611dc8612401565b5b6000611dd784828501611bd3565b91505092915050565b600060208284031215611df657611df5612401565b5b6000611e0484828501611be8565b91505092915050565b611e16816121f3565b82525050565b611e2d611e28826121f3565b612312565b82525050565b611e3c81612205565b82525050565b6000611e4d826120d7565b611e5781856120ed565b9350611e6781856020860161227c565b611e7081612406565b840191505092915050565b611e848161225b565b82525050565b6000611e95826120e2565b611e9f81856120fe565b9350611eaf81856020860161227c565b611eb881612406565b840191505092915050565b611ecc81612244565b82525050565b611edb8161224e565b82525050565b6000611eed8284611e1c565b60148201915081905092915050565b6000602082019050611f116000830184611e0d565b92915050565b6000606082019050611f2c6000830186611e0d565b611f396020830185611e0d565b611f466040830184611ec3565b949350505050565b6000604082019050611f636000830185611e0d565b611f706020830184611ec3565b9392505050565b6000602082019050611f8c6000830184611e33565b92915050565b60006040820190508181036000830152611fac8185611e42565b9050611fbb6020830184611ec3565b9392505050565b60006080820190508181036000830152611fdc8187611e42565b9050611feb6020830186611ec3565b611ff86040830185611ec3565b6120056060830184611ec3565b95945050505050565b60006020820190506120236000830184611e7b565b92915050565b600060208201905081810360008301526120438184611e8a565b905092915050565b60006020820190506120606000830184611ec3565b92915050565b600060208201905061207b6000830184611ed2565b92915050565b600061208b61209c565b905061209782826122e1565b919050565b6000604051905090565b600067ffffffffffffffff8211156120c1576120c06123c3565b5b6120ca82612406565b9050602081019050919050565b600081519050919050565b600081519050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600061211a82612244565b915061212583612244565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0382111561215a57612159612336565b5b828201905092915050565b600061217082612244565b915061217b83612244565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156121b4576121b3612336565b5b828202905092915050565b60006121ca82612244565b91506121d583612244565b9250828210156121e8576121e7612336565b5b828203905092915050565b60006121fe82612224565b9050919050565b60008115159050919050565b600081905061221f82612424565b919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b600061226682612211565b9050919050565b82818337600083830152505050565b60005b8381101561229a57808201518184015260208101905061227f565b838111156122a9576000848401525b50505050565b600060028204905060018216806122c757607f821691505b602082108114156122db576122da612394565b5b50919050565b6122ea82612406565b810181811067ffffffffffffffff82111715612309576123086123c3565b5b80604052505050565b600061231d82612324565b9050919050565b600061232f82612417565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b60008160601b9050919050565b6003811061243557612434612365565b5b50565b612441816121f3565b811461244c57600080fd5b50565b61245881612205565b811461246357600080fd5b50565b61246f81612244565b811461247a57600080fd5b5056fea2646970667358221220b8278837e775bf149b356e2bff0f3e4931577c5c8ff848545ce78afb4e0ce8cb64736f6c63430008070033" } diff --git a/e2e/contracts/testzrc20/TestZRC20.sol b/e2e/contracts/testzrc20/TestZRC20.sol index c31ccb4686..3447949d06 100644 --- a/e2e/contracts/testzrc20/TestZRC20.sol +++ b/e2e/contracts/testzrc20/TestZRC20.sol @@ -85,6 +85,8 @@ contract TestZRC20 is IZRC20, IZRC20Metadata, ZRC20Errors { string private _symbol; uint8 private _decimals; + address public gatewayAddress; + /// @notice extend the contract with new fields to test contract bytecode upgrade uint256 public newField; string public newPublicField; diff --git a/e2e/contracts/testzrc20/bindings.go b/e2e/contracts/testzrc20/bindings.go index dc615b9770..934a84aad9 100644 --- a/e2e/contracts/testzrc20/bindings.go +++ b/e2e/contracts/testzrc20/bindings.go @@ -1,4 +1,4 @@ -//go:generate sh -c "solc --evm-version paris TestZRC20.sol --combined-json abi,bin | jq '.contracts.\"TestZRC20.sol:TestZRC20\"' > TestZRC20.json" +//go:generate sh -c "solc --evm-version london TestZRC20.sol --combined-json abi,bin | jq '.contracts.\"TestZRC20.sol:TestZRC20\"' > TestZRC20.json" //go:generate sh -c "cat TestZRC20.json | jq .abi > TestZRC20.abi" //go:generate sh -c "cat TestZRC20.json | jq .bin | tr -d '\"' > TestZRC20.bin" //go:generate sh -c "abigen --abi TestZRC20.abi --bin TestZRC20.bin --pkg testzrc20 --type TestZRC20 --out TestZRC20.go" diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index b2e78ed380..5b810276db 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -54,8 +54,10 @@ const ( /* Solana tests */ - TestSolanaDepositName = "solana_deposit" - TestSolanaWithdrawName = "solana_withdraw" + TestSolanaDepositName = "solana_deposit" + TestSolanaWithdrawName = "solana_withdraw" + TestSolanaDepositAndCallName = "solana_deposit_and_call" + TestSolanaDepositAndCallRefundName = "solana_deposit_and_call_refund" /* Bitcoin tests @@ -108,14 +110,39 @@ const ( TestUpdateBytecodeConnectorName = "update_bytecode_connector" TestRateLimiterName = "rate_limiter" TestCriticalAdminTransactionsName = "critical_admin_transactions" + TestPauseERC20CustodyName = "pause_erc20_custody" + TestMigrateERC20CustodyFundsName = "migrate_erc20_custody_funds" + TestMigrateTSSName = "migrate_TSS" - TestMigrateTSSName = "migrate_TSS" + /* + V2 smart contract tests + */ + TestV2ETHDepositName = "v2_eth_deposit" + TestV2ETHDepositAndCallName = "v2_eth_deposit_and_call" + TestV2ETHDepositAndCallRevertName = "v2_eth_deposit_and_call_revert" + TestV2ETHDepositAndCallRevertWithCallName = "v2_eth_deposit_and_call_revert_with_call" + TestV2ETHWithdrawName = "v2_eth_withdraw" + TestV2ETHWithdrawAndCallName = "v2_eth_withdraw_and_call" + TestV2ETHWithdrawAndCallRevertName = "v2_eth_withdraw_and_call_revert" + TestV2ETHWithdrawAndCallRevertWithCallName = "v2_eth_withdraw_and_call_revert_with_call" + TestV2ERC20DepositName = "v2_erc20_deposit" + TestV2ERC20DepositAndCallName = "v2_erc20_deposit_and_call" + TestV2ERC20DepositAndCallRevertName = "v2_erc20_deposit_and_call_revert" + TestV2ERC20DepositAndCallRevertWithCallName = "v2_erc20_deposit_and_call_revert_with_call" + TestV2ERC20WithdrawName = "v2_erc20_withdraw" + TestV2ERC20WithdrawAndCallName = "v2_erc20_withdraw_and_call" + TestV2ERC20WithdrawAndCallRevertName = "v2_erc20_withdraw_and_call_revert" + TestV2ERC20WithdrawAndCallRevertWithCallName = "v2_erc20_withdraw_and_call_revert_with_call" + TestV2ZEVMToEVMCallName = "v2_zevm_to_evm_call" + TestV2EVMToZEVMCallName = "v2_evm_to_zevm_call" /* - Special tests + Operational tests Not used to test functionalities but do various interactions with the netwoks */ - TestDeploy = "deploy" + TestDeploy = "deploy" + TestOperationAddLiquidityETHName = "add_liquidity_eth" + TestOperationAddLiquidityERC20Name = "add_liquidity_erc20" /* Stateful precompiled contracts tests @@ -345,7 +372,7 @@ var AllE2ETests = []runner.E2ETest{ TestSolanaDepositName, "deposit SOL into ZEVM", []runner.ArgDefinition{ - {Description: "amount in lamport", DefaultValue: "13370000"}, + {Description: "amount in lamport", DefaultValue: "1200000"}, }, TestSolanaDeposit, ), @@ -353,10 +380,26 @@ var AllE2ETests = []runner.E2ETest{ TestSolanaWithdrawName, "withdraw SOL from ZEVM", []runner.ArgDefinition{ - {Description: "amount in lamport", DefaultValue: "1336000"}, + {Description: "amount in lamport", DefaultValue: "1000000"}, }, TestSolanaWithdraw, ), + runner.NewE2ETest( + TestSolanaDepositAndCallName, + "deposit SOL into ZEVM and call a contract", + []runner.ArgDefinition{ + {Description: "amount in lamport", DefaultValue: "1200000"}, + }, + TestSolanaDepositAndCall, + ), + runner.NewE2ETest( + TestSolanaDepositAndCallRefundName, + "deposit SOL into ZEVM and call a contract that reverts; should refund", + []runner.ArgDefinition{ + {Description: "amount in lamport", DefaultValue: "1200000"}, + }, + TestSolanaDepositAndCallRefund, + ), /* Bitcoin tests */ @@ -577,6 +620,167 @@ var AllE2ETests = []runner.E2ETest{ []runner.ArgDefinition{}, TestCriticalAdminTransactions, ), + runner.NewE2ETest( + TestMigrateTSSName, + "migrate TSS funds", + []runner.ArgDefinition{}, + TestMigrateTSS, + ), + runner.NewE2ETest( + TestPauseERC20CustodyName, + "pausing ERC20 custody on ZetaChain", + []runner.ArgDefinition{}, + TestPauseERC20Custody, + ), + runner.NewE2ETest( + TestMigrateERC20CustodyFundsName, + "migrate ERC20 custody funds", + []runner.ArgDefinition{}, + TestMigrateERC20CustodyFunds, + ), + /* + V2 smart contract tests + */ + runner.NewE2ETest( + TestV2ETHDepositName, + "deposit Ether into ZEVM using V2 contract", + []runner.ArgDefinition{ + {Description: "amount in wei", DefaultValue: "100000000000000000000"}, + }, + TestV2ETHDeposit, + ), + runner.NewE2ETest( + TestV2ETHDepositAndCallName, + "deposit Ether into ZEVM and call a contract using V2 contract", + []runner.ArgDefinition{ + {Description: "amount in wei", DefaultValue: "10000000000000000"}, + }, + TestV2ETHDepositAndCall, + ), + runner.NewE2ETest( + TestV2ETHDepositAndCallRevertName, + "deposit Ether into ZEVM and call a contract using V2 contract that reverts", + []runner.ArgDefinition{ + {Description: "amount in wei", DefaultValue: "10000000000000000"}, + }, + TestV2ETHDepositAndCallRevert, + ), + runner.NewE2ETest( + TestV2ETHDepositAndCallRevertWithCallName, + "deposit Ether into ZEVM and call a contract using V2 contract that reverts with a onRevert call", + []runner.ArgDefinition{ + {Description: "amount in wei", DefaultValue: "10000000000000000"}, + }, + TestV2ETHDepositAndCallRevertWithCall, + ), + runner.NewE2ETest( + TestV2ETHWithdrawName, + "withdraw Ether from ZEVM using V2 contract", + []runner.ArgDefinition{ + {Description: "amount in wei", DefaultValue: "100000"}, + }, + TestV2ETHWithdraw, + ), + runner.NewE2ETest( + TestV2ETHWithdrawAndCallName, + "withdraw Ether from ZEVM and call a contract using V2 contract", + []runner.ArgDefinition{ + {Description: "amount in wei", DefaultValue: "100000"}, + }, + TestV2ETHWithdrawAndCall, + ), + runner.NewE2ETest( + TestV2ETHWithdrawAndCallRevertName, + "withdraw Ether from ZEVM and call a contract using V2 contract that reverts", + []runner.ArgDefinition{ + {Description: "amount in wei", DefaultValue: "100000"}, + }, + TestV2ETHWithdrawAndCallRevert, + ), + runner.NewE2ETest( + TestV2ETHWithdrawAndCallRevertWithCallName, + "withdraw Ether from ZEVM and call a contract using V2 contract that reverts with a onRevert call", + []runner.ArgDefinition{ + {Description: "amount in wei", DefaultValue: "100000"}, + }, + TestV2ETHWithdrawAndCallRevertWithCall, + ), + runner.NewE2ETest( + TestV2ERC20DepositName, + "deposit ERC20 into ZEVM using V2 contract", + []runner.ArgDefinition{ + {Description: "amount", DefaultValue: "100000000000000000000"}, + }, + TestV2ERC20Deposit, + ), + runner.NewE2ETest( + TestV2ERC20DepositAndCallName, + "deposit ERC20 into ZEVM and call a contract using V2 contract", + []runner.ArgDefinition{ + {Description: "amount", DefaultValue: "100000"}, + }, + TestV2ERC20DepositAndCall, + ), + runner.NewE2ETest( + TestV2ERC20DepositAndCallRevertName, + "deposit ERC20 into ZEVM and call a contract using V2 contract that reverts", + []runner.ArgDefinition{ + {Description: "amount", DefaultValue: "10000000000000000000"}, + }, + TestV2ERC20DepositAndCallRevert, + ), + runner.NewE2ETest( + TestV2ERC20DepositAndCallRevertWithCallName, + "deposit ERC20 into ZEVM and call a contract using V2 contract that reverts with a onRevert call", + []runner.ArgDefinition{ + {Description: "amount", DefaultValue: "10000000000000000000"}, + }, + TestV2ERC20DepositAndCallRevertWithCall, + ), + runner.NewE2ETest( + TestV2ERC20WithdrawName, + "withdraw ERC20 from ZEVM using V2 contract", + []runner.ArgDefinition{ + {Description: "amount", DefaultValue: "1000"}, + }, + TestV2ERC20Withdraw, + ), + runner.NewE2ETest( + TestV2ERC20WithdrawAndCallName, + "withdraw ERC20 from ZEVM and call a contract using V2 contract", + []runner.ArgDefinition{ + {Description: "amount", DefaultValue: "1000"}, + }, + TestV2ERC20WithdrawAndCall, + ), + runner.NewE2ETest( + TestV2ERC20WithdrawAndCallRevertName, + "withdraw ERC20 from ZEVM and call a contract using V2 contract that reverts", + []runner.ArgDefinition{ + {Description: "amount", DefaultValue: "1000"}, + }, + TestV2ERC20WithdrawAndCallRevert, + ), + runner.NewE2ETest( + TestV2ERC20WithdrawAndCallRevertWithCallName, + "withdraw ERC20 from ZEVM and call a contract using V2 contract that reverts with a onRevert call", + []runner.ArgDefinition{ + {Description: "amount", DefaultValue: "1000"}, + }, + TestV2ERC20WithdrawAndCallRevertWithCall, + ), + runner.NewE2ETest( + TestV2ZEVMToEVMCallName, + "zevm -> evm call using V2 contract", + []runner.ArgDefinition{}, + TestV2ZEVMToEVMCall, + ), + runner.NewE2ETest( + TestV2EVMToZEVMCallName, + "evm -> zevm call using V2 contract", + []runner.ArgDefinition{}, + TestV2EVMToZEVMCall, + ), /* Special tests */ @@ -589,10 +793,22 @@ var AllE2ETests = []runner.E2ETest{ TestDeployContract, ), runner.NewE2ETest( - TestMigrateTSSName, - "migrate TSS funds", - []runner.ArgDefinition{}, - TestMigrateTSS, + TestOperationAddLiquidityETHName, + "add liquidity to the ZETA/ETH pool", + []runner.ArgDefinition{ + {Description: "amountZETA", DefaultValue: "50000000000000000000"}, + {Description: "amountETH", DefaultValue: "50000000000000000000"}, + }, + TestOperationAddLiquidityETH, + ), + runner.NewE2ETest( + TestOperationAddLiquidityERC20Name, + "add liquidity to the ZETA/ERC20 pool", + []runner.ArgDefinition{ + {Description: "amountZETA", DefaultValue: "50000000000000000000"}, + {Description: "amountERC20", DefaultValue: "50000000000000000000"}, + }, + TestOperationAddLiquidityERC20, ), /* Stateful precompiled contracts tests diff --git a/e2e/e2etests/helpers.go b/e2e/e2etests/helpers.go index 4dc37498d9..8146ff52b8 100644 --- a/e2e/e2etests/helpers.go +++ b/e2e/e2etests/helpers.go @@ -108,6 +108,13 @@ func parseInt(t require.TestingT, s string) int { return v } +func parseBigInt(t require.TestingT, s string) *big.Int { + v, ok := big.NewInt(0).SetString(s, 10) + require.True(t, ok, "unable to parse big.Int from %q", s) + + return v +} + // bigIntFromFloat64 takes float64 (e.g. 0.001) that represents btc amount // and converts it to big.Int for downstream usage. func btcAmountFromFloat64(t require.TestingT, amount float64) *big.Int { diff --git a/e2e/e2etests/test_context_upgrade.go b/e2e/e2etests/test_context_upgrade.go index 4a71b07428..82e81f2147 100644 --- a/e2e/e2etests/test_context_upgrade.go +++ b/e2e/e2etests/test_context_upgrade.go @@ -2,7 +2,6 @@ package e2etests import ( "bytes" - "math/big" "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -17,8 +16,7 @@ func TestContextUpgrade(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) // parse the value from the provided arguments - value, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid value specified for TestContextUpgrade.") + value := parseBigInt(r, args[0]) data := make([]byte, 0, 32) data = append(data, r.ContextAppAddr.Bytes()...) diff --git a/e2e/e2etests/test_donation.go b/e2e/e2etests/test_donation.go index 2347e97e23..d628cce62e 100644 --- a/e2e/e2etests/test_donation.go +++ b/e2e/e2etests/test_donation.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -14,8 +12,8 @@ import ( func TestDonationEther(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestDonationEther.") + // parse the donation amount + amount := parseBigInt(r, args[0]) txDonation, err := r.SendEther(r.TSSAddress, amount, []byte(constant.DonationMessage)) require.NoError(r, err) diff --git a/e2e/e2etests/test_erc20_deposit.go b/e2e/e2etests/test_erc20_deposit.go index c7f1d4fc5f..66d640f0c4 100644 --- a/e2e/e2etests/test_erc20_deposit.go +++ b/e2e/e2etests/test_erc20_deposit.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -12,8 +10,8 @@ import ( func TestERC20Deposit(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestERC20Deposit.") + // parse the deposit amount + amount := parseBigInt(r, args[0]) hash := r.DepositERC20WithAmountAndMessage(r.EVMAddress(), amount, []byte{}) diff --git a/e2e/e2etests/test_erc20_deposit_restricted_address.go b/e2e/e2etests/test_erc20_deposit_restricted_address.go index 758da5a969..ea5dd81252 100644 --- a/e2e/e2etests/test_erc20_deposit_restricted_address.go +++ b/e2e/e2etests/test_erc20_deposit_restricted_address.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - ethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -13,8 +11,8 @@ import ( func TestERC20DepositRestricted(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok) + // parse the deposit amount + amount := parseBigInt(r, args[0]) // deposit ERC20 to restricted address r.DepositERC20WithAmountAndMessage(ethcommon.HexToAddress(testutils.RestrictedEVMAddressTest), amount, []byte{}) diff --git a/e2e/e2etests/test_erc20_multiple_deposits.go b/e2e/e2etests/test_erc20_multiple_deposits.go index 87b8309924..8948bc3fe7 100644 --- a/e2e/e2etests/test_erc20_multiple_deposits.go +++ b/e2e/e2etests/test_erc20_multiple_deposits.go @@ -15,11 +15,9 @@ import ( func TestMultipleERC20Deposit(r *runner.E2ERunner, args []string) { require.Len(r, args, 2) - depositAmount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok) - - numberOfDeposits, ok := big.NewInt(0).SetString(args[1], 10) - require.True(r, ok) + // parse the deposit amount and count + depositAmount := parseBigInt(r, args[0]) + numberOfDeposits := parseBigInt(r, args[1]) require.NotZero(r, numberOfDeposits.Int64()) initialBal, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) diff --git a/e2e/e2etests/test_erc20_multiple_withdraws.go b/e2e/e2etests/test_erc20_multiple_withdraws.go index 53a45d1b41..80bdcac46e 100644 --- a/e2e/e2etests/test_erc20_multiple_withdraws.go +++ b/e2e/e2etests/test_erc20_multiple_withdraws.go @@ -16,17 +16,15 @@ func TestMultipleERC20Withdraws(r *runner.E2ERunner, args []string) { approvedAmount := big.NewInt(1e18) - withdrawalAmount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok) + // parse the withdrawal amount and number of withdrawals + withdrawalAmount := parseBigInt(r, args[0]) require.Equal( r, -1, withdrawalAmount.Cmp(approvedAmount), "Invalid withdrawal amount specified for TestMultipleWithdraws.", ) - - numberOfWithdrawals, ok := big.NewInt(0).SetString(args[1], 10) - require.True(r, ok) + numberOfWithdrawals := parseBigInt(r, args[1]) require.NotEmpty(r, numberOfWithdrawals.Int64()) // calculate total withdrawal to ensure it doesn't exceed approved amount. diff --git a/e2e/e2etests/test_eth_deposit.go b/e2e/e2etests/test_eth_deposit.go index c5f0701516..419dbe2298 100644 --- a/e2e/e2etests/test_eth_deposit.go +++ b/e2e/e2etests/test_eth_deposit.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -13,8 +11,8 @@ import ( func TestEtherDeposit(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestEtherDeposit.") + // parse the deposit amount + amount := parseBigInt(r, args[0]) hash := r.DepositEtherWithAmount(amount) // in wei // wait for the cctx to be mined diff --git a/e2e/e2etests/test_eth_deposit_call.go b/e2e/e2etests/test_eth_deposit_call.go index 7fd928266d..9d024e146a 100644 --- a/e2e/e2etests/test_eth_deposit_call.go +++ b/e2e/e2etests/test_eth_deposit_call.go @@ -1,9 +1,6 @@ package e2etests import ( - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" @@ -17,8 +14,8 @@ import ( func TestEtherDepositAndCall(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - value, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestEtherDepositAndCall.") + // parse deposit amount + value := parseBigInt(r, args[0]) r.Logger.Info("Deploying example contract") exampleAddr, _, exampleContract, err := testcontract.DeployExample(r.ZEVMAuth, r.ZEVMClient) @@ -57,16 +54,7 @@ func TestEtherDepositAndCall(r *runner.E2ERunner, args []string) { utils.RequireCCTXStatus(r, cctx, types.CctxStatus_OutboundMined) // Checking example contract has been called, bar value should be set to amount - bar, err := exampleContract.Bar(&bind.CallOpts{}) - require.NoError(r, err) - require.Equal( - r, - 0, - bar.Cmp(value), - "cross-chain call failed bar value %s should be equal to amount %s", - bar.String(), - value.String(), - ) + utils.MustHaveCalledExampleContract(r, exampleContract, value) r.Logger.Info("Cross-chain call succeeded") r.Logger.Info("Deploying reverter contract") @@ -100,6 +88,5 @@ func TestEtherDepositAndCall(r *runner.E2ERunner, args []string) { r.Logger.Info("Cross-chain call to reverter reverted") // check the status message contains revert error hash in case of revert - // 0xbfb4ebcf is the hash of "Foo()" - require.Contains(r, cctx.CctxStatus.StatusMessage, "0xbfb4ebcf") + require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevertFoo) } diff --git a/e2e/e2etests/test_eth_deposit_refund.go b/e2e/e2etests/test_eth_deposit_refund.go index 946c457927..343452cdf1 100644 --- a/e2e/e2etests/test_eth_deposit_refund.go +++ b/e2e/e2etests/test_eth_deposit_refund.go @@ -15,8 +15,8 @@ import ( func TestEtherDepositAndCallRefund(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - value, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestEtherDepositAndCallRefund.") + // parse the deposit amount + value := parseBigInt(r, args[0]) evmClient := r.EVMClient diff --git a/e2e/e2etests/test_message_passing_evm_to_zevm.go b/e2e/e2etests/test_message_passing_evm_to_zevm.go index 4a7ac7f906..09dac29a73 100644 --- a/e2e/e2etests/test_message_passing_evm_to_zevm.go +++ b/e2e/e2etests/test_message_passing_evm_to_zevm.go @@ -1,7 +1,6 @@ package e2etests import ( - "fmt" "math/big" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -17,8 +16,8 @@ import ( func TestMessagePassingEVMtoZEVM(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestMessagePassingEVMtoZEVM.") + // parse the amount + amount := parseBigInt(r, args[0]) // Set destination details zEVMChainID, err := r.ZEVMClient.ChainID(r.Ctx) @@ -56,7 +55,7 @@ func TestMessagePassingEVMtoZEVM(r *runner.E2ERunner, args []string) { cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, receipt.TxHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) utils.RequireCCTXStatus(r, cctx, cctxtypes.CctxStatus_OutboundMined) - r.Logger.Info(fmt.Sprintf("🔄 Cctx mined for contract call chain zevm %s", cctx.Index)) + r.Logger.Info("🔄 Cctx mined for contract call chain zevm %s", cctx.Index) // On finalization the Fungible module calls the onReceive function which in turn calls the onZetaMessage function on the destination contract receipt, err = r.ZEVMClient.TransactionReceipt(r.Ctx, ethcommon.HexToHash(cctx.GetCurrentOutboundParam().Hash)) diff --git a/e2e/e2etests/test_message_passing_evm_to_zevm_revert.go b/e2e/e2etests/test_message_passing_evm_to_zevm_revert.go index 85aca0caae..8d5a1e9469 100644 --- a/e2e/e2etests/test_message_passing_evm_to_zevm_revert.go +++ b/e2e/e2etests/test_message_passing_evm_to_zevm_revert.go @@ -22,8 +22,8 @@ func TestMessagePassingEVMtoZEVMRevert(r *runner.E2ERunner, args []string) { fungibleEthAddress := ethcommon.HexToAddress(fungibleModuleAddress) require.True(r, fungibleEthAddress != ethcommon.Address{}, "invalid fungible module address") - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok) + // parse the amount + amount := parseBigInt(r, args[0]) // Set destination details zEVMChainID, err := r.ZEVMClient.ChainID(r.Ctx) diff --git a/e2e/e2etests/test_message_passing_evm_to_zevm_revert_fail.go b/e2e/e2etests/test_message_passing_evm_to_zevm_revert_fail.go index a767f619ad..271c33e9c8 100644 --- a/e2e/e2etests/test_message_passing_evm_to_zevm_revert_fail.go +++ b/e2e/e2etests/test_message_passing_evm_to_zevm_revert_fail.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/stretchr/testify/require" @@ -15,8 +13,8 @@ import ( func TestMessagePassingEVMtoZEVMRevertFail(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestMessagePassingEVMtoZEVMRevertFail.") + // parse the amount + amount := parseBigInt(r, args[0]) // Deploying a test contract not containing a logic for reverting the cctx testDappNoRevertEVMAddr, tx, testDappNoRevertEVM, err := testdappnorevert.DeployTestDAppNoRevert( diff --git a/e2e/e2etests/test_message_passing_external_chains.go b/e2e/e2etests/test_message_passing_external_chains.go index 2c2ed65997..96cdc2fe99 100644 --- a/e2e/e2etests/test_message_passing_external_chains.go +++ b/e2e/e2etests/test_message_passing_external_chains.go @@ -5,7 +5,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" + zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" @@ -18,8 +18,8 @@ import ( func TestMessagePassingExternalChains(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestMessagePassing.") + // parse the amount + amount := parseBigInt(r, args[0]) chainID, err := r.EVMClient.ChainID(r.Ctx) require.NoError(r, err) diff --git a/e2e/e2etests/test_message_passing_external_chains_revert.go b/e2e/e2etests/test_message_passing_external_chains_revert.go index 37146a4c78..0f2f5c1c7b 100644 --- a/e2e/e2etests/test_message_passing_external_chains_revert.go +++ b/e2e/e2etests/test_message_passing_external_chains_revert.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -19,8 +17,8 @@ import ( func TestMessagePassingRevertSuccessExternalChains(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok) + // parse the amount + amount := parseBigInt(r, args[0]) chainID, err := r.EVMClient.ChainID(r.Ctx) require.NoError(r, err) diff --git a/e2e/e2etests/test_message_passing_external_chains_revert_fail.go b/e2e/e2etests/test_message_passing_external_chains_revert_fail.go index 9cc1ee8d8f..8504aaca56 100644 --- a/e2e/e2etests/test_message_passing_external_chains_revert_fail.go +++ b/e2e/e2etests/test_message_passing_external_chains_revert_fail.go @@ -6,7 +6,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" - zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" + zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" @@ -19,8 +19,8 @@ import ( func TestMessagePassingRevertFailExternalChains(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestMessagePassingRevertFail.") + // parse the amount + amount := parseBigInt(r, args[0]) chainID, err := r.EVMClient.ChainID(r.Ctx) require.NoError(r, err) diff --git a/e2e/e2etests/test_message_passing_zevm_to_evm.go b/e2e/e2etests/test_message_passing_zevm_to_evm.go index dde161f2f9..4e791b57c7 100644 --- a/e2e/e2etests/test_message_passing_zevm_to_evm.go +++ b/e2e/e2etests/test_message_passing_zevm_to_evm.go @@ -16,8 +16,8 @@ import ( func TestMessagePassingZEVMtoEVM(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestMessagePassingZEVMtoEVM.") + // parse the amount + amount := parseBigInt(r, args[0]) // Set destination details EVMChainID, err := r.EVMClient.ChainID(r.Ctx) diff --git a/e2e/e2etests/test_message_passing_zevm_to_evm_revert.go b/e2e/e2etests/test_message_passing_zevm_to_evm_revert.go index 85f81f0a04..e837c1c05c 100644 --- a/e2e/e2etests/test_message_passing_zevm_to_evm_revert.go +++ b/e2e/e2etests/test_message_passing_zevm_to_evm_revert.go @@ -16,8 +16,8 @@ import ( func TestMessagePassingZEVMtoEVMRevert(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestMessagePassingZEVMtoEVMRevert.") + // parse the amount + amount := parseBigInt(r, args[0]) // Set destination details EVMChainID, err := r.EVMClient.ChainID(r.Ctx) diff --git a/e2e/e2etests/test_message_passing_zevm_to_evm_revert_fail.go b/e2e/e2etests/test_message_passing_zevm_to_evm_revert_fail.go index cc22db23a3..506bb562cd 100644 --- a/e2e/e2etests/test_message_passing_zevm_to_evm_revert_fail.go +++ b/e2e/e2etests/test_message_passing_zevm_to_evm_revert_fail.go @@ -15,8 +15,8 @@ import ( func TestMessagePassingZEVMtoEVMRevertFail(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestMessagePassingZEVMtoEVMRevertFail.") + // parse the amount + amount := parseBigInt(r, args[0]) // Deploying a test contract not containing a logic for reverting the cctx testDappNoRevertAddr, tx, testDappNoRevert, err := testdappnorevert.DeployTestDAppNoRevert( diff --git a/e2e/e2etests/test_migrate_chain_support.go b/e2e/e2etests/test_migrate_chain_support.go index b8a92cd472..62c9bac84f 100644 --- a/e2e/e2etests/test_migrate_chain_support.go +++ b/e2e/e2etests/test_migrate_chain_support.go @@ -12,7 +12,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/fatih/color" "github.com/stretchr/testify/require" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/e2e/config" "github.com/zeta-chain/zetacore/e2e/runner" diff --git a/e2e/e2etests/test_migrate_erc20_custody_funds.go b/e2e/e2etests/test_migrate_erc20_custody_funds.go new file mode 100644 index 0000000000..1c38909b3d --- /dev/null +++ b/e2e/e2etests/test_migrate_erc20_custody_funds.go @@ -0,0 +1,61 @@ +package e2etests + +import ( + sdkmath "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/txserver" + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/testutil/sample" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// TestMigrateERC20CustodyFunds tests the migration of ERC20 custody funds +func TestMigrateERC20CustodyFunds(r *runner.E2ERunner, _ []string) { + // get erc20 balance on ERC20 custody contract + balance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, r.ERC20CustodyAddr) + require.NoError(r, err) + + // get EVM chain ID + chainID, err := r.EVMClient.ChainID(r.Ctx) + require.NoError(r, err) + + newAddr := sample.EthAddress() + + // send MigrateERC20CustodyFunds command + // NOTE: we currently use a random address for the destination as a sufficient way to check migration + // TODO: makes the test more complete and perform a withdraw to new custody once the contract V2 architecture is integrated + // https://github.com/zeta-chain/node/issues/2474 + msg := crosschaintypes.NewMsgMigrateERC20CustodyFunds( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + chainID.Int64(), + newAddr.Hex(), + r.ERC20Addr.Hex(), + sdkmath.NewUintFromBigInt(balance), + ) + res, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msg) + require.NoError(r, err) + + // fetch cctx index from tx response + cctxIndex, err := txserver.FetchAttributeFromTxResponse(res, "cctx_index") + require.NoError(r, err) + + cctxRes, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: cctxIndex}) + require.NoError(r, err) + + cctx := cctxRes.CrossChainTx + r.Logger.CCTX(*cctx, "migration") + + // wait for the cctx to be mined + r.WaitForMinedCCTXFromIndex(cctxIndex) + + // check ERC20 balance on new address + newAddrBalance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, newAddr) + require.NoError(r, err) + require.Equal(r, balance, newAddrBalance) + + // artificially set the ERC20 Custody address to the new address to prevent accounting check from failing + r.ERC20CustodyAddr = newAddr +} diff --git a/e2e/e2etests/test_migrate_tss.go b/e2e/e2etests/test_migrate_tss.go index c72b876f0f..c1bb79c062 100644 --- a/e2e/e2etests/test_migrate_tss.go +++ b/e2e/e2etests/test_migrate_tss.go @@ -2,7 +2,6 @@ package e2etests import ( "context" - "fmt" "sort" "strconv" "time" @@ -146,9 +145,9 @@ func TestMigrateTSS(r *runner.E2ERunner, _ []string) { btcTSSBalanceNew += utxo.Amount } - r.Logger.Info(fmt.Sprintf("BTC Balance Old: %f", btcTSSBalanceOld*1e8)) - r.Logger.Info(fmt.Sprintf("BTC Balance New: %f", btcTSSBalanceNew*1e8)) - r.Logger.Info(fmt.Sprintf("Migrator amount : %s", cctxBTC.GetCurrentOutboundParam().Amount)) + r.Logger.Info("BTC Balance Old: %f", btcTSSBalanceOld*1e8) + r.Logger.Info("BTC Balance New: %f", btcTSSBalanceNew*1e8) + r.Logger.Info("Migrator amount : %s", cctxBTC.GetCurrentOutboundParam().Amount) // btcTSSBalanceNew should be less than btcTSSBalanceOld as there is some loss of funds during migration // #nosec G701 e2eTest - always in range @@ -165,9 +164,9 @@ func TestMigrateTSS(r *runner.E2ERunner, _ []string) { ethTSSBalanceNew, err := r.EVMClient.BalanceAt(context.Background(), r.TSSAddress, nil) require.NoError(r, err) - r.Logger.Info(fmt.Sprintf("TSS Balance Old: %s", ethTSSBalanceOld.String())) - r.Logger.Info(fmt.Sprintf("TSS Balance New: %s", ethTSSBalanceNew.String())) - r.Logger.Info(fmt.Sprintf("Migrator amount : %s", cctxETH.GetCurrentOutboundParam().Amount.String())) + r.Logger.Info("TSS Balance Old: %s", ethTSSBalanceOld.String()) + r.Logger.Info("TSS Balance New: %s", ethTSSBalanceNew.String()) + r.Logger.Info("Migrator amount : %s", cctxETH.GetCurrentOutboundParam().Amount.String()) // ethTSSBalanceNew should be less than ethTSSBalanceOld as there is some loss of funds during migration require.Equal(r, ethTSSBalanceNew.String(), cctxETH.GetCurrentOutboundParam().Amount.String()) diff --git a/e2e/e2etests/test_operation_add_liquidity_erc20.go b/e2e/e2etests/test_operation_add_liquidity_erc20.go new file mode 100644 index 0000000000..b52d22391e --- /dev/null +++ b/e2e/e2etests/test_operation_add_liquidity_erc20.go @@ -0,0 +1,25 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/e2e/runner" +) + +// TestOperationAddLiquidityERC20 is an operational test to add liquidity in erc20 token +func TestOperationAddLiquidityERC20(r *runner.E2ERunner, args []string) { + require.Len(r, args, 2) + + liqZETA := big.NewInt(0) + _, ok := liqZETA.SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestOperationAddLiquidityERC20") + + liqERC20 := big.NewInt(0) + _, ok = liqERC20.SetString(args[1], 10) + require.True(r, ok, "Invalid amount specified for TestOperationAddLiquidityERC20") + + // perform the add liquidity + r.AddLiquidityERC20(liqZETA, liqERC20) +} diff --git a/e2e/e2etests/test_operation_add_liquidity_eth.go b/e2e/e2etests/test_operation_add_liquidity_eth.go new file mode 100644 index 0000000000..dae3ae4c65 --- /dev/null +++ b/e2e/e2etests/test_operation_add_liquidity_eth.go @@ -0,0 +1,25 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/e2e/runner" +) + +// TestOperationAddLiquidityETH is an operational test to add liquidity in gas token +func TestOperationAddLiquidityETH(r *runner.E2ERunner, args []string) { + require.Len(r, args, 2) + + liqZETA := big.NewInt(0) + _, ok := liqZETA.SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestOperationAddLiquidityETH") + + liqETH := big.NewInt(0) + _, ok = liqETH.SetString(args[1], 10) + require.True(r, ok, "Invalid amount specified for TestOperationAddLiquidityETH") + + // perform the add liquidity + r.AddLiquidityETH(liqZETA, liqETH) +} diff --git a/e2e/e2etests/test_pause_erc20_custody.go b/e2e/e2etests/test_pause_erc20_custody.go new file mode 100644 index 0000000000..d39fbab512 --- /dev/null +++ b/e2e/e2etests/test_pause_erc20_custody.go @@ -0,0 +1,81 @@ +package e2etests + +import ( + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/txserver" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// TestPauseERC20Custody tests the pausing and unpausing of ERC20 custody contracts on the EVM chain +func TestPauseERC20Custody(r *runner.E2ERunner, _ []string) { + // get EVM chain ID + chainID, err := r.EVMClient.ChainID(r.Ctx) + require.NoError(r, err) + + // check ERC20 custody contract is not paused + paused, err := r.ERC20Custody.Paused(&bind.CallOpts{}) + require.NoError(r, err) + require.False(r, paused) + + // Part 1: Pause ERC20 custody contract + + // send command for pausing ERC20 custody contract + msg := crosschaintypes.NewMsgUpdateERC20CustodyPauseStatus( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + chainID.Int64(), + true, + ) + res, err := r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msg) + require.NoError(r, err) + + // fetch cctx index from tx response + cctxIndex, err := txserver.FetchAttributeFromTxResponse(res, "cctx_index") + require.NoError(r, err) + + cctxRes, err := r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: cctxIndex}) + require.NoError(r, err) + + cctx := cctxRes.CrossChainTx + r.Logger.CCTX(*cctx, "pausing") + + // wait for the cctx to be mined + r.WaitForMinedCCTXFromIndex(cctxIndex) + + // check ERC20 custody contract is paused + paused, err = r.ERC20Custody.Paused(&bind.CallOpts{}) + require.NoError(r, err) + require.True(r, paused) + + // Part 2: Unpause ERC20 custody contract + + // send command for unpausing ERC20 custody contract + msg = crosschaintypes.NewMsgUpdateERC20CustodyPauseStatus( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.AdminPolicyName), + chainID.Int64(), + false, + ) + res, err = r.ZetaTxServer.BroadcastTx(utils.AdminPolicyName, msg) + require.NoError(r, err) + + // fetch cctx index from tx response + cctxIndex, err = txserver.FetchAttributeFromTxResponse(res, "cctx_index") + require.NoError(r, err) + + cctxRes, err = r.CctxClient.Cctx(r.Ctx, &crosschaintypes.QueryGetCctxRequest{Index: cctxIndex}) + require.NoError(r, err) + + cctx = cctxRes.CrossChainTx + r.Logger.CCTX(*cctx, "unpausing") + + // wait for the cctx to be mined + r.WaitForMinedCCTXFromIndex(cctxIndex) + + // check ERC20 custody contract is unpaused + paused, err = r.ERC20Custody.Paused(&bind.CallOpts{}) + require.NoError(r, err) + require.False(r, paused) +} diff --git a/e2e/e2etests/test_solana_deposit.go b/e2e/e2etests/test_solana_deposit.go index 486ae89782..b1c7622c8c 100644 --- a/e2e/e2etests/test_solana_deposit.go +++ b/e2e/e2etests/test_solana_deposit.go @@ -3,7 +3,7 @@ package e2etests import ( "math/big" - "github.com/gagliardetto/solana-go" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -14,25 +14,28 @@ import ( func TestSolanaDeposit(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - // parse deposit amount (in lamports) - // #nosec G115 e2e - always in range - depositAmount := big.NewInt(int64(parseInt(r, args[0]))) - - // load deployer private key - privkey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String()) + // get ERC20 SOL balance before deposit + balanceBefore, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) require.NoError(r, err) + r.Logger.Info("runner balance of SOL before deposit: %d", balanceBefore) - // create 'deposit' instruction - instruction := r.CreateDepositInstruction(privkey.PublicKey(), r.EVMAddress(), depositAmount.Uint64()) - - // create and sign the transaction - signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, privkey) + // parse deposit amount (in lamports) + depositAmount := parseBigInt(r, args[0]) - // broadcast the transaction and wait for finalization - sig, out := r.BroadcastTxSync(signedTx) - r.Logger.Info("deposit logs: %v", out.Meta.LogMessages) + // execute the deposit transaction + sig := r.SOLDepositAndCall(nil, r.EVMAddress(), depositAmount, nil) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "solana_deposit") utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + + // get ERC20 SOL balance after deposit + balanceAfter, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) + require.NoError(r, err) + r.Logger.Info("runner balance of SOL after deposit: %d", balanceAfter) + + // the runner balance should be increased by the deposit amount + amountIncreased := new(big.Int).Sub(balanceAfter, balanceBefore) + require.Equal(r, depositAmount.String(), amountIncreased.String()) } diff --git a/e2e/e2etests/test_solana_deposit_call.go b/e2e/e2etests/test_solana_deposit_call.go new file mode 100644 index 0000000000..edb0239af9 --- /dev/null +++ b/e2e/e2etests/test_solana_deposit_call.go @@ -0,0 +1,35 @@ +package e2etests + +import ( + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + testcontract "github.com/zeta-chain/zetacore/testutil/contracts" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// TestSolanaDepositAndCall tests deposit of lamports calling a example contract +func TestSolanaDepositAndCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + // parse deposit amount (in lamports) + depositAmount := parseBigInt(r, args[0]) + + // deploy an example contract in ZEVM + contractAddr, _, contract, err := testcontract.DeployExample(r.ZEVMAuth, r.ZEVMClient) + require.NoError(r, err) + r.Logger.Info("Example contract deployed at: %s", contractAddr.String()) + + // execute the deposit transaction + data := []byte("hello lamports") + sig := r.SOLDepositAndCall(nil, contractAddr, depositAmount, data) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "solana_deposit_and_call") + utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_OutboundMined) + + // check if example contract has been called, bar value should be set to amount + utils.MustHaveCalledExampleContract(r, contract, depositAmount) +} diff --git a/e2e/e2etests/test_solana_deposit_refund.go b/e2e/e2etests/test_solana_deposit_refund.go new file mode 100644 index 0000000000..07fe6c64e2 --- /dev/null +++ b/e2e/e2etests/test_solana_deposit_refund.go @@ -0,0 +1,36 @@ +package e2etests + +import ( + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + testcontract "github.com/zeta-chain/zetacore/testutil/contracts" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// TestSolanaDepositAndCallRefund tests deposit of lamports calling a example contract +func TestSolanaDepositAndCallRefund(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + // parse deposit amount (in lamports) + depositAmount := parseBigInt(r, args[0]) + + // deploy a reverter contract in ZEVM + // TODO: consider removing repeated deployments of reverter contract + reverterAddr, _, _, err := testcontract.DeployReverter(r.ZEVMAuth, r.ZEVMClient) + require.NoError(r, err) + r.Logger.Info("Reverter contract deployed at: %s", reverterAddr.String()) + + // execute the deposit transaction + data := []byte("hello reverter") + sig := r.SOLDepositAndCall(nil, reverterAddr, depositAmount, data) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, sig.String(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "solana_deposit_and_refund") + utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_Reverted) + + // check the status message contains revert error hash in case of revert + require.Contains(r, cctx.CctxStatus.StatusMessage, utils.ErrHashRevertFoo) +} diff --git a/e2e/e2etests/test_solana_withdraw.go b/e2e/e2etests/test_solana_withdraw.go index 776944c9b3..6ef0354a06 100644 --- a/e2e/e2etests/test_solana_withdraw.go +++ b/e2e/e2etests/test_solana_withdraw.go @@ -13,15 +13,14 @@ import ( func TestSolanaWithdraw(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - // print balanceAfter of from address - balanceBefore, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.ZEVMAuth.From) + // get ERC20 SOL balance before withdraw + balanceBefore, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) require.NoError(r, err) - r.Logger.Info("from address %s balance of SOL before: %d", r.ZEVMAuth.From, balanceBefore) + r.Logger.Info("runner balance of SOL before withdraw: %d", balanceBefore) // parse withdraw amount (in lamports), approve amount is 1 SOL approvedAmount := new(big.Int).SetUint64(solana.LAMPORTS_PER_SOL) - // #nosec G115 e2e - always in range - withdrawAmount := big.NewInt(int64(parseInt(r, args[0]))) + withdrawAmount := parseBigInt(r, args[0]) require.Equal( r, -1, @@ -36,10 +35,10 @@ func TestSolanaWithdraw(r *runner.E2ERunner, args []string) { // withdraw r.WithdrawSOLZRC20(privkey.PublicKey(), withdrawAmount, approvedAmount) - // print balance of from address after withdraw - balanceAfter, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.ZEVMAuth.From) + // get ERC20 SOL balance after withdraw + balanceAfter, err := r.SOLZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) require.NoError(r, err) - r.Logger.Info("from address %s balance of SOL after: %d", r.ZEVMAuth.From, balanceAfter) + r.Logger.Info("runner balance of SOL after withdraw: %d", balanceAfter) // check if the balance is reduced correctly amountReduced := new(big.Int).Sub(balanceBefore, balanceAfter) diff --git a/e2e/e2etests/test_stress_eth_deposit.go b/e2e/e2etests/test_stress_eth_deposit.go index 04ef846889..dc5c51c7e4 100644 --- a/e2e/e2etests/test_stress_eth_deposit.go +++ b/e2e/e2etests/test_stress_eth_deposit.go @@ -2,7 +2,6 @@ package e2etests import ( "fmt" - "math/big" "time" ethcommon "github.com/ethereum/go-ethereum/common" @@ -18,9 +17,8 @@ import ( func TestStressEtherDeposit(r *runner.E2ERunner, args []string) { require.Len(r, args, 2) - depositAmount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok) - + // parse deposit amount and number of deposits + depositAmount := parseBigInt(r, args[0]) numDeposits := parseInt(r, args[1]) r.Logger.Print("starting stress test of %d deposits", numDeposits) diff --git a/e2e/e2etests/test_stress_eth_withdraw.go b/e2e/e2etests/test_stress_eth_withdraw.go index 2abd036a25..39ff484054 100644 --- a/e2e/e2etests/test_stress_eth_withdraw.go +++ b/e2e/e2etests/test_stress_eth_withdraw.go @@ -19,8 +19,8 @@ import ( func TestStressEtherWithdraw(r *runner.E2ERunner, args []string) { require.Len(r, args, 2) - withdrawalAmount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid withdrawal amount specified for TestStressEtherWithdraw.") + // parse withdraw amount and number of withdraws + withdrawalAmount := parseBigInt(r, args[0]) numWithdraws, err := strconv.Atoi(args[1]) require.NoError(r, err) diff --git a/e2e/e2etests/test_v2_erc20_deposit.go b/e2e/e2etests/test_v2_erc20_deposit.go new file mode 100644 index 0000000000..2dd83c6b3a --- /dev/null +++ b/e2e/e2etests/test_v2_erc20_deposit.go @@ -0,0 +1,38 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestV2ERC20Deposit(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ERC20Deposit") + + r.ApproveERC20OnEVM(r.GatewayEVMAddr) + + oldBalance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) + require.NoError(r, err) + + // perform the deposit + tx := r.V2ERC20Deposit(r.EVMAddress(), amount, gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "deposit") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + // check the balance was updated + newBalance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.EVMAddress()) + require.NoError(r, err) + require.Equal(r, new(big.Int).Add(oldBalance, amount), newBalance) +} diff --git a/e2e/e2etests/test_v2_erc20_deposit_and_call.go b/e2e/e2etests/test_v2_erc20_deposit_and_call.go new file mode 100644 index 0000000000..b6529840ec --- /dev/null +++ b/e2e/e2etests/test_v2_erc20_deposit_and_call.go @@ -0,0 +1,50 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageDepositERC20 = "this is a test ERC20 deposit and call payload" + +func TestV2ERC20DepositAndCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ERC20DepositAndCall") + + r.ApproveERC20OnEVM(r.GatewayEVMAddr) + + r.AssertTestDAppZEVMCalled(false, payloadMessageDepositERC20, amount) + + oldBalance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.TestDAppV2ZEVMAddr) + require.NoError(r, err) + + // perform the deposit + tx := r.V2ERC20DepositAndCall( + r.TestDAppV2ZEVMAddr, + amount, + []byte(payloadMessageDepositERC20), + gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, + ) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "deposit") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + // check the payload was received on the contract + r.AssertTestDAppZEVMCalled(true, payloadMessageDepositERC20, amount) + + // check the balance was updated + newBalance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, r.TestDAppV2ZEVMAddr) + require.NoError(r, err) + require.Equal(r, new(big.Int).Add(oldBalance, amount), newBalance) +} diff --git a/e2e/e2etests/test_v2_erc20_deposit_and_call_revert.go b/e2e/e2etests/test_v2_erc20_deposit_and_call_revert.go new file mode 100644 index 0000000000..3f15808303 --- /dev/null +++ b/e2e/e2etests/test_v2_erc20_deposit_and_call_revert.go @@ -0,0 +1,45 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/testutil/sample" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestV2ERC20DepositAndCallRevert(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ERC20DepositAndCallRevert") + + r.ApproveERC20OnEVM(r.GatewayEVMAddr) + + // use a random address to get the revert amount + revertAddress := sample.EthAddress() + balance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, revertAddress) + require.NoError(r, err) + require.EqualValues(r, int64(0), balance.Int64()) + + // perform the deposit + tx := r.V2ERC20DepositAndCall(r.TestDAppV2ZEVMAddr, amount, []byte("revert"), gatewayevm.RevertOptions{ + RevertAddress: revertAddress, + OnRevertGasLimit: big.NewInt(0), + }) + + // wait for the cctx to be reverted + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "deposit") + require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) + + // check the balance is more than 0 + balance, err = r.ERC20.BalanceOf(&bind.CallOpts{}, revertAddress) + require.NoError(r, err) + require.True(r, balance.Cmp(big.NewInt(0)) > 0) +} diff --git a/e2e/e2etests/test_v2_erc20_deposit_and_call_revert_with_call.go b/e2e/e2etests/test_v2_erc20_deposit_and_call_revert_with_call.go new file mode 100644 index 0000000000..790434f58c --- /dev/null +++ b/e2e/e2etests/test_v2_erc20_deposit_and_call_revert_with_call.go @@ -0,0 +1,41 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageDepositOnRevertERC20 = "this is a test ERC20 deposit and call on revert" + +func TestV2ERC20DepositAndCallRevertWithCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ERC20DepositAndCallRevertWithCall") + + r.ApproveERC20OnEVM(r.GatewayEVMAddr) + + r.AssertTestDAppEVMCalled(false, payloadMessageDepositOnRevertERC20, amount) + + // perform the deposit + tx := r.V2ERC20DepositAndCall(r.TestDAppV2ZEVMAddr, amount, []byte("revert"), gatewayevm.RevertOptions{ + RevertAddress: r.TestDAppV2EVMAddr, + CallOnRevert: true, + RevertMessage: []byte(payloadMessageDepositOnRevertERC20), + OnRevertGasLimit: big.NewInt(200000), + }) + + // wait for the cctx to be reverted + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "deposit") + require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) + + // check the payload was received on the contract + r.AssertTestDAppEVMCalled(true, payloadMessageDepositOnRevertERC20, big.NewInt(0)) +} diff --git a/e2e/e2etests/test_v2_erc20_withdraw.go b/e2e/e2etests/test_v2_erc20_withdraw.go new file mode 100644 index 0000000000..28b0c1ed80 --- /dev/null +++ b/e2e/e2etests/test_v2_erc20_withdraw.go @@ -0,0 +1,30 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestV2ERC20Withdraw(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ERC20Withdraw") + + r.ApproveERC20ZRC20(r.GatewayZEVMAddr) + r.ApproveETHZRC20(r.GatewayZEVMAddr) + + // perform the withdraw + tx := r.V2ERC20Withdraw(r.EVMAddress(), amount, gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) +} diff --git a/e2e/e2etests/test_v2_erc20_withdraw_and_call.go b/e2e/e2etests/test_v2_erc20_withdraw_and_call.go new file mode 100644 index 0000000000..609d74a8b9 --- /dev/null +++ b/e2e/e2etests/test_v2_erc20_withdraw_and_call.go @@ -0,0 +1,41 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageWithdrawERC20 = "this is a test ERC20 withdraw and call payload" + +func TestV2ERC20WithdrawAndCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ERC20WithdrawAndCall") + + r.AssertTestDAppEVMCalled(false, payloadMessageWithdrawERC20, amount) + + r.ApproveERC20ZRC20(r.GatewayZEVMAddr) + r.ApproveETHZRC20(r.GatewayZEVMAddr) + + // perform the withdraw + tx := r.V2ERC20WithdrawAndCall( + r.TestDAppV2EVMAddr, + amount, + r.EncodeERC20Call(r.ERC20Addr, amount, payloadMessageWithdrawERC20), + gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, + ) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + r.AssertTestDAppEVMCalled(true, payloadMessageWithdrawERC20, amount) +} diff --git a/e2e/e2etests/test_v2_erc20_withdraw_and_call_revert.go b/e2e/e2etests/test_v2_erc20_withdraw_and_call_revert.go new file mode 100644 index 0000000000..d5fe206aab --- /dev/null +++ b/e2e/e2etests/test_v2_erc20_withdraw_and_call_revert.go @@ -0,0 +1,51 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/testutil/sample" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestV2ERC20WithdrawAndCallRevert(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ERC20WithdrawAndCallRevert") + + r.ApproveERC20ZRC20(r.GatewayZEVMAddr) + r.ApproveETHZRC20(r.GatewayZEVMAddr) + + // use a random address to get the revert amount + revertAddress := sample.EthAddress() + balance, err := r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, revertAddress) + require.NoError(r, err) + require.EqualValues(r, int64(0), balance.Int64()) + + // perform the withdraw + tx := r.V2ERC20WithdrawAndCall( + r.TestDAppV2EVMAddr, + amount, + r.EncodeERC20CallRevert(r.ERC20Addr, amount), + gatewayzevm.RevertOptions{ + RevertAddress: revertAddress, + OnRevertGasLimit: big.NewInt(0), + }, + ) + + // wait for the cctx to be reverted + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) + + // check the balance is more than 0 + balance, err = r.ERC20ZRC20.BalanceOf(&bind.CallOpts{}, revertAddress) + require.NoError(r, err) + require.True(r, balance.Cmp(big.NewInt(0)) > 0) +} diff --git a/e2e/e2etests/test_v2_erc20_withdraw_and_call_revert_with_call.go b/e2e/e2etests/test_v2_erc20_withdraw_and_call_revert_with_call.go new file mode 100644 index 0000000000..3d34bea2f7 --- /dev/null +++ b/e2e/e2etests/test_v2_erc20_withdraw_and_call_revert_with_call.go @@ -0,0 +1,46 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageWithdrawOnRevertERC20 = "this is a test ERC20 withdraw and call on revert" + +func TestV2ERC20WithdrawAndCallRevertWithCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ERC20WithdrawAndCallRevertWithCall") + + r.AssertTestDAppZEVMCalled(false, payloadMessageWithdrawOnRevertERC20, amount) + + r.ApproveERC20ZRC20(r.GatewayZEVMAddr) + r.ApproveETHZRC20(r.GatewayZEVMAddr) + + // perform the withdraw + tx := r.V2ERC20WithdrawAndCall( + r.TestDAppV2EVMAddr, + amount, + r.EncodeERC20CallRevert(r.ERC20Addr, amount), + gatewayzevm.RevertOptions{ + RevertAddress: r.TestDAppV2ZEVMAddr, + CallOnRevert: true, + RevertMessage: []byte(payloadMessageWithdrawOnRevertERC20), + OnRevertGasLimit: big.NewInt(0), + }, + ) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) + + r.AssertTestDAppZEVMCalled(true, payloadMessageWithdrawOnRevertERC20, big.NewInt(0)) +} diff --git a/e2e/e2etests/test_v2_eth_deposit.go b/e2e/e2etests/test_v2_eth_deposit.go new file mode 100644 index 0000000000..d99db8dcc3 --- /dev/null +++ b/e2e/e2etests/test_v2_eth_deposit.go @@ -0,0 +1,29 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestV2ETHDeposit(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ETHDeposit") + + r.Logger.Info("starting v2 eth deposit test") + + // perform the deposit + tx := r.V2ETHDeposit(r.EVMAddress(), amount, gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "deposit") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) +} diff --git a/e2e/e2etests/test_v2_eth_deposit_and_call.go b/e2e/e2etests/test_v2_eth_deposit_and_call.go new file mode 100644 index 0000000000..657d3068f7 --- /dev/null +++ b/e2e/e2etests/test_v2_eth_deposit_and_call.go @@ -0,0 +1,48 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageDepositETH = "this is a test ETH deposit and call payload" + +func TestV2ETHDepositAndCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ETHDepositAndCall") + + r.AssertTestDAppZEVMCalled(false, payloadMessageDepositETH, amount) + + oldBalance, err := r.ETHZRC20.BalanceOf(&bind.CallOpts{}, r.TestDAppV2ZEVMAddr) + require.NoError(r, err) + + // perform the deposit and call to the TestDAppV2ZEVMAddr + tx := r.V2ETHDepositAndCall( + r.TestDAppV2ZEVMAddr, + amount, + []byte(payloadMessageDepositETH), + gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, + ) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "deposit_and_call") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + // check the payload was received on the contract + r.AssertTestDAppZEVMCalled(true, payloadMessageDepositETH, amount) + + // check the balance was updated + newBalance, err := r.ETHZRC20.BalanceOf(&bind.CallOpts{}, r.TestDAppV2ZEVMAddr) + require.NoError(r, err) + require.Equal(r, new(big.Int).Add(oldBalance, amount), newBalance) +} diff --git a/e2e/e2etests/test_v2_eth_deposit_and_call_revert.go b/e2e/e2etests/test_v2_eth_deposit_and_call_revert.go new file mode 100644 index 0000000000..f8093f629a --- /dev/null +++ b/e2e/e2etests/test_v2_eth_deposit_and_call_revert.go @@ -0,0 +1,44 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/testutil/sample" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestV2ETHDepositAndCallRevert(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ETHDepositAndCallRevert") + + r.ApproveERC20OnEVM(r.GatewayEVMAddr) + + // use a random address to get the revert amount + revertAddress := sample.EthAddress() + balance, err := r.EVMClient.BalanceAt(r.Ctx, revertAddress, nil) + require.NoError(r, err) + require.EqualValues(r, int64(0), balance.Int64()) + + // perform the deposit + tx := r.V2ETHDepositAndCall(r.TestDAppV2ZEVMAddr, amount, []byte("revert"), gatewayevm.RevertOptions{ + RevertAddress: revertAddress, + OnRevertGasLimit: big.NewInt(0), + }) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "deposit") + require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) + + // check the balance is more than 0 + balance, err = r.EVMClient.BalanceAt(r.Ctx, revertAddress, nil) + require.NoError(r, err) + require.True(r, balance.Cmp(big.NewInt(0)) > 0) +} diff --git a/e2e/e2etests/test_v2_eth_deposit_and_call_revert_with_call.go b/e2e/e2etests/test_v2_eth_deposit_and_call_revert_with_call.go new file mode 100644 index 0000000000..ad23989e38 --- /dev/null +++ b/e2e/e2etests/test_v2_eth_deposit_and_call_revert_with_call.go @@ -0,0 +1,41 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageDepositOnRevertETH = "this is a test ETH deposit and call on revert" + +func TestV2ETHDepositAndCallRevertWithCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ETHDepositAndCallRevertWithCall") + + r.ApproveERC20OnEVM(r.GatewayEVMAddr) + + r.AssertTestDAppEVMCalled(false, payloadMessageDepositOnRevertETH, amount) + + // perform the deposit + tx := r.V2ETHDepositAndCall(r.TestDAppV2ZEVMAddr, amount, []byte("revert"), gatewayevm.RevertOptions{ + RevertAddress: r.TestDAppV2EVMAddr, + CallOnRevert: true, + RevertMessage: []byte(payloadMessageDepositOnRevertETH), + OnRevertGasLimit: big.NewInt(200000), + }) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "deposit") + require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) + + // check the payload was received on the contract + r.AssertTestDAppEVMCalled(true, payloadMessageDepositOnRevertETH, big.NewInt(0)) +} diff --git a/e2e/e2etests/test_v2_eth_withdraw.go b/e2e/e2etests/test_v2_eth_withdraw.go new file mode 100644 index 0000000000..5445040b08 --- /dev/null +++ b/e2e/e2etests/test_v2_eth_withdraw.go @@ -0,0 +1,37 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestV2ETHWithdraw(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ETHWithdraw") + + oldBalance, err := r.EVMClient.BalanceAt(r.Ctx, r.EVMAddress(), nil) + require.NoError(r, err) + + r.ApproveETHZRC20(r.GatewayZEVMAddr) + + // perform the withdraw + tx := r.V2ETHWithdraw(r.EVMAddress(), amount, gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + // check the balance was updated, we just check newBalance is greater than oldBalance because of the gas fee + newBalance, err := r.EVMClient.BalanceAt(r.Ctx, r.EVMAddress(), nil) + require.NoError(r, err) + require.Greater(r, newBalance.Uint64(), oldBalance.Uint64()) +} diff --git a/e2e/e2etests/test_v2_eth_withdraw_and_call.go b/e2e/e2etests/test_v2_eth_withdraw_and_call.go new file mode 100644 index 0000000000..5a54092288 --- /dev/null +++ b/e2e/e2etests/test_v2_eth_withdraw_and_call.go @@ -0,0 +1,40 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageWithdrawETH = "this is a test ETH withdraw and call payload" + +func TestV2ETHWithdrawAndCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ETHWithdrawAndCall") + + r.AssertTestDAppEVMCalled(false, payloadMessageWithdrawETH, amount) + + r.ApproveETHZRC20(r.GatewayZEVMAddr) + + // perform the withdraw + tx := r.V2ETHWithdrawAndCall( + r.TestDAppV2EVMAddr, + amount, + r.EncodeGasCall(payloadMessageWithdrawETH), + gatewayzevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, + ) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + r.AssertTestDAppEVMCalled(true, payloadMessageWithdrawETH, amount) +} diff --git a/e2e/e2etests/test_v2_eth_withdraw_and_call_revert.go b/e2e/e2etests/test_v2_eth_withdraw_and_call_revert.go new file mode 100644 index 0000000000..7f4ce48224 --- /dev/null +++ b/e2e/e2etests/test_v2_eth_withdraw_and_call_revert.go @@ -0,0 +1,45 @@ +package e2etests + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/testutil/sample" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestV2ETHWithdrawAndCallRevert(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ETHWithdrawAndCall") + + r.ApproveETHZRC20(r.GatewayZEVMAddr) + + // use a random address to get the revert amount + revertAddress := sample.EthAddress() + balance, err := r.ETHZRC20.BalanceOf(&bind.CallOpts{}, revertAddress) + require.NoError(r, err) + require.EqualValues(r, int64(0), balance.Int64()) + + // perform the withdraw + tx := r.V2ETHWithdrawAndCall(r.TestDAppV2EVMAddr, amount, r.EncodeGasCall("revert"), gatewayzevm.RevertOptions{ + RevertAddress: revertAddress, + OnRevertGasLimit: big.NewInt(0), + }) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) + + // check the balance is more than 0 + balance, err = r.ETHZRC20.BalanceOf(&bind.CallOpts{}, revertAddress) + require.NoError(r, err) + require.True(r, balance.Cmp(big.NewInt(0)) > 0) +} diff --git a/e2e/e2etests/test_v2_eth_withdraw_and_call_revert_with_call.go b/e2e/e2etests/test_v2_eth_withdraw_and_call_revert_with_call.go new file mode 100644 index 0000000000..638e50a39c --- /dev/null +++ b/e2e/e2etests/test_v2_eth_withdraw_and_call_revert_with_call.go @@ -0,0 +1,45 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageWithdrawOnRevertETH = "this is a test ETH withdraw and call on revert" + +func TestV2ETHWithdrawAndCallRevertWithCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 1) + + amount, ok := big.NewInt(0).SetString(args[0], 10) + require.True(r, ok, "Invalid amount specified for TestV2ETHWithdrawAndCallRevertWithCall") + + r.AssertTestDAppZEVMCalled(false, payloadMessageWithdrawOnRevertETH, amount) + + r.ApproveETHZRC20(r.GatewayZEVMAddr) + + // perform the withdraw + tx := r.V2ETHWithdrawAndCall( + r.TestDAppV2EVMAddr, + amount, + r.EncodeGasCall("revert"), + gatewayzevm.RevertOptions{ + RevertAddress: r.TestDAppV2ZEVMAddr, + CallOnRevert: true, + RevertMessage: []byte(payloadMessageWithdrawOnRevertETH), + OnRevertGasLimit: big.NewInt(0), + }, + ) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "withdraw") + require.Equal(r, crosschaintypes.CctxStatus_Reverted, cctx.CctxStatus.Status) + + r.AssertTestDAppZEVMCalled(true, payloadMessageWithdrawOnRevertETH, big.NewInt(0)) +} diff --git a/e2e/e2etests/test_v2_evm_to_zevm_call.go b/e2e/e2etests/test_v2_evm_to_zevm_call.go new file mode 100644 index 0000000000..44fce408d1 --- /dev/null +++ b/e2e/e2etests/test_v2_evm_to_zevm_call.go @@ -0,0 +1,35 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageZEVMCall = "this is a test ZEVM call payload" + +func TestV2EVMToZEVMCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0) + + r.AssertTestDAppZEVMCalled(false, payloadMessageZEVMCall, big.NewInt(0)) + + // perform the withdraw + tx := r.V2EVMToZEMVCall( + r.TestDAppV2ZEVMAddr, + []byte(payloadMessageZEVMCall), + gatewayevm.RevertOptions{OnRevertGasLimit: big.NewInt(0)}, + ) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "call") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + // check the payload was received on the contract + r.AssertTestDAppZEVMCalled(true, payloadMessageZEVMCall, big.NewInt(0)) +} diff --git a/e2e/e2etests/test_v2_zevm_to_evm_call.go b/e2e/e2etests/test_v2_zevm_to_evm_call.go new file mode 100644 index 0000000000..9132e72837 --- /dev/null +++ b/e2e/e2etests/test_v2_zevm_to_evm_call.go @@ -0,0 +1,36 @@ +package e2etests + +import ( + "math/big" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/runner" + "github.com/zeta-chain/zetacore/e2e/utils" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +const payloadMessageEVMCall = "this is a test EVM call payload" + +func TestV2ZEVMToEVMCall(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0) + + r.AssertTestDAppEVMCalled(false, payloadMessageEVMCall, big.NewInt(0)) + + // Necessary approval for fee payment + r.ApproveETHZRC20(r.GatewayZEVMAddr) + + // perform the withdraw + tx := r.V2ZEVMToEMVCall(r.TestDAppV2EVMAddr, r.EncodeSimpleCall(payloadMessageEVMCall), gatewayzevm.RevertOptions{ + OnRevertGasLimit: big.NewInt(0), + }) + + // wait for the cctx to be mined + cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, tx.Hash().Hex(), r.CctxClient, r.Logger, r.CctxTimeout) + r.Logger.CCTX(*cctx, "call") + require.Equal(r, crosschaintypes.CctxStatus_OutboundMined, cctx.CctxStatus.Status) + + // check the payload was received on the contract + r.AssertTestDAppEVMCalled(true, payloadMessageEVMCall, big.NewInt(0)) +} diff --git a/e2e/e2etests/test_whitelist_erc20.go b/e2e/e2etests/test_whitelist_erc20.go index 5a09decd71..0fa043fe03 100644 --- a/e2e/e2etests/test_whitelist_erc20.go +++ b/e2e/e2etests/test_whitelist_erc20.go @@ -7,7 +7,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/e2e/contracts/erc20" "github.com/zeta-chain/zetacore/e2e/runner" diff --git a/e2e/e2etests/test_zeta_deposit.go b/e2e/e2etests/test_zeta_deposit.go index eb8021876d..00b672ee61 100644 --- a/e2e/e2etests/test_zeta_deposit.go +++ b/e2e/e2etests/test_zeta_deposit.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -12,8 +10,8 @@ import ( func TestZetaDeposit(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestZetaDeposit.") + // parse deposit amount + amount := parseBigInt(r, args[0]) hash := r.DepositZetaWithAmount(r.EVMAddress(), amount) diff --git a/e2e/e2etests/test_zeta_deposit_new_address.go b/e2e/e2etests/test_zeta_deposit_new_address.go index 047fbd7042..79a1360db9 100644 --- a/e2e/e2etests/test_zeta_deposit_new_address.go +++ b/e2e/e2etests/test_zeta_deposit_new_address.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -13,8 +11,8 @@ import ( func TestZetaDepositNewAddress(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok) + // parse deposit amount + amount := parseBigInt(r, args[0]) newAddress := sample.EthAddress() hash := r.DepositZetaWithAmount(newAddress, amount) diff --git a/e2e/e2etests/test_zeta_deposit_restricted_address.go b/e2e/e2etests/test_zeta_deposit_restricted_address.go index d525a97d79..d760ccafbd 100644 --- a/e2e/e2etests/test_zeta_deposit_restricted_address.go +++ b/e2e/e2etests/test_zeta_deposit_restricted_address.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - ethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -13,8 +11,8 @@ import ( func TestZetaDepositRestricted(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok, "Invalid amount specified for TestZetaDepositRestricted.") + // parse the deposit amount + amount := parseBigInt(r, args[0]) // Deposit amount to restricted address r.DepositZetaWithAmount(ethcommon.HexToAddress(testutils.RestrictedEVMAddressTest), amount) diff --git a/e2e/e2etests/test_zeta_withdraw.go b/e2e/e2etests/test_zeta_withdraw.go index 25b9d3d34a..7695c28bab 100644 --- a/e2e/e2etests/test_zeta_withdraw.go +++ b/e2e/e2etests/test_zeta_withdraw.go @@ -1,8 +1,6 @@ package e2etests import ( - "math/big" - "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/e2e/runner" @@ -13,8 +11,8 @@ import ( func TestZetaWithdraw(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok) + // parse withdraw amount + amount := parseBigInt(r, args[0]) r.DepositAndApproveWZeta(amount) tx := r.WithdrawZeta(amount, true) diff --git a/e2e/e2etests/test_zeta_withdraw_bitcoin_revert.go b/e2e/e2etests/test_zeta_withdraw_bitcoin_revert.go index 3685dccfb3..ac8e1f2448 100644 --- a/e2e/e2etests/test_zeta_withdraw_bitcoin_revert.go +++ b/e2e/e2etests/test_zeta_withdraw_bitcoin_revert.go @@ -4,7 +4,7 @@ import ( "math/big" "github.com/stretchr/testify/require" - connectorzevm "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zetaconnectorzevm.sol" + connectorzevm "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/zetaconnectorzevm.sol" "github.com/zeta-chain/zetacore/e2e/runner" "github.com/zeta-chain/zetacore/e2e/utils" @@ -14,8 +14,8 @@ import ( func TestZetaWithdrawBTCRevert(r *runner.E2ERunner, args []string) { require.Len(r, args, 1) - amount, ok := big.NewInt(0).SetString(args[0], 10) - require.True(r, ok) + // parse withdraw amount + amount := parseBigInt(r, args[0]) r.ZEVMAuth.Value = amount tx, err := r.WZeta.Deposit(r.ZEVMAuth) diff --git a/e2e/runner/accounting.go b/e2e/runner/accounting.go index 6b70e5f19c..7c9a0c5746 100644 --- a/e2e/runner/accounting.go +++ b/e2e/runner/accounting.go @@ -166,18 +166,24 @@ func (r *E2ERunner) CheckSolanaTSSBalance() error { } func (r *E2ERunner) checkERC20TSSBalance() error { - erc20Balance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, r.ERC20CustodyAddr) + custodyBalance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, r.ERC20CustodyAddr) if err != nil { return err } + custodyV2Balance, err := r.ERC20.BalanceOf(&bind.CallOpts{}, r.ERC20CustodyV2Addr) + if err != nil { + return err + } + custodyFullBalance := big.NewInt(0).Add(custodyBalance, custodyV2Balance) + erc20zrc20Supply, err := r.ERC20ZRC20.TotalSupply(&bind.CallOpts{}) if err != nil { return err } - if erc20Balance.Cmp(erc20zrc20Supply) < 0 { - return fmt.Errorf("ERC20: TSS balance (%d) < ZRC20 TotalSupply (%d) ", erc20Balance, erc20zrc20Supply) + if custodyFullBalance.Cmp(erc20zrc20Supply) < 0 { + return fmt.Errorf("ERC20: TSS balance (%d) < ZRC20 TotalSupply (%d) ", custodyFullBalance, erc20zrc20Supply) } - r.Logger.Info("ERC20: TSS balance (%d) >= ERC20 ZRC20 TotalSupply (%d)", erc20Balance, erc20zrc20Supply) + r.Logger.Info("ERC20: TSS balance (%d) >= ERC20 ZRC20 TotalSupply (%d)", custodyFullBalance, erc20zrc20Supply) return nil } @@ -202,7 +208,7 @@ func (r *E2ERunner) checkZetaTSSBalance() error { } zetaSupply, _ := big.NewInt(0).SetString(result.Amount.Amount, 10) if zetaLocked.Cmp(zetaSupply) < 0 { - r.Logger.Info(fmt.Sprintf("ZETA: TSS balance (%d) < ZRC20 TotalSupply (%d)", zetaLocked, zetaSupply)) + r.Logger.Info("ZETA: TSS balance (%d) < ZRC20 TotalSupply (%d)", zetaLocked, zetaSupply) } else { r.Logger.Info("ZETA: TSS balance (%d) >= ZRC20 TotalSupply (%d)", zetaLocked, zetaSupply) } diff --git a/e2e/runner/evm.go b/e2e/runner/evm.go index 10fd599d63..3b5e7f8d90 100644 --- a/e2e/runner/evm.go +++ b/e2e/runner/evm.go @@ -5,6 +5,7 @@ import ( "math/big" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" @@ -164,6 +165,54 @@ func (r *E2ERunner) SendEther(_ ethcommon.Address, value *big.Int, data []byte) return signedTx, nil } +// ApproveERC20OnEVM approves ERC20 on EVM to a specific address +// check if allowance is zero before calling this method +// allow a high amount to avoid multiple approvals +func (r *E2ERunner) ApproveERC20OnEVM(allowed ethcommon.Address) { + allowance, err := r.ERC20.Allowance(&bind.CallOpts{}, r.Account.EVMAddress(), r.GatewayEVMAddr) + require.NoError(r, err) + + // approve 1M*1e18 if allowance is zero + if allowance.Cmp(big.NewInt(0)) == 0 { + tx, err := r.ERC20.Approve(r.EVMAuth, allowed, big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1000000))) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.EVMClient, tx, r.Logger, r.ReceiptTimeout) + require.True(r, receipt.Status == 1, "approval failed") + } +} + +// ApproveETHZRC20 approves ETH ZRC20 on EVM to a specific address +// check if allowance is zero before calling this method +// allow a high amount to avoid multiple approvals +func (r *E2ERunner) ApproveETHZRC20(allowed ethcommon.Address) { + allowance, err := r.ETHZRC20.Allowance(&bind.CallOpts{}, r.Account.EVMAddress(), r.GatewayEVMAddr) + require.NoError(r, err) + + // approve 1M*1e18 if allowance is zero + if allowance.Cmp(big.NewInt(0)) == 0 { + tx, err := r.ETHZRC20.Approve(r.ZEVMAuth, allowed, big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1000000))) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.True(r, receipt.Status == 1, "approval failed") + } +} + +// ApproveERC20ZRC20 approves ERC20 ZRC20 on EVM to a specific address +// check if allowance is zero before calling this method +// allow a high amount to avoid multiple approvals +func (r *E2ERunner) ApproveERC20ZRC20(allowed ethcommon.Address) { + allowance, err := r.ERC20ZRC20.Allowance(&bind.CallOpts{}, r.Account.EVMAddress(), r.GatewayEVMAddr) + require.NoError(r, err) + + // approve 1M*1e18 if allowance is zero + if allowance.Cmp(big.NewInt(0)) == 0 { + tx, err := r.ERC20ZRC20.Approve(r.ZEVMAuth, allowed, big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1000000))) + require.NoError(r, err) + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.True(r, receipt.Status == 1, "approval failed") + } +} + // AnvilMineBlocks mines blocks on Anvil localnet // the block time is provided in seconds // the method returns a function to stop the mining diff --git a/e2e/runner/liquidity.go b/e2e/runner/liquidity.go new file mode 100644 index 0000000000..05f83e6c44 --- /dev/null +++ b/e2e/runner/liquidity.go @@ -0,0 +1,82 @@ +package runner + +import ( + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/e2e/utils" +) + +// AddLiquidityETH adds liquidity token to the uniswap pool ZETA/ETH +// we use the provided amount of ZETA and ETH to add liquidity as wanted amount +// 0 is used for the minimum amount of ZETA and ETH +func (r *E2ERunner) AddLiquidityETH(amountZETA, amountETH *big.Int) { + // approve uni router + r.ApproveETHZRC20(r.UniswapV2RouterAddr) + + previousValue := r.ZEVMAuth.Value + r.ZEVMAuth.Value = amountZETA + defer func() { + r.ZEVMAuth.Value = previousValue + }() + + r.Logger.Info("Adding liquidity to ZETA/ETH pool") + tx, err := r.UniswapV2Router.AddLiquidityETH( + r.ZEVMAuth, + r.ETHZRC20Addr, + amountETH, + big.NewInt(0), + big.NewInt(0), + r.EVMAddress(), + big.NewInt(time.Now().Add(10*time.Minute).Unix()), + ) + require.NoError(r, err) + + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.EqualValues(r, types.ReceiptStatusSuccessful, receipt.Status, "add liquidity failed") + + // get the pair address + pairAddress, err := r.UniswapV2Factory.GetPair(&bind.CallOpts{}, r.WZetaAddr, r.ETHZRC20Addr) + require.NoError(r, err) + + r.Logger.Info("ZETA/ETH pair address: %s", pairAddress.Hex()) +} + +// AddLiquidityERC20 adds liquidity token to the uniswap pool ZETA/ERC20 +// we use the provided amount of ZETA and ERC20 to add liquidity as wanted amount +// 0 is used for the minimum amount of ZETA and ERC20 +func (r *E2ERunner) AddLiquidityERC20(amountZETA, amountERC20 *big.Int) { + // approve uni router + r.ApproveERC20ZRC20(r.UniswapV2RouterAddr) + + previousValue := r.ZEVMAuth.Value + r.ZEVMAuth.Value = amountZETA + defer func() { + r.ZEVMAuth.Value = previousValue + }() + + r.Logger.Info("Adding liquidity to ZETA/ERC20 pool") + tx, err := r.UniswapV2Router.AddLiquidityETH( + r.ZEVMAuth, + r.ERC20ZRC20Addr, + amountERC20, + big.NewInt(0), + big.NewInt(0), + r.EVMAddress(), + big.NewInt(time.Now().Add(10*time.Minute).Unix()), + ) + require.NoError(r, err) + + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + require.EqualValues(r, types.ReceiptStatusSuccessful, receipt.Status, "add liquidity failed") + + // get the pair address + pairAddress, err := r.UniswapV2Factory.GetPair(&bind.CallOpts{}, r.WZetaAddr, r.ERC20ZRC20Addr) + require.NoError(r, err) + + r.Logger.Info("ZETA/ERC20 pair address: %s", pairAddress.Hex()) +} diff --git a/e2e/runner/logger.go b/e2e/runner/logger.go index 49ab8af29c..0b970ab6f3 100644 --- a/e2e/runner/logger.go +++ b/e2e/runner/logger.go @@ -1,12 +1,14 @@ package runner import ( + "encoding/hex" "fmt" "sync" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/fatih/color" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" ) @@ -55,7 +57,7 @@ func (l *Logger) Print(message string, args ...interface{}) { text := fmt.Sprintf(message, args...) // #nosec G104 - we are not using user input - l.logger.Printf(l.getPrefixWithPadding() + loggerSeparator + text + "\n") + l.logger.Print(l.getPrefixWithPadding() + loggerSeparator + text + "\n") } // PrintNoPrefix prints a message to the logger without the prefix @@ -65,7 +67,7 @@ func (l *Logger) PrintNoPrefix(message string, args ...interface{}) { text := fmt.Sprintf(message, args...) // #nosec G104 - we are not using user input - l.logger.Printf(text + "\n") + _, _ = l.logger.Print(text + "\n") } // Info prints a message to the logger if verbose is true @@ -76,7 +78,7 @@ func (l *Logger) Info(message string, args ...interface{}) { if l.verbose { text := fmt.Sprintf(message, args...) // #nosec G104 - we are not using user input - l.logger.Printf(l.getPrefixWithPadding() + loggerSeparator + "[INFO]" + text + "\n") + _, _ = l.logger.Print(l.getPrefixWithPadding() + loggerSeparator + "[INFO]" + text + "\n") } } @@ -88,11 +90,11 @@ func (l *Logger) InfoLoud(message string, args ...interface{}) { if l.verbose { text := fmt.Sprintf(message, args...) // #nosec G104 - we are not using user input - l.logger.Printf(l.getPrefixWithPadding() + loggerSeparator + "[INFO] =======================================") + l.logger.Print(l.getPrefixWithPadding() + loggerSeparator + "[INFO] =======================================") // #nosec G104 - we are not using user input - l.logger.Printf(l.getPrefixWithPadding() + loggerSeparator + "[INFO]" + text + "\n") + l.logger.Print(l.getPrefixWithPadding() + loggerSeparator + "[INFO]" + text + "\n") // #nosec G104 - we are not using user input - l.logger.Printf(l.getPrefixWithPadding() + loggerSeparator + "[INFO] =======================================") + l.logger.Print(l.getPrefixWithPadding() + loggerSeparator + "[INFO] =======================================") } } @@ -103,7 +105,7 @@ func (l *Logger) Error(message string, args ...interface{}) { text := fmt.Sprintf(message, args...) // #nosec G104 - we are not using user input - l.logger.Printf(l.getPrefixWithPadding() + loggerSeparator + "[ERROR]" + text + "\n") + l.logger.Print(l.getPrefixWithPadding() + loggerSeparator + "[ERROR]" + text + "\n") } // CCTX prints a CCTX @@ -194,11 +196,36 @@ func (l *Logger) ZRC20Withdrawal( event.From.Hex(), event.To, event.Value, - event.Gasfee, + event.GasFee, ) } } +type depositParser interface { + ParseDeposited(ethtypes.Log) (*gatewayevm.GatewayEVMDeposited, error) +} + +// GatewayDeposit prints a GatewayDeposit event +func (l *Logger) GatewayDeposit( + contract depositParser, + receipt ethtypes.Receipt, + name string, +) { + for _, log := range receipt.Logs { + event, err := contract.ParseDeposited(*log) + if err != nil { + continue + } + + l.Info(" Gateway Deposit: %s", name) + l.Info(" Sender: %s", event.Sender.Hex()) + l.Info(" Receiver: %s", event.Receiver.Hex()) + l.Info(" Amount: %s", event.Amount.String()) + l.Info(" Asset: %s", event.Asset.Hex()) + l.Info(" Payload: %s", hex.EncodeToString(event.Payload)) + } +} + func (l *Logger) getPrefixWithPadding() string { // add padding to prefix prefix := l.prefix diff --git a/e2e/runner/report.go b/e2e/runner/report.go index d80024e0dd..31cd085fc6 100644 --- a/e2e/runner/report.go +++ b/e2e/runner/report.go @@ -59,7 +59,7 @@ func (r *E2ERunner) PrintTestReports(tr TestReports) { if err != nil { r.Logger.Print("Error rendering test report: %s", err) } - r.Logger.PrintNoPrefix(table) + r.Logger.PrintNoPrefix(table, "") } // NetworkReport is a struct that contains the report for the network used after running e2e tests diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index 1dfe0d3fb8..77d068c398 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -17,15 +17,18 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - zetaeth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zeta.eth.sol" - zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" - connectorzevm "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zetaconnectorzevm.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" - "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" - uniswapv2router "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + zetaeth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zeta.eth.sol" + zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/wzeta.sol" + connectorzevm "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/zetaconnectorzevm.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" + uniswapv2router "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + erc20custodyv2 "github.com/zeta-chain/protocol-contracts/v2/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/e2e/config" "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" @@ -33,6 +36,7 @@ import ( "github.com/zeta-chain/zetacore/e2e/contracts/zevmswap" "github.com/zeta-chain/zetacore/e2e/txserver" "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/pkg/contracts/testdappv2" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" @@ -140,6 +144,20 @@ type E2ERunner struct { Logger *Logger BitcoinParams *chaincfg.Params mutex sync.Mutex + + // evm v2 + GatewayEVMAddr ethcommon.Address + GatewayEVM *gatewayevm.GatewayEVM + ERC20CustodyV2Addr ethcommon.Address + ERC20CustodyV2 *erc20custodyv2.ERC20Custody + TestDAppV2EVMAddr ethcommon.Address + TestDAppV2EVM *testdappv2.TestDAppV2 + + // zevm v2 + GatewayZEVMAddr ethcommon.Address + GatewayZEVM *gatewayzevm.GatewayZEVM + TestDAppV2ZEVMAddr ethcommon.Address + TestDAppV2ZEVM *testdappv2.TestDAppV2 } func NewE2ERunner( @@ -285,6 +303,35 @@ func (r *E2ERunner) CopyAddressesFrom(other *E2ERunner) (err error) { if err != nil { return err } + + // v2 contracts + r.GatewayEVMAddr = other.GatewayEVMAddr + r.GatewayEVM, err = gatewayevm.NewGatewayEVM(r.GatewayEVMAddr, r.EVMClient) + if err != nil { + return err + } + r.ERC20CustodyV2Addr = other.ERC20CustodyV2Addr + r.ERC20CustodyV2, err = erc20custodyv2.NewERC20Custody(r.ERC20CustodyV2Addr, r.EVMClient) + if err != nil { + return err + } + r.TestDAppV2EVMAddr = other.TestDAppV2EVMAddr + r.TestDAppV2EVM, err = testdappv2.NewTestDAppV2(r.TestDAppV2EVMAddr, r.EVMClient) + if err != nil { + return err + } + + r.GatewayZEVMAddr = other.GatewayZEVMAddr + r.GatewayZEVM, err = gatewayzevm.NewGatewayZEVM(r.GatewayZEVMAddr, r.ZEVMClient) + if err != nil { + return err + } + r.TestDAppV2ZEVMAddr = other.TestDAppV2ZEVMAddr + r.TestDAppV2ZEVM, err = testdappv2.NewTestDAppV2(r.TestDAppV2ZEVMAddr, r.ZEVMClient) + if err != nil { + return err + } + return nil } @@ -327,6 +374,17 @@ func (r *E2ERunner) PrintContractAddresses() { r.Logger.Print("ERC20Custody: %s", r.ERC20CustodyAddr.Hex()) r.Logger.Print("ERC20: %s", r.ERC20Addr.Hex()) r.Logger.Print("TestDappEVM: %s", r.EvmTestDAppAddr.Hex()) + + // v2 contracts + + r.Logger.Print(" --- 📜zEVM v2 contracts ---") + r.Logger.Print("GatewayZEVM: %s", r.GatewayZEVMAddr.Hex()) + r.Logger.Print("TestDAppV2ZEVM: %s", r.TestDAppV2ZEVMAddr.Hex()) + + r.Logger.Print(" --- 📜EVM v2 contracts ---") + r.Logger.Print("GatewayEVM: %s", r.GatewayEVMAddr.Hex()) + r.Logger.Print("ERC20CustodyV2: %s", r.ERC20CustodyV2Addr.Hex()) + r.Logger.Print("TestDAppV2EVM: %s", r.TestDAppV2EVMAddr.Hex()) } // IsRunningUpgrade returns true if the test is running an upgrade test suite. diff --git a/e2e/runner/setup_evm.go b/e2e/runner/setup_evm.go index 74652ed163..ab8d3edd16 100644 --- a/e2e/runner/setup_evm.go +++ b/e2e/runner/setup_evm.go @@ -7,9 +7,9 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - zetaeth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zeta.eth.sol" - zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + zetaeth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zeta.eth.sol" + zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol" "github.com/zeta-chain/zetacore/e2e/config" "github.com/zeta-chain/zetacore/e2e/contracts/erc20" diff --git a/e2e/runner/setup_zeta.go b/e2e/runner/setup_zeta.go index eaee86bdbb..4f72f72108 100644 --- a/e2e/runner/setup_zeta.go +++ b/e2e/runner/setup_zeta.go @@ -8,12 +8,12 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" - connectorzevm "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zetaconnectorzevm.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" - "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" - uniswapv2router "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/wzeta.sol" + connectorzevm "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/zetaconnectorzevm.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" + uniswapv2router "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/e2e/contracts/contextapp" "github.com/zeta-chain/zetacore/e2e/contracts/testdapp" @@ -65,8 +65,8 @@ func (r *E2ERunner) SetTSSAddresses() error { return nil } -// SetZEVMContracts set contracts for the ZEVM -func (r *E2ERunner) SetZEVMContracts() { +// SetZEVMSystemContracts set system contracts for the ZEVM +func (r *E2ERunner) SetZEVMSystemContracts() { r.Logger.Print("⚙️ deploying system contracts and ZRC20s on ZEVM") startTime := time.Now() defer func() { @@ -74,18 +74,12 @@ func (r *E2ERunner) SetZEVMContracts() { }() // deploy system contracts and ZRC20 contracts on ZetaChain - addresses, err := r.ZetaTxServer.DeploySystemContractsAndZRC20( + addresses, err := r.ZetaTxServer.DeploySystemContracts( e2eutils.OperationalPolicyName, e2eutils.AdminPolicyName, - r.ERC20Addr.Hex(), ) require.NoError(r, err) - // Set ERC20ZRC20Addr - r.ERC20ZRC20Addr = ethcommon.HexToAddress(addresses.ERC20zrc20Addr) - r.ERC20ZRC20, err = zrc20.NewZRC20(r.ERC20ZRC20Addr, r.ZEVMClient) - require.NoError(r, err) - // UniswapV2FactoryAddr r.UniswapV2FactoryAddr = ethcommon.HexToAddress(addresses.UniswapV2FactoryAddr) r.UniswapV2Factory, err = uniswapv2factory.NewUniswapV2Factory(r.UniswapV2FactoryAddr, r.ZEVMClient) @@ -123,11 +117,6 @@ func (r *E2ERunner) SetZEVMContracts() { r.SystemContract = systemContract r.SystemContractAddr = systemContractAddr - // set ZRC20 contracts - r.SetupETHZRC20() - r.SetupBTCZRC20() - r.SetupSOLZRC20() - // deploy TestDApp contract on zEVM appAddr, txApp, _, err := testdapp.DeployTestDApp( r.ZEVMAuth, @@ -177,6 +166,33 @@ func (r *E2ERunner) SetZEVMContracts() { r.ContextApp = contextApp } +// SetZEVMZRC20s set ZRC20 for the ZEVM +func (r *E2ERunner) SetZEVMZRC20s() { + r.Logger.Print("⚙️ deploying system contracts and ZRC20s on ZEVM") + startTime := time.Now() + defer func() { + r.Logger.Info("System contract deployments took %s\n", time.Since(startTime)) + }() + + // deploy system contracts and ZRC20 contracts on ZetaChain + erc20zrc20Addr, err := r.ZetaTxServer.DeployZRC20s( + e2eutils.OperationalPolicyName, + e2eutils.AdminPolicyName, + r.ERC20Addr.Hex(), + ) + require.NoError(r, err) + + // Set ERC20ZRC20Addr + r.ERC20ZRC20Addr = ethcommon.HexToAddress(erc20zrc20Addr) + r.ERC20ZRC20, err = zrc20.NewZRC20(r.ERC20ZRC20Addr, r.ZEVMClient) + require.NoError(r, err) + + // set ZRC20 contracts + r.SetupETHZRC20() + r.SetupBTCZRC20() + r.SetupSOLZRC20() +} + // SetupETHZRC20 sets up the ETH ZRC20 in the runner from the values queried from the chain func (r *E2ERunner) SetupETHZRC20() { ethZRC20Addr, err := r.SystemContract.GasCoinZRC20ByChainId( diff --git a/e2e/runner/solana.go b/e2e/runner/solana.go index 71d2cfb629..ba6f240279 100644 --- a/e2e/runner/solana.go +++ b/e2e/runner/solana.go @@ -31,6 +31,7 @@ func (r *E2ERunner) ComputePdaAddress() solana.PublicKey { func (r *E2ERunner) CreateDepositInstruction( signer solana.PublicKey, receiver ethcommon.Address, + data []byte, amount uint64, ) solana.Instruction { // compute the gateway PDA address @@ -51,7 +52,7 @@ func (r *E2ERunner) CreateDepositInstruction( inst.DataBytes, err = borsh.Serialize(solanacontract.DepositInstructionParams{ Discriminator: solanacontract.DiscriminatorDeposit(), Amount: amount, - Memo: receiver.Bytes(), + Memo: append(receiver.Bytes(), data...), }) require.NoError(r, err) @@ -96,9 +97,16 @@ func (r *E2ERunner) BroadcastTxSync(tx *solana.Transaction) (solana.Signature, * require.NoError(r, err) r.Logger.Info("broadcast success! tx sig %s; waiting for confirmation...", sig) + var ( + start = time.Now() + timeout = 2 * time.Minute // Solana tx expires automatically after 2 minutes + ) + // wait for the transaction to be finalized var out *rpc.GetTransactionResult for { + require.False(r, time.Since(start) > timeout, "waiting solana tx timeout") + time.Sleep(1 * time.Second) out, err = r.SolanaClient.GetTransaction(r.Ctx, sig, &rpc.GetTransactionOpts{}) if err == nil { @@ -109,6 +117,33 @@ func (r *E2ERunner) BroadcastTxSync(tx *solana.Transaction) (solana.Signature, * return sig, out } +// SOLDepositAndCall deposits an amount of ZRC20 SOL tokens (in lamports) and calls a contract (if data is provided) +func (r *E2ERunner) SOLDepositAndCall( + signerPrivKey *solana.PrivateKey, + receiver ethcommon.Address, + amount *big.Int, + data []byte, +) solana.Signature { + // if signer is not provided, use the runner account as default + if signerPrivKey == nil { + privkey, err := solana.PrivateKeyFromBase58(r.Account.SolanaPrivateKey.String()) + require.NoError(r, err) + signerPrivKey = &privkey + } + + // create 'deposit' instruction + instruction := r.CreateDepositInstruction(signerPrivKey.PublicKey(), receiver, data, amount.Uint64()) + + // create and sign the transaction + signedTx := r.CreateSignedTransaction([]solana.Instruction{instruction}, *signerPrivKey) + + // broadcast the transaction and wait for finalization + sig, out := r.BroadcastTxSync(signedTx) + r.Logger.Info("deposit logs: %v", out.Meta.LogMessages) + + return sig +} + // WithdrawSOLZRC20 withdraws an amount of ZRC20 SOL tokens func (r *E2ERunner) WithdrawSOLZRC20(to solana.PublicKey, amount *big.Int, approveAmount *big.Int) { // approve diff --git a/e2e/runner/testdapp.go b/e2e/runner/testdapp.go new file mode 100644 index 0000000000..5515067aae --- /dev/null +++ b/e2e/runner/testdapp.go @@ -0,0 +1,103 @@ +package runner + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/pkg/contracts/testdappv2" +) + +// AssertTestDAppZEVMCalled is a function that asserts the values of the test dapp on the ZEVM +// this function uses TestDAppV2 for the assertions, in the future we should only use this contracts for all tests +// https://github.com/zeta-chain/node/issues/2655 +func (r *E2ERunner) AssertTestDAppZEVMCalled(expectedCalled bool, message string, amount *big.Int) { + r.assertTestDAppCalled(r.TestDAppV2ZEVM, message, expectedCalled, amount) +} + +// AssertTestDAppEVMCalled is a function that asserts the values of the test dapp on the external EVM +func (r *E2ERunner) AssertTestDAppEVMCalled(expectedCalled bool, message string, amount *big.Int) { + r.assertTestDAppCalled(r.TestDAppV2EVM, message, expectedCalled, amount) +} + +func (r *E2ERunner) assertTestDAppCalled( + testDApp *testdappv2.TestDAppV2, + message string, + expectedCalled bool, + expectedAmount *big.Int, +) { + // check the payload was received on the contract + called, err := testDApp.GetCalledWithMessage(&bind.CallOpts{}, message) + require.NoError(r, err) + require.EqualValues(r, expectedCalled, called) + + if expectedCalled { + amount, err := testDApp.GetAmountWithMessage(&bind.CallOpts{}, message) + require.NoError(r, err) + require.EqualValues( + r, + expectedAmount.Uint64(), + amount.Uint64(), + "Amounts do not match, expected %s, actual %s", + expectedAmount.String(), + amount.String(), + ) + } +} + +// EncodeGasCall encodes the payload for the gasCall function +func (r *E2ERunner) EncodeGasCall(message string) []byte { + abi, err := testdappv2.TestDAppV2MetaData.GetAbi() + require.NoError(r, err) + + // encode the message + encoded, err := abi.Pack("gasCall", message) + require.NoError(r, err) + return encoded +} + +// EncodeGasCallRevert encodes the payload for the gasCall function that reverts +func (r *E2ERunner) EncodeGasCallRevert() []byte { + abi, err := testdappv2.TestDAppV2MetaData.GetAbi() + require.NoError(r, err) + + // encode the message + encoded, err := abi.Pack("gasCall", "revert") + require.NoError(r, err) + return encoded +} + +// EncodeERC20Call encodes the payload for the erc20Call function +func (r *E2ERunner) EncodeERC20Call(erc20Addr ethcommon.Address, amount *big.Int, message string) []byte { + abi, err := testdappv2.TestDAppV2MetaData.GetAbi() + require.NoError(r, err) + + // encode the message + encoded, err := abi.Pack("erc20Call", erc20Addr, amount, message) + require.NoError(r, err) + return encoded +} + +// EncodeERC20CallRevert encodes the payload for the erc20Call function that reverts +func (r *E2ERunner) EncodeERC20CallRevert(erc20Addr ethcommon.Address, amount *big.Int) []byte { + abi, err := testdappv2.TestDAppV2MetaData.GetAbi() + require.NoError(r, err) + + // encode the message + encoded, err := abi.Pack("erc20Call", erc20Addr, amount, "revert") + require.NoError(r, err) + return encoded +} + +// EncodeSimpleCall encodes the payload for the simpleCall function +func (r *E2ERunner) EncodeSimpleCall(message string) []byte { + abi, err := testdappv2.TestDAppV2MetaData.GetAbi() + require.NoError(r, err) + + // encode the message + encoded, err := abi.Pack("simpleCall", message) + require.NoError(r, err) + return encoded +} diff --git a/e2e/runner/v2_evm.go b/e2e/runner/v2_evm.go new file mode 100644 index 0000000000..09a8ed7c46 --- /dev/null +++ b/e2e/runner/v2_evm.go @@ -0,0 +1,117 @@ +package runner + +import ( + "math/big" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/utils" +) + +// V2ETHDeposit calls Deposit of Gateway with gas token on EVM +func (r *E2ERunner) V2ETHDeposit( + receiver ethcommon.Address, + amount *big.Int, + revertOptions gatewayevm.RevertOptions, +) *ethtypes.Transaction { + // set the value of the transaction + previousValue := r.EVMAuth.Value + defer func() { + r.EVMAuth.Value = previousValue + }() + r.EVMAuth.Value = amount + + tx, err := r.GatewayEVM.Deposit0(r.EVMAuth, receiver, revertOptions) + require.NoError(r, err) + + logDepositInfoAndWaitForTxReceipt(r, tx, "eth_deposit") + + return tx +} + +// V2ETHDepositAndCall calls DepositAndCall of Gateway with gas token on EVM +func (r *E2ERunner) V2ETHDepositAndCall( + receiver ethcommon.Address, + amount *big.Int, + payload []byte, + revertOptions gatewayevm.RevertOptions, +) *ethtypes.Transaction { + // set the value of the transaction + previousValue := r.EVMAuth.Value + defer func() { + r.EVMAuth.Value = previousValue + }() + r.EVMAuth.Value = amount + + tx, err := r.GatewayEVM.DepositAndCall(r.EVMAuth, receiver, payload, revertOptions) + require.NoError(r, err) + + logDepositInfoAndWaitForTxReceipt(r, tx, "eth_deposit_and_call") + + return tx +} + +// V2ERC20Deposit calls Deposit of Gateway with erc20 token on EVM +func (r *E2ERunner) V2ERC20Deposit( + receiver ethcommon.Address, + amount *big.Int, + revertOptions gatewayevm.RevertOptions, +) *ethtypes.Transaction { + tx, err := r.GatewayEVM.Deposit(r.EVMAuth, receiver, amount, r.ERC20Addr, revertOptions) + require.NoError(r, err) + + logDepositInfoAndWaitForTxReceipt(r, tx, "erc20_deposit") + + return tx +} + +// V2ERC20DepositAndCall calls DepositAndCall of Gateway with erc20 token on EVM +func (r *E2ERunner) V2ERC20DepositAndCall( + receiver ethcommon.Address, + amount *big.Int, + payload []byte, + revertOptions gatewayevm.RevertOptions, +) *ethtypes.Transaction { + tx, err := r.GatewayEVM.DepositAndCall0( + r.EVMAuth, + receiver, + amount, + r.ERC20Addr, + payload, + revertOptions, + ) + require.NoError(r, err) + + logDepositInfoAndWaitForTxReceipt(r, tx, "erc20_deposit_and_call") + + return tx +} + +// V2EVMToZEMVCall calls Call of Gateway on EVM +func (r *E2ERunner) V2EVMToZEMVCall( + receiver ethcommon.Address, + payload []byte, + revertOptions gatewayevm.RevertOptions, +) *ethtypes.Transaction { + tx, err := r.GatewayEVM.Call(r.EVMAuth, receiver, payload, revertOptions) + require.NoError(r, err) + + return tx +} + +func logDepositInfoAndWaitForTxReceipt( + r *E2ERunner, + tx *ethtypes.Transaction, + name string, +) { + r.Logger.EVMTransaction(*tx, name) + + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.EVMClient, tx, r.Logger, r.ReceiptTimeout) + r.requireTxSuccessful(receipt, name+" failed") + + r.Logger.EVMReceipt(*receipt, name) + r.Logger.GatewayDeposit(r.GatewayEVM, *receipt, name) +} diff --git a/e2e/runner/v2_setup_evm.go b/e2e/runner/v2_setup_evm.go new file mode 100644 index 0000000000..001da12e04 --- /dev/null +++ b/e2e/runner/v2_setup_evm.go @@ -0,0 +1,108 @@ +package runner + +import ( + "math/big" + "time" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + erc20custodyv2 "github.com/zeta-chain/protocol-contracts/v2/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/pkg/constant" + "github.com/zeta-chain/zetacore/pkg/contracts/erc1967proxy" + "github.com/zeta-chain/zetacore/pkg/contracts/testdappv2" +) + +// SetupEVMV2 setup contracts on EVM with v2 contracts +func (r *E2ERunner) SetupEVMV2() { + ensureTxReceipt := func(tx *ethtypes.Transaction, failMessage string) { + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.EVMClient, tx, r.Logger, r.ReceiptTimeout) + r.requireTxSuccessful(receipt, failMessage) + } + + r.Logger.Print("⚙️ setting up EVM v2 network") + startTime := time.Now() + defer func() { + r.Logger.Info("EVM v2 setup took %s\n", time.Since(startTime)) + }() + + r.Logger.InfoLoud("Deploy Gateway and ERC20Custody ERC20\n") + + // donate to the TSS address to avoid account errors because deploying gas token ZRC20 will automatically mint + // gas token on ZetaChain to initialize the pool + txDonation, err := r.SendEther(r.TSSAddress, big.NewInt(101000000000000000), []byte(constant.DonationMessage)) + require.NoError(r, err) + + r.Logger.Info("Deploying Gateway EVM") + gatewayEVMAddr, txGateway, _, err := gatewayevm.DeployGatewayEVM(r.EVMAuth, r.EVMClient) + require.NoError(r, err) + + ensureTxReceipt(txGateway, "Gateway deployment failed") + + gatewayEVMABI, err := gatewayevm.GatewayEVMMetaData.GetAbi() + require.NoError(r, err) + + // Encode the initializer data + initializerData, err := gatewayEVMABI.Pack("initialize", r.TSSAddress, r.ZetaEthAddr, r.Account.EVMAddress()) + require.NoError(r, err) + + // Deploy the proxy contract + proxyAddress, txProxy, _, err := erc1967proxy.DeployERC1967Proxy( + r.EVMAuth, + r.EVMClient, + gatewayEVMAddr, + initializerData, + ) + require.NoError(r, err) + + r.GatewayEVMAddr = proxyAddress + r.GatewayEVM, err = gatewayevm.NewGatewayEVM(proxyAddress, r.EVMClient) + require.NoError(r, err) + r.Logger.Info("Gateway EVM contract address: %s, tx hash: %s", gatewayEVMAddr.Hex(), txGateway.Hash().Hex()) + + r.Logger.Info("Deploying ERC20Custody contract") + erc20CustodyNewAddr, txCustody, erc20CustodyNew, err := erc20custodyv2.DeployERC20Custody( + r.EVMAuth, + r.EVMClient, + r.GatewayEVMAddr, + r.TSSAddress, + r.Account.EVMAddress(), + ) + require.NoError(r, err) + + r.ERC20CustodyV2Addr = erc20CustodyNewAddr + r.ERC20CustodyV2 = erc20CustodyNew + r.Logger.Info( + "ERC20CustodyV2 contract address: %s, tx hash: %s", + erc20CustodyNewAddr.Hex(), + txCustody.Hash().Hex(), + ) + + ensureTxReceipt(txCustody, "ERC20CustodyV2 deployment failed") + + // set custody contract in gateway + txSetCustody, err := r.GatewayEVM.SetCustody(r.EVMAuth, erc20CustodyNewAddr) + require.NoError(r, err) + + // deploy test dapp v2 + testDAppV2Addr, txTestDAppV2, _, err := testdappv2.DeployTestDAppV2(r.EVMAuth, r.EVMClient) + require.NoError(r, err) + + r.TestDAppV2EVMAddr = testDAppV2Addr + r.TestDAppV2EVM, err = testdappv2.NewTestDAppV2(testDAppV2Addr, r.EVMClient) + require.NoError(r, err) + + // check contract deployment receipt + ensureTxReceipt(txDonation, "EVM donation tx failed") + ensureTxReceipt(txProxy, "Gateway proxy deployment failed") + ensureTxReceipt(txSetCustody, "Set custody in Gateway failed") + ensureTxReceipt(txTestDAppV2, "TestDAppV2 deployment failed") + + // whitelist the ERC20 + txWhitelist, err := r.ERC20CustodyV2.Whitelist(r.EVMAuth, r.ERC20Addr) + require.NoError(r, err) + + ensureTxReceipt(txWhitelist, "ERC20 whitelist failed") +} diff --git a/e2e/runner/v2_setup_zeta.go b/e2e/runner/v2_setup_zeta.go new file mode 100644 index 0000000000..c82e8fa741 --- /dev/null +++ b/e2e/runner/v2_setup_zeta.go @@ -0,0 +1,110 @@ +package runner + +import ( + "time" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/e2e/utils" + "github.com/zeta-chain/zetacore/pkg/contracts/erc1967proxy" + "github.com/zeta-chain/zetacore/pkg/contracts/testdappv2" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +// SetZEVMContractsV2 set contracts for the ZEVM +func (r *E2ERunner) SetZEVMContractsV2() { + ensureTxReceipt := func(tx *ethtypes.Transaction, failMessage string) { + receipt := utils.MustWaitForTxReceipt(r.Ctx, r.ZEVMClient, tx, r.Logger, r.ReceiptTimeout) + r.requireTxSuccessful(receipt, failMessage+" tx hash: "+tx.Hash().Hex()) + } + + r.Logger.Print("⚙️ setting up ZEVM v2 network") + startTime := time.Now() + defer func() { + r.Logger.Info("ZEVM v2 network took %s\n", time.Since(startTime)) + }() + + r.Logger.Info("Deploying Gateway ZEVM") + gatewayZEVMAddr, txGateway, _, err := gatewayzevm.DeployGatewayZEVM(r.ZEVMAuth, r.ZEVMClient) + require.NoError(r, err) + + ensureTxReceipt(txGateway, "Gateway deployment failed") + + gatewayZEVMABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + require.NoError(r, err) + + // Encode the initializer data + initializerData, err := gatewayZEVMABI.Pack("initialize", r.WZetaAddr, r.Account.EVMAddress()) + require.NoError(r, err) + + // Deploy the proxy contract + r.Logger.Info( + "Deploying proxy with %s and %s, address: %s", + r.WZetaAddr.Hex(), + r.Account.EVMAddress().Hex(), + gatewayZEVMAddr.Hex(), + ) + proxyAddress, txProxy, _, err := erc1967proxy.DeployERC1967Proxy( + r.ZEVMAuth, + r.ZEVMClient, + gatewayZEVMAddr, + initializerData, + ) + require.NoError(r, err) + + r.GatewayZEVMAddr = proxyAddress + r.GatewayZEVM, err = gatewayzevm.NewGatewayZEVM(proxyAddress, r.ZEVMClient) + require.NoError(r, err) + r.Logger.Info("Gateway ZEVM contract address: %s, tx hash: %s", gatewayZEVMAddr.Hex(), txGateway.Hash().Hex()) + + // Set the gateway address in the protocol + err = r.ZetaTxServer.UpdateGatewayAddress(utils.AdminPolicyName, r.GatewayZEVMAddr.Hex()) + require.NoError(r, err) + + // deploy test dapp v2 + testDAppV2Addr, txTestDAppV2, _, err := testdappv2.DeployTestDAppV2(r.ZEVMAuth, r.ZEVMClient) + require.NoError(r, err) + + r.TestDAppV2ZEVMAddr = testDAppV2Addr + r.TestDAppV2ZEVM, err = testdappv2.NewTestDAppV2(testDAppV2Addr, r.ZEVMClient) + require.NoError(r, err) + + ensureTxReceipt(txProxy, "Gateway proxy deployment failed") + ensureTxReceipt(txTestDAppV2, "TestDAppV2 deployment failed") +} + +// UpdateChainParamsERC20CustodyContract update the erc20 custody contract in the chain params +// this operation is used when transitioning to new smart contract architecture where a new ERC20 custody contract is deployed +func (r *E2ERunner) UpdateChainParamsERC20CustodyContract() { + res, err := r.ObserverClient.GetChainParams(r.Ctx, &observertypes.QueryGetChainParamsRequest{}) + require.NoError(r, err) + + evmChainID, err := r.EVMClient.ChainID(r.Ctx) + require.NoError(r, err) + + // find old chain params + var ( + chainParams *observertypes.ChainParams + found bool + ) + for _, cp := range res.ChainParams.ChainParams { + if cp.ChainId == evmChainID.Int64() { + chainParams = cp + found = true + break + } + } + require.True(r, found, "Chain params not found for chain id %d", evmChainID) + + // update with the new ERC20 custody contract address + chainParams.Erc20CustodyContractAddress = r.ERC20CustodyV2Addr.Hex() + + // update the chain params + _, err = r.ZetaTxServer.BroadcastTx(utils.OperationalPolicyName, observertypes.NewMsgUpdateChainParams( + r.ZetaTxServer.MustGetAccountAddressFromName(utils.OperationalPolicyName), + chainParams, + )) + require.NoError(r, err) +} diff --git a/e2e/runner/v2_zevm.go b/e2e/runner/v2_zevm.go new file mode 100644 index 0000000000..681696b195 --- /dev/null +++ b/e2e/runner/v2_zevm.go @@ -0,0 +1,117 @@ +package runner + +import ( + "math/big" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" +) + +var gasLimit = big.NewInt(1000000) + +// V2ETHWithdraw calls Withdraw of Gateway with gas token on ZEVM +func (r *E2ERunner) V2ETHWithdraw( + receiver ethcommon.Address, + amount *big.Int, + revertOptions gatewayzevm.RevertOptions, +) *ethtypes.Transaction { + tx, err := r.GatewayZEVM.Withdraw( + r.ZEVMAuth, + receiver.Bytes(), + amount, + r.ETHZRC20Addr, + revertOptions, + ) + require.NoError(r, err) + + return tx +} + +// V2ETHWithdrawAndCall calls WithdrawAndCall of Gateway with gas token on ZEVM +func (r *E2ERunner) V2ETHWithdrawAndCall( + receiver ethcommon.Address, + amount *big.Int, + payload []byte, + revertOptions gatewayzevm.RevertOptions, +) *ethtypes.Transaction { + tx, err := r.GatewayZEVM.WithdrawAndCall( + r.ZEVMAuth, + receiver.Bytes(), + amount, + r.ETHZRC20Addr, + payload, + gasLimit, + revertOptions, + ) + require.NoError(r, err) + + return tx +} + +// V2ERC20Withdraw calls Withdraw of Gateway with erc20 token on ZEVM +func (r *E2ERunner) V2ERC20Withdraw( + receiver ethcommon.Address, + amount *big.Int, + revertOptions gatewayzevm.RevertOptions, +) *ethtypes.Transaction { + tx, err := r.GatewayZEVM.Withdraw( + r.ZEVMAuth, + receiver.Bytes(), + amount, + r.ERC20ZRC20Addr, + revertOptions, + ) + require.NoError(r, err) + + return tx +} + +// V2ERC20WithdrawAndCall calls WithdrawAndCall of Gateway with erc20 token on ZEVM +func (r *E2ERunner) V2ERC20WithdrawAndCall( + receiver ethcommon.Address, + amount *big.Int, + payload []byte, + revertOptions gatewayzevm.RevertOptions, +) *ethtypes.Transaction { + // this function take more gas than default 500k + // so we need to increase the gas limit + previousGasLimit := r.ZEVMAuth.GasLimit + r.ZEVMAuth.GasLimit = 10000000 + defer func() { + r.ZEVMAuth.GasLimit = previousGasLimit + }() + + tx, err := r.GatewayZEVM.WithdrawAndCall( + r.ZEVMAuth, + receiver.Bytes(), + amount, + r.ERC20ZRC20Addr, + payload, + gasLimit, + revertOptions, + ) + require.NoError(r, err) + + return tx +} + +// V2ZEVMToEMVCall calls Call of Gateway on ZEVM +func (r *E2ERunner) V2ZEVMToEMVCall( + receiver ethcommon.Address, + payload []byte, + revertOptions gatewayzevm.RevertOptions, +) *ethtypes.Transaction { + tx, err := r.GatewayZEVM.Call( + r.ZEVMAuth, + receiver.Bytes(), + r.ETHZRC20Addr, + payload, + gasLimit, + revertOptions, + ) + require.NoError(r, err) + + return tx +} diff --git a/e2e/runner/zeta.go b/e2e/runner/zeta.go index 95f160a801..fec82f459a 100644 --- a/e2e/runner/zeta.go +++ b/e2e/runner/zeta.go @@ -9,8 +9,8 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/require" - zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" - connectorzevm "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zetaconnectorzevm.sol" + zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol" + connectorzevm "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/zetaconnectorzevm.sol" "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/pkg/retry" @@ -244,7 +244,7 @@ func (r *E2ERunner) WithdrawERC20(amount *big.Int) *ethtypes.Transaction { event.From.Hex(), event.To, event.Value, - event.Gasfee, + event.GasFee, ) } diff --git a/e2e/txserver/zeta_tx_server.go b/e2e/txserver/zeta_tx_server.go index 928c4c0382..39a6d8325b 100644 --- a/e2e/txserver/zeta_tx_server.go +++ b/e2e/txserver/zeta_tx_server.go @@ -298,10 +298,29 @@ func (zts ZetaTxServer) EnableHeaderVerification(account string, chainIDList []i return err } -// DeploySystemContractsAndZRC20 deploys the system contracts and ZRC20 contracts -// returns the addresses of uniswap factory, router and erc20 zrc20 -func (zts ZetaTxServer) DeploySystemContractsAndZRC20( - accountOperational, accountAdmin, erc20Addr string, +// UpdateGatewayAddress updates the gateway address +func (zts ZetaTxServer) UpdateGatewayAddress(account, gatewayAddr string) error { + // retrieve account + acc, err := zts.clientCtx.Keyring.Key(account) + if err != nil { + return err + } + addr, err := acc.GetAddress() + if err != nil { + return err + } + + _, err = zts.BroadcastTx(account, fungibletypes.NewMsgUpdateGatewayContract( + addr.String(), + gatewayAddr, + )) + return err +} + +// DeploySystemContracts deploys the system contracts +// returns the addresses of uniswap factory, router +func (zts ZetaTxServer) DeploySystemContracts( + accountOperational, accountAdmin string, ) (SystemContractAddresses, error) { // retrieve account accOperational, err := zts.clientCtx.Keyring.Key(accountOperational) @@ -375,6 +394,37 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20( ) } + return SystemContractAddresses{ + UniswapV2FactoryAddr: uniswapV2FactoryAddr, + UniswapV2RouterAddr: uniswapV2RouterAddr, + ZEVMConnectorAddr: zevmConnectorAddr, + WZETAAddr: wzetaAddr, + }, nil +} + +// DeployZRC20s deploys the ZRC20 contracts +// returns the addresses of erc20 zrc20 +func (zts ZetaTxServer) DeployZRC20s( + accountOperational, accountAdmin, erc20Addr string, +) (string, error) { + // retrieve account + accOperational, err := zts.clientCtx.Keyring.Key(accountOperational) + if err != nil { + return "", err + } + addrOperational, err := accOperational.GetAddress() + if err != nil { + return "", err + } + accAdmin, err := zts.clientCtx.Keyring.Key(accountAdmin) + if err != nil { + return "", err + } + addrAdmin, err := accAdmin.GetAddress() + if err != nil { + return "", err + } + // authorization for deploying new ZRC20 has changed from accountOperational to accountAdmin in v19 // we use this query to check the current authorization for the message // if pre v19 the query is not implement and authorization is operational @@ -382,7 +432,7 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20( deployerAddr := addrAdmin.String() authorization, preV19, err := zts.fetchMessagePermissions(&fungibletypes.MsgDeployFungibleCoinZRC20{}) if err != nil { - return SystemContractAddresses{}, fmt.Errorf("failed to fetch message permissions: %s", err.Error()) + return "", fmt.Errorf("failed to fetch message permissions: %s", err.Error()) } if preV19 || authorization == authoritytypes.PolicyType_groupOperational { deployerAccount = accountOperational @@ -401,7 +451,7 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20( 100000, )) if err != nil { - return SystemContractAddresses{}, fmt.Errorf("failed to deploy eth zrc20: %s", err.Error()) + return "", fmt.Errorf("failed to deploy eth zrc20: %s", err.Error()) } // deploy btc zrc20 @@ -416,7 +466,7 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20( 100000, )) if err != nil { - return SystemContractAddresses{}, fmt.Errorf("failed to deploy btc zrc20: %s", err.Error()) + return "", fmt.Errorf("failed to deploy btc zrc20: %s", err.Error()) } // deploy sol zrc20 @@ -431,11 +481,11 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20( 100000, )) if err != nil { - return SystemContractAddresses{}, fmt.Errorf("failed to deploy sol zrc20: %s", err.Error()) + return "", fmt.Errorf("failed to deploy sol zrc20: %s", err.Error()) } // deploy erc20 zrc20 - res, err = zts.BroadcastTx(deployerAccount, fungibletypes.NewMsgDeployFungibleCoinZRC20( + res, err := zts.BroadcastTx(deployerAccount, fungibletypes.NewMsgDeployFungibleCoinZRC20( deployerAddr, erc20Addr, chains.GoerliLocalnet.ChainId, @@ -446,25 +496,19 @@ func (zts ZetaTxServer) DeploySystemContractsAndZRC20( 100000, )) if err != nil { - return SystemContractAddresses{}, fmt.Errorf("failed to deploy erc20 zrc20: %s", err.Error()) + return "", fmt.Errorf("failed to deploy erc20 zrc20: %s", err.Error()) } // fetch the erc20 zrc20 contract address and remove the quotes erc20zrc20Addr, err := FetchAttributeFromTxResponse(res, "Contract") if err != nil { - return SystemContractAddresses{}, fmt.Errorf("failed to fetch erc20 zrc20 contract address: %s", err.Error()) + return "", fmt.Errorf("failed to fetch erc20 zrc20 contract address: %s, %s", err.Error(), res.String()) } if !ethcommon.IsHexAddress(erc20zrc20Addr) { - return SystemContractAddresses{}, fmt.Errorf("invalid address in event: %s", erc20zrc20Addr) + return "", fmt.Errorf("invalid address in event: %s", erc20zrc20Addr) } - return SystemContractAddresses{ - uniswapV2FactoryAddr, - uniswapV2RouterAddr, - zevmConnectorAddr, - wzetaAddr, - erc20zrc20Addr, - }, nil + return erc20zrc20Addr, nil } // FundEmissionsPool funds the emissions pool with the given amount diff --git a/e2e/utils/contracts.go b/e2e/utils/contracts.go new file mode 100644 index 0000000000..605e458a22 --- /dev/null +++ b/e2e/utils/contracts.go @@ -0,0 +1,33 @@ +package utils + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/stretchr/testify/require" + + testcontract "github.com/zeta-chain/zetacore/testutil/contracts" +) + +const ( + // ErrHashRevertFoo is the keccak256 hash of custom error "Foo()" on reverter contract + ErrHashRevertFoo = "0xbfb4ebcf" +) + +// MustHaveCalledExampleContract checks if the contract has been called correctly +func MustHaveCalledExampleContract( + t require.TestingT, + contract *testcontract.Example, + amount *big.Int, +) { + bar, err := contract.Bar(&bind.CallOpts{}) + require.NoError(t, err) + require.Equal( + t, + 0, + bar.Cmp(amount), + "cross-chain call failed bar value %s should be equal to amount %s", + bar.String(), + amount.String(), + ) +} diff --git a/e2e/utils/zetacore.go b/e2e/utils/zetacore.go index a10dc8d68b..9122860d75 100644 --- a/e2e/utils/zetacore.go +++ b/e2e/utils/zetacore.go @@ -20,7 +20,7 @@ const ( AdminPolicyName = "admin" OperationalPolicyName = "operational" - DefaultCctxTimeout = 4 * time.Minute + DefaultCctxTimeout = 6 * time.Minute ) // WaitCctxMinedByInboundHash waits until cctx is mined; returns the cctxIndex (the last one) diff --git a/go.mod b/go.mod index 865fc87402..c151309321 100644 --- a/go.mod +++ b/go.mod @@ -37,8 +37,11 @@ require ( github.com/hashicorp/go-getter v1.7.5 github.com/huandu/skiplist v1.2.0 github.com/improbable-eng/grpc-web v0.15.0 + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/libp2p/go-libp2p v0.27.8 github.com/libp2p/go-libp2p-kad-dht v0.24.2 + github.com/mattn/go-sqlite3 v1.14.19 // indirect github.com/multiformats/go-multiaddr v0.9.0 github.com/nanmu42/etherscan-api v1.10.0 github.com/near/borsh-go v0.3.1 @@ -57,7 +60,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/zeta-chain/ethermint v0.0.0-20240729121328-43bf9ddbf82f github.com/zeta-chain/keystone/keys v0.0.0-20231105174229-903bc9405da2 - github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240418181724-c222fd3ae1f5 + github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240816144801-7eb673cf8890 gitlab.com/thorchain/tss/go-tss v1.6.5 gitlab.com/thorchain/tss/tss-lib v0.2.0 go.nhat.io/grpcmock v0.25.0 @@ -185,7 +188,7 @@ require ( github.com/hashicorp/hcl v1.0.0 // indirect github.com/hdevalence/ed25519consensus v0.1.0 // indirect github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.2.2 // indirect + github.com/holiman/uint256 v1.2.3 // indirect github.com/huin/goupnp v1.2.0 // indirect github.com/iancoleman/orderedmap v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -198,8 +201,6 @@ require ( github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/jbenet/goprocess v0.1.4 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -228,7 +229,6 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/mattn/go-sqlite3 v1.14.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/miekg/dns v1.1.54 // indirect github.com/miekg/pkcs11 v1.1.1 // indirect @@ -292,8 +292,8 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect github.com/tidwall/sjson v1.2.5 // indirect - github.com/tklauser/go-sysconf v0.3.10 // indirect - github.com/tklauser/numcpus v0.4.0 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect diff --git a/go.sum b/go.sum index 9586df94bf..6eb0a71e76 100644 --- a/go.sum +++ b/go.sum @@ -911,8 +911,8 @@ github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3s github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw= -github.com/holiman/uint256 v1.2.2 h1:TXKcSGc2WaxPD2+bmzAsVthL4+pEN0YwXcL5qED83vk= -github.com/holiman/uint256 v1.2.2/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= +github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= +github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c= github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U= @@ -1556,11 +1556,11 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= -github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= -github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= -github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= -github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= @@ -1631,8 +1631,8 @@ github.com/zeta-chain/go-tss v0.0.0-20240729195411-9f5ae8189449 h1:4U+4g2QQjbrme github.com/zeta-chain/go-tss v0.0.0-20240729195411-9f5ae8189449/go.mod h1:LN1IBRN8xQkKgdgLhl5BDGZyPm70QOTbVLejdS2FVpo= github.com/zeta-chain/keystone/keys v0.0.0-20231105174229-903bc9405da2 h1:gd2uE0X+ZbdFJ8DubxNqLbOVlCB12EgWdzSNRAR82tM= github.com/zeta-chain/keystone/keys v0.0.0-20231105174229-903bc9405da2/go.mod h1:x7Bkwbzt2W2lQfjOirnff0Dj+tykdbTG1FMJPVPZsvE= -github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240418181724-c222fd3ae1f5 h1:ljM7xka3WZvth9k1uYxrG3/FKQQTkR96FZlIjUKOoYw= -github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240418181724-c222fd3ae1f5/go.mod h1:v79f+eY6PMpmLv188FAubst4XV2Mm8mUmx1OgmdFG3c= +github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240816144801-7eb673cf8890 h1:y2TNtm9ZF/GjJIg40wiZ/IqoeDouFfqi27Uu3xSQaVE= +github.com/zeta-chain/protocol-contracts v1.0.2-athens3.0.20240816144801-7eb673cf8890/go.mod h1:SjT7QirtJE8stnAe1SlNOanxtfSfijJm3MGJ+Ax7w7w= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= github.com/zondax/hid v0.9.2 h1:WCJFnEDMiqGF64nlZz28E9qLVZ0KSJ7xpc5DLEyma2U= github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= @@ -2027,6 +2027,8 @@ golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/pkg/chains/chain_test.go b/pkg/chains/chain_test.go index 23bc6adf18..d097d3946c 100644 --- a/pkg/chains/chain_test.go +++ b/pkg/chains/chain_test.go @@ -400,7 +400,7 @@ func TestGetChainFromChainID(t *testing.T) { chain, found := chains.GetChainFromChainID(chains.ZetaChainMainnet.ChainId, []chains.Chain{}) require.EqualValues(t, chains.ZetaChainMainnet, chain) require.True(t, found) - chain, found = chains.GetChainFromChainID(9999, []chains.Chain{}) + _, found = chains.GetChainFromChainID(9999, []chains.Chain{}) require.False(t, found) } diff --git a/pkg/chains/conversion.go b/pkg/chains/conversion.go index 949d39ebb7..3200b76693 100644 --- a/pkg/chains/conversion.go +++ b/pkg/chains/conversion.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" + "cosmossdk.io/errors" "github.com/btcsuite/btcd/chaincfg/chainhash" ethcommon "github.com/ethereum/go-ethereum/common" ) @@ -39,7 +40,7 @@ func ParseAddressAndData(message string) (ethcommon.Address, []byte, error) { data, err := hex.DecodeString(message) if err != nil { - return ethcommon.Address{}, nil, fmt.Errorf("message should be a hex encoded string: " + err.Error()) + return ethcommon.Address{}, nil, errors.Wrap(err, "message should be a hex encoded string") } if len(data) < 20 { diff --git a/pkg/coin/coin.go b/pkg/coin/coin.go index 4c5c86889b..223c2cbed9 100644 --- a/pkg/coin/coin.go +++ b/pkg/coin/coin.go @@ -16,9 +16,12 @@ func GetCoinType(coin string) (CoinType, error) { if err != nil { return CoinType_Cmd, err } - if coinInt < 0 || coinInt > 3 { + + // check boundaries of the enum + if coinInt < 0 || coinInt > int64(len(CoinType_name)) { return CoinType_Cmd, fmt.Errorf("invalid coin type %d", coinInt) } + // #nosec G115 always in range return CoinType(coinInt), nil } diff --git a/pkg/coin/coin.pb.go b/pkg/coin/coin.pb.go index b3c3999173..a974356d66 100644 --- a/pkg/coin/coin.pb.go +++ b/pkg/coin/coin.pb.go @@ -24,10 +24,11 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type CoinType int32 const ( - CoinType_Zeta CoinType = 0 - CoinType_Gas CoinType = 1 - CoinType_ERC20 CoinType = 2 - CoinType_Cmd CoinType = 3 + CoinType_Zeta CoinType = 0 + CoinType_Gas CoinType = 1 + CoinType_ERC20 CoinType = 2 + CoinType_Cmd CoinType = 3 + CoinType_NoAssetCall CoinType = 4 ) var CoinType_name = map[int32]string{ @@ -35,13 +36,15 @@ var CoinType_name = map[int32]string{ 1: "Gas", 2: "ERC20", 3: "Cmd", + 4: "NoAssetCall", } var CoinType_value = map[string]int32{ - "Zeta": 0, - "Gas": 1, - "ERC20": 2, - "Cmd": 3, + "Zeta": 0, + "Gas": 1, + "ERC20": 2, + "Cmd": 3, + "NoAssetCall": 4, } func (x CoinType) String() string { @@ -61,17 +64,18 @@ func init() { } var fileDescriptor_924f8c5071ab3892 = []byte{ - // 190 bytes of a gzipped FileDescriptorProto + // 207 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xab, 0x4a, 0x2d, 0x49, 0x4c, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x07, 0xb3, 0xf2, 0x8b, 0x52, 0xf5, 0x0b, 0xb2, 0xd3, 0xf5, 0x93, 0xf3, 0x33, 0xf3, 0xc0, 0x84, 0x5e, 0x41, 0x51, 0x7e, 0x49, 0xbe, 0x90, 0x34, 0x5c, 0x9d, 0x1e, 0x4c, 0x9d, 0x5e, 0x41, 0x76, 0xba, 0x1e, 0x48, 0x89, 0x94, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, - 0x58, 0x9d, 0x3e, 0x88, 0x05, 0xd1, 0xa2, 0x65, 0xce, 0xc5, 0xe1, 0x9c, 0x9f, 0x99, 0x17, 0x52, + 0x58, 0x9d, 0x3e, 0x88, 0x05, 0xd1, 0xa2, 0xe5, 0xc1, 0xc5, 0xe1, 0x9c, 0x9f, 0x99, 0x17, 0x52, 0x59, 0x90, 0x2a, 0xc4, 0xc1, 0xc5, 0x12, 0x95, 0x5a, 0x92, 0x28, 0xc0, 0x20, 0xc4, 0xce, 0xc5, 0xec, 0x9e, 0x58, 0x2c, 0xc0, 0x28, 0xc4, 0xc9, 0xc5, 0xea, 0x1a, 0xe4, 0x6c, 0x64, 0x20, 0xc0, - 0x04, 0x12, 0x73, 0xce, 0x4d, 0x11, 0x60, 0x96, 0x62, 0x59, 0xb1, 0x44, 0x8e, 0xd1, 0xc9, 0xf1, - 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, - 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0xd4, 0xd3, 0x33, 0x4b, 0x32, 0x4a, - 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xc1, 0xce, 0xd5, 0xc5, 0xe1, 0xf2, 0x24, 0x36, 0xb0, 0x13, 0x8c, - 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x52, 0x5c, 0x7d, 0x09, 0xdf, 0x00, 0x00, 0x00, + 0x04, 0x12, 0x73, 0xce, 0x4d, 0x11, 0x60, 0x16, 0xe2, 0xe7, 0xe2, 0xf6, 0xcb, 0x77, 0x2c, 0x2e, + 0x4e, 0x2d, 0x71, 0x4e, 0xcc, 0xc9, 0x11, 0x60, 0x91, 0x62, 0x59, 0xb1, 0x44, 0x8e, 0xd1, 0xc9, + 0xf1, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, + 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0xd4, 0xd3, 0x33, 0x4b, 0x32, + 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xc1, 0xee, 0xd7, 0xc5, 0xe1, 0x95, 0x24, 0x36, 0xb0, 0x9b, + 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x45, 0xfd, 0x2c, 0xf4, 0xf0, 0x00, 0x00, 0x00, } diff --git a/pkg/coin/coin_test.go b/pkg/coin/coin_test.go index be7808d7a2..9f930039f7 100644 --- a/pkg/coin/coin_test.go +++ b/pkg/coin/coin_test.go @@ -101,19 +101,16 @@ func TestGetCoinType(t *testing.T) { { name: "invalid coin type negative", coin: "-1", - want: CoinType_Cmd, wantErr: true, }, { - name: "invalid coin type large number", - coin: "4", - want: CoinType_Cmd, - wantErr: true, + name: "invalid coin type large number", + coin: "4", + want: CoinType(4), }, { name: "invalid coin type non-integer", coin: "abc", - want: CoinType_Cmd, wantErr: true, }, } diff --git a/pkg/constant/constant.go b/pkg/constant/constant.go index 6aba0f4dea..e0d2e16a5d 100644 --- a/pkg/constant/constant.go +++ b/pkg/constant/constant.go @@ -1,6 +1,12 @@ package constant +import "time" + const ( + // ZetaBlockTime is the block time of the ZetaChain network + // It's a rough estimate that can be used in non-critical path to estimate the time of a block + ZetaBlockTime = 6000 * time.Millisecond + // DonationMessage is the message for donation transactions // Transaction sent to the TSS or ERC20 Custody address containing this message are considered as a donation DonationMessage = "I am rich!" @@ -8,6 +14,12 @@ const ( // CmdWhitelistERC20 is used for CCTX of type cmd to give the instruction to the TSS to whitelist an ERC20 on an exeternal chain CmdWhitelistERC20 = "cmd_whitelist_erc20" + // CmdMigrateERC20CustodyFunds is used for CCTX of type cmd to give the instruction to the TSS to transfer its funds on a new address + CmdMigrateERC20CustodyFunds = "cmd_migrate_erc20_custody_funds" + + // CmdUpdateERC20CustodyPauseStatus is used for CCTX of type cmd to give the instruction to the TSS to update the pause status of the ERC20 custody contract + CmdUpdateERC20CustodyPauseStatus = "cmd_update_erc20_custody_pause_status" + // CmdMigrateTssFunds is used for CCTX of type cmd to give the instruction to the TSS to transfer its funds on a new address CmdMigrateTssFunds = "cmd_migrate_tss_funds" @@ -19,4 +31,13 @@ const ( // The Solana protocol sets minimum rent exempt to 890880 lamports but we set it to 1_000_000 lamports (0.001 SOL) // The number 890880 comes from CLI command `solana rent 0` and has been verified on devnet gateway program SolanaWalletRentExempt = 1_000_000 + + // EVMZeroAddress is the zero address for EVM address format + EVMZeroAddress = "0x0000000000000000000000000000000000000000" + + // OptionPause is the argument used in CmdUpdateERC20CustodyPauseStatus to pause the ERC20 custody contract + OptionPause = "pause" + + // OptionUnpause is the argument used in CmdUpdateERC20CustodyPauseStatus to unpause the ERC20 custody contract + OptionUnpause = "unpause" ) diff --git a/pkg/contracts/erc1967proxy/ERC1967Proxy.abi b/pkg/contracts/erc1967proxy/ERC1967Proxy.abi new file mode 100644 index 0000000000..9dadb5f5fa --- /dev/null +++ b/pkg/contracts/erc1967proxy/ERC1967Proxy.abi @@ -0,0 +1,71 @@ +[ + { + "type": "constructor", + "inputs": [ + { + "name": "_logic", + "type": "address", + "internalType": "address" + }, + { + "name": "_data", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "payable" + }, + { + "type": "fallback", + "stateMutability": "payable" + }, + { + "type": "receive", + "stateMutability": "payable" + }, + { + "type": "event", + "name": "AdminChanged", + "inputs": [ + { + "name": "previousAdmin", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "newAdmin", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "BeaconUpgraded", + "inputs": [ + { + "name": "beacon", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + } +] diff --git a/pkg/contracts/erc1967proxy/ERC1967Proxy.bin b/pkg/contracts/erc1967proxy/ERC1967Proxy.bin new file mode 100644 index 0000000000..d5f2d8838f --- /dev/null +++ b/pkg/contracts/erc1967proxy/ERC1967Proxy.bin @@ -0,0 +1 @@ +0x60806040526040516107bb3803806107bb833981016040819052610022916102dc565b61002e82826000610035565b505061043b565b61003e8361006b565b60008251118061004b5750805b156100665761006483836100ab60201b6100291760201c565b505b505050565b610074816100d7565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606100d08383604051806060016040528060278152602001610794602791396101a9565b9392505050565b6100ea8161022260201b6100551760201c565b6101515760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b806101887f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b61023160201b6100711760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b6060600080856001600160a01b0316856040516101c691906103aa565b600060405180830381855af49150503d8060008114610201576040519150601f19603f3d011682016040523d82523d6000602084013e610206565b606091505b50909250905061021886838387610234565b9695505050505050565b6001600160a01b03163b151590565b90565b606083156102a0578251610299576001600160a01b0385163b6102995760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610148565b50816102aa565b6102aa83836102b2565b949350505050565b8151156102c25781518083602001fd5b8060405162461bcd60e51b815260040161014891906103c6565b600080604083850312156102ef57600080fd5b82516001600160a01b038116811461030657600080fd5b60208401519092506001600160401b038082111561032357600080fd5b818501915085601f83011261033757600080fd5b81518181111561034957610349610425565b604051601f8201601f19908116603f0116810190838211818310171561037157610371610425565b8160405282815288602084870101111561038a57600080fd5b61039b8360208301602088016103f9565b80955050505050509250929050565b600082516103bc8184602087016103f9565b9190910192915050565b60208152600082518060208401526103e58160408501602087016103f9565b601f01601f19169190910160400192915050565b60005b838110156104145781810151838201526020016103fc565b838111156100645750506000910152565b634e487b7160e01b600052604160045260246000fd5b61034a8061044a6000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610074565b6100b9565b565b606061004e83836040518060600160405280602781526020016102ee602791396100dd565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b90565b60006100b47f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b905090565b3660008037600080366000845af43d6000803e8080156100d8573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516101079190610250565b600060405180830381855af49150503d8060008114610142576040519150601f19603f3d011682016040523d82523d6000602084013e610147565b606091505b509150915061015886838387610162565b9695505050505050565b606083156101fa5782516101f35773ffffffffffffffffffffffffffffffffffffffff85163b6101f3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610204565b610204838361020c565b949350505050565b81511561021c5781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101ea919061026c565b600082516102628184602087016102bd565b9190910192915050565b602081526000825180602084015261028b8160408501602087016102bd565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b60005b838110156102d85781810151838201526020016102c0565b838111156102e7576000848401525b5050505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220721016d1ac0a7f9891c702fb3aea5ab01194272fdd3832cd3f3c7073e88919c564736f6c63430008070033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564 diff --git a/pkg/contracts/erc1967proxy/ERC1967Proxy.go b/pkg/contracts/erc1967proxy/ERC1967Proxy.go new file mode 100644 index 0000000000..74e88b4fbf --- /dev/null +++ b/pkg/contracts/erc1967proxy/ERC1967Proxy.go @@ -0,0 +1,668 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package erc1967proxy + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// ERC1967ProxyMetaData contains all meta data concerning the ERC1967Proxy contract. +var ERC1967ProxyMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"_logic\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"payable\"},{\"type\":\"fallback\",\"stateMutability\":\"payable\"},{\"type\":\"receive\",\"stateMutability\":\"payable\"},{\"type\":\"event\",\"name\":\"AdminChanged\",\"inputs\":[{\"name\":\"previousAdmin\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"newAdmin\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"BeaconUpgraded\",\"inputs\":[{\"name\":\"beacon\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Upgraded\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false}]", + Bin: "0x60806040526040516107bb3803806107bb833981016040819052610022916102dc565b61002e82826000610035565b505061043b565b61003e8361006b565b60008251118061004b5750805b156100665761006483836100ab60201b6100291760201c565b505b505050565b610074816100d7565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b60606100d08383604051806060016040528060278152602001610794602791396101a9565b9392505050565b6100ea8161022260201b6100551760201c565b6101515760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b60648201526084015b60405180910390fd5b806101887f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc60001b61023160201b6100711760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b6060600080856001600160a01b0316856040516101c691906103aa565b600060405180830381855af49150503d8060008114610201576040519150601f19603f3d011682016040523d82523d6000602084013e610206565b606091505b50909250905061021886838387610234565b9695505050505050565b6001600160a01b03163b151590565b90565b606083156102a0578251610299576001600160a01b0385163b6102995760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610148565b50816102aa565b6102aa83836102b2565b949350505050565b8151156102c25781518083602001fd5b8060405162461bcd60e51b815260040161014891906103c6565b600080604083850312156102ef57600080fd5b82516001600160a01b038116811461030657600080fd5b60208401519092506001600160401b038082111561032357600080fd5b818501915085601f83011261033757600080fd5b81518181111561034957610349610425565b604051601f8201601f19908116603f0116810190838211818310171561037157610371610425565b8160405282815288602084870101111561038a57600080fd5b61039b8360208301602088016103f9565b80955050505050509250929050565b600082516103bc8184602087016103f9565b9190910192915050565b60208152600082518060208401526103e58160408501602087016103f9565b601f01601f19169190910160400192915050565b60005b838110156104145781810151838201526020016103fc565b838111156100645750506000910152565b634e487b7160e01b600052604160045260246000fd5b61034a8061044a6000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610074565b6100b9565b565b606061004e83836040518060600160405280602781526020016102ee602791396100dd565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff163b151590565b90565b60006100b47f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b905090565b3660008037600080366000845af43d6000803e8080156100d8573d6000f35b3d6000fd5b60606000808573ffffffffffffffffffffffffffffffffffffffff16856040516101079190610250565b600060405180830381855af49150503d8060008114610142576040519150601f19603f3d011682016040523d82523d6000602084013e610147565b606091505b509150915061015886838387610162565b9695505050505050565b606083156101fa5782516101f35773ffffffffffffffffffffffffffffffffffffffff85163b6101f3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064015b60405180910390fd5b5081610204565b610204838361020c565b949350505050565b81511561021c5781518083602001fd5b806040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101ea919061026c565b600082516102628184602087016102bd565b9190910192915050565b602081526000825180602084015261028b8160408501602087016102bd565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b60005b838110156102d85781810151838201526020016102c0565b838111156102e7576000848401525b5050505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a2646970667358221220721016d1ac0a7f9891c702fb3aea5ab01194272fdd3832cd3f3c7073e88919c564736f6c63430008070033416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564", +} + +// ERC1967ProxyABI is the input ABI used to generate the binding from. +// Deprecated: Use ERC1967ProxyMetaData.ABI instead. +var ERC1967ProxyABI = ERC1967ProxyMetaData.ABI + +// ERC1967ProxyBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use ERC1967ProxyMetaData.Bin instead. +var ERC1967ProxyBin = ERC1967ProxyMetaData.Bin + +// DeployERC1967Proxy deploys a new Ethereum contract, binding an instance of ERC1967Proxy to it. +func DeployERC1967Proxy(auth *bind.TransactOpts, backend bind.ContractBackend, _logic common.Address, _data []byte) (common.Address, *types.Transaction, *ERC1967Proxy, error) { + parsed, err := ERC1967ProxyMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(ERC1967ProxyBin), backend, _logic, _data) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ERC1967Proxy{ERC1967ProxyCaller: ERC1967ProxyCaller{contract: contract}, ERC1967ProxyTransactor: ERC1967ProxyTransactor{contract: contract}, ERC1967ProxyFilterer: ERC1967ProxyFilterer{contract: contract}}, nil +} + +// ERC1967Proxy is an auto generated Go binding around an Ethereum contract. +type ERC1967Proxy struct { + ERC1967ProxyCaller // Read-only binding to the contract + ERC1967ProxyTransactor // Write-only binding to the contract + ERC1967ProxyFilterer // Log filterer for contract events +} + +// ERC1967ProxyCaller is an auto generated read-only Go binding around an Ethereum contract. +type ERC1967ProxyCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC1967ProxyTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ERC1967ProxyTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC1967ProxyFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type ERC1967ProxyFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ERC1967ProxySession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ERC1967ProxySession struct { + Contract *ERC1967Proxy // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC1967ProxyCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ERC1967ProxyCallerSession struct { + Contract *ERC1967ProxyCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ERC1967ProxyTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ERC1967ProxyTransactorSession struct { + Contract *ERC1967ProxyTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ERC1967ProxyRaw is an auto generated low-level Go binding around an Ethereum contract. +type ERC1967ProxyRaw struct { + Contract *ERC1967Proxy // Generic contract binding to access the raw methods on +} + +// ERC1967ProxyCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ERC1967ProxyCallerRaw struct { + Contract *ERC1967ProxyCaller // Generic read-only contract binding to access the raw methods on +} + +// ERC1967ProxyTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ERC1967ProxyTransactorRaw struct { + Contract *ERC1967ProxyTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewERC1967Proxy creates a new instance of ERC1967Proxy, bound to a specific deployed contract. +func NewERC1967Proxy(address common.Address, backend bind.ContractBackend) (*ERC1967Proxy, error) { + contract, err := bindERC1967Proxy(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &ERC1967Proxy{ERC1967ProxyCaller: ERC1967ProxyCaller{contract: contract}, ERC1967ProxyTransactor: ERC1967ProxyTransactor{contract: contract}, ERC1967ProxyFilterer: ERC1967ProxyFilterer{contract: contract}}, nil +} + +// NewERC1967ProxyCaller creates a new read-only instance of ERC1967Proxy, bound to a specific deployed contract. +func NewERC1967ProxyCaller(address common.Address, caller bind.ContractCaller) (*ERC1967ProxyCaller, error) { + contract, err := bindERC1967Proxy(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &ERC1967ProxyCaller{contract: contract}, nil +} + +// NewERC1967ProxyTransactor creates a new write-only instance of ERC1967Proxy, bound to a specific deployed contract. +func NewERC1967ProxyTransactor(address common.Address, transactor bind.ContractTransactor) (*ERC1967ProxyTransactor, error) { + contract, err := bindERC1967Proxy(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &ERC1967ProxyTransactor{contract: contract}, nil +} + +// NewERC1967ProxyFilterer creates a new log filterer instance of ERC1967Proxy, bound to a specific deployed contract. +func NewERC1967ProxyFilterer(address common.Address, filterer bind.ContractFilterer) (*ERC1967ProxyFilterer, error) { + contract, err := bindERC1967Proxy(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &ERC1967ProxyFilterer{contract: contract}, nil +} + +// bindERC1967Proxy binds a generic wrapper to an already deployed contract. +func bindERC1967Proxy(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := ERC1967ProxyMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ERC1967Proxy *ERC1967ProxyRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC1967Proxy.Contract.ERC1967ProxyCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ERC1967Proxy *ERC1967ProxyRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC1967Proxy.Contract.ERC1967ProxyTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC1967Proxy *ERC1967ProxyRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC1967Proxy.Contract.ERC1967ProxyTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ERC1967Proxy *ERC1967ProxyCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _ERC1967Proxy.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ERC1967Proxy *ERC1967ProxyTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC1967Proxy.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ERC1967Proxy *ERC1967ProxyTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ERC1967Proxy.Contract.contract.Transact(opts, method, params...) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_ERC1967Proxy *ERC1967ProxyTransactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _ERC1967Proxy.contract.RawTransact(opts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_ERC1967Proxy *ERC1967ProxySession) Fallback(calldata []byte) (*types.Transaction, error) { + return _ERC1967Proxy.Contract.Fallback(&_ERC1967Proxy.TransactOpts, calldata) +} + +// Fallback is a paid mutator transaction binding the contract fallback function. +// +// Solidity: fallback() payable returns() +func (_ERC1967Proxy *ERC1967ProxyTransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _ERC1967Proxy.Contract.Fallback(&_ERC1967Proxy.TransactOpts, calldata) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_ERC1967Proxy *ERC1967ProxyTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ERC1967Proxy.contract.RawTransact(opts, nil) // calldata is disallowed for receive function +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_ERC1967Proxy *ERC1967ProxySession) Receive() (*types.Transaction, error) { + return _ERC1967Proxy.Contract.Receive(&_ERC1967Proxy.TransactOpts) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_ERC1967Proxy *ERC1967ProxyTransactorSession) Receive() (*types.Transaction, error) { + return _ERC1967Proxy.Contract.Receive(&_ERC1967Proxy.TransactOpts) +} + +// ERC1967ProxyAdminChangedIterator is returned from FilterAdminChanged and is used to iterate over the raw logs and unpacked data for AdminChanged events raised by the ERC1967Proxy contract. +type ERC1967ProxyAdminChangedIterator struct { + Event *ERC1967ProxyAdminChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ERC1967ProxyAdminChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ERC1967ProxyAdminChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ERC1967ProxyAdminChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ERC1967ProxyAdminChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ERC1967ProxyAdminChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ERC1967ProxyAdminChanged represents a AdminChanged event raised by the ERC1967Proxy contract. +type ERC1967ProxyAdminChanged struct { + PreviousAdmin common.Address + NewAdmin common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterAdminChanged is a free log retrieval operation binding the contract event 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f. +// +// Solidity: event AdminChanged(address previousAdmin, address newAdmin) +func (_ERC1967Proxy *ERC1967ProxyFilterer) FilterAdminChanged(opts *bind.FilterOpts) (*ERC1967ProxyAdminChangedIterator, error) { + + logs, sub, err := _ERC1967Proxy.contract.FilterLogs(opts, "AdminChanged") + if err != nil { + return nil, err + } + return &ERC1967ProxyAdminChangedIterator{contract: _ERC1967Proxy.contract, event: "AdminChanged", logs: logs, sub: sub}, nil +} + +// WatchAdminChanged is a free log subscription operation binding the contract event 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f. +// +// Solidity: event AdminChanged(address previousAdmin, address newAdmin) +func (_ERC1967Proxy *ERC1967ProxyFilterer) WatchAdminChanged(opts *bind.WatchOpts, sink chan<- *ERC1967ProxyAdminChanged) (event.Subscription, error) { + + logs, sub, err := _ERC1967Proxy.contract.WatchLogs(opts, "AdminChanged") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ERC1967ProxyAdminChanged) + if err := _ERC1967Proxy.contract.UnpackLog(event, "AdminChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseAdminChanged is a log parse operation binding the contract event 0x7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f. +// +// Solidity: event AdminChanged(address previousAdmin, address newAdmin) +func (_ERC1967Proxy *ERC1967ProxyFilterer) ParseAdminChanged(log types.Log) (*ERC1967ProxyAdminChanged, error) { + event := new(ERC1967ProxyAdminChanged) + if err := _ERC1967Proxy.contract.UnpackLog(event, "AdminChanged", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ERC1967ProxyBeaconUpgradedIterator is returned from FilterBeaconUpgraded and is used to iterate over the raw logs and unpacked data for BeaconUpgraded events raised by the ERC1967Proxy contract. +type ERC1967ProxyBeaconUpgradedIterator struct { + Event *ERC1967ProxyBeaconUpgraded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ERC1967ProxyBeaconUpgradedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ERC1967ProxyBeaconUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ERC1967ProxyBeaconUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ERC1967ProxyBeaconUpgradedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ERC1967ProxyBeaconUpgradedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ERC1967ProxyBeaconUpgraded represents a BeaconUpgraded event raised by the ERC1967Proxy contract. +type ERC1967ProxyBeaconUpgraded struct { + Beacon common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterBeaconUpgraded is a free log retrieval operation binding the contract event 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e. +// +// Solidity: event BeaconUpgraded(address indexed beacon) +func (_ERC1967Proxy *ERC1967ProxyFilterer) FilterBeaconUpgraded(opts *bind.FilterOpts, beacon []common.Address) (*ERC1967ProxyBeaconUpgradedIterator, error) { + + var beaconRule []interface{} + for _, beaconItem := range beacon { + beaconRule = append(beaconRule, beaconItem) + } + + logs, sub, err := _ERC1967Proxy.contract.FilterLogs(opts, "BeaconUpgraded", beaconRule) + if err != nil { + return nil, err + } + return &ERC1967ProxyBeaconUpgradedIterator{contract: _ERC1967Proxy.contract, event: "BeaconUpgraded", logs: logs, sub: sub}, nil +} + +// WatchBeaconUpgraded is a free log subscription operation binding the contract event 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e. +// +// Solidity: event BeaconUpgraded(address indexed beacon) +func (_ERC1967Proxy *ERC1967ProxyFilterer) WatchBeaconUpgraded(opts *bind.WatchOpts, sink chan<- *ERC1967ProxyBeaconUpgraded, beacon []common.Address) (event.Subscription, error) { + + var beaconRule []interface{} + for _, beaconItem := range beacon { + beaconRule = append(beaconRule, beaconItem) + } + + logs, sub, err := _ERC1967Proxy.contract.WatchLogs(opts, "BeaconUpgraded", beaconRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ERC1967ProxyBeaconUpgraded) + if err := _ERC1967Proxy.contract.UnpackLog(event, "BeaconUpgraded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseBeaconUpgraded is a log parse operation binding the contract event 0x1cf3b03a6cf19fa2baba4df148e9dcabedea7f8a5c07840e207e5c089be95d3e. +// +// Solidity: event BeaconUpgraded(address indexed beacon) +func (_ERC1967Proxy *ERC1967ProxyFilterer) ParseBeaconUpgraded(log types.Log) (*ERC1967ProxyBeaconUpgraded, error) { + event := new(ERC1967ProxyBeaconUpgraded) + if err := _ERC1967Proxy.contract.UnpackLog(event, "BeaconUpgraded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// ERC1967ProxyUpgradedIterator is returned from FilterUpgraded and is used to iterate over the raw logs and unpacked data for Upgraded events raised by the ERC1967Proxy contract. +type ERC1967ProxyUpgradedIterator struct { + Event *ERC1967ProxyUpgraded // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ERC1967ProxyUpgradedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ERC1967ProxyUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ERC1967ProxyUpgraded) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ERC1967ProxyUpgradedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ERC1967ProxyUpgradedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ERC1967ProxyUpgraded represents a Upgraded event raised by the ERC1967Proxy contract. +type ERC1967ProxyUpgraded struct { + Implementation common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterUpgraded is a free log retrieval operation binding the contract event 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b. +// +// Solidity: event Upgraded(address indexed implementation) +func (_ERC1967Proxy *ERC1967ProxyFilterer) FilterUpgraded(opts *bind.FilterOpts, implementation []common.Address) (*ERC1967ProxyUpgradedIterator, error) { + + var implementationRule []interface{} + for _, implementationItem := range implementation { + implementationRule = append(implementationRule, implementationItem) + } + + logs, sub, err := _ERC1967Proxy.contract.FilterLogs(opts, "Upgraded", implementationRule) + if err != nil { + return nil, err + } + return &ERC1967ProxyUpgradedIterator{contract: _ERC1967Proxy.contract, event: "Upgraded", logs: logs, sub: sub}, nil +} + +// WatchUpgraded is a free log subscription operation binding the contract event 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b. +// +// Solidity: event Upgraded(address indexed implementation) +func (_ERC1967Proxy *ERC1967ProxyFilterer) WatchUpgraded(opts *bind.WatchOpts, sink chan<- *ERC1967ProxyUpgraded, implementation []common.Address) (event.Subscription, error) { + + var implementationRule []interface{} + for _, implementationItem := range implementation { + implementationRule = append(implementationRule, implementationItem) + } + + logs, sub, err := _ERC1967Proxy.contract.WatchLogs(opts, "Upgraded", implementationRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ERC1967ProxyUpgraded) + if err := _ERC1967Proxy.contract.UnpackLog(event, "Upgraded", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseUpgraded is a log parse operation binding the contract event 0xbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b. +// +// Solidity: event Upgraded(address indexed implementation) +func (_ERC1967Proxy *ERC1967ProxyFilterer) ParseUpgraded(log types.Log) (*ERC1967ProxyUpgraded, error) { + event := new(ERC1967ProxyUpgraded) + if err := _ERC1967Proxy.contract.UnpackLog(event, "Upgraded", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/pkg/contracts/erc1967proxy/bindings.go b/pkg/contracts/erc1967proxy/bindings.go new file mode 100644 index 0000000000..ed88d93cc2 --- /dev/null +++ b/pkg/contracts/erc1967proxy/bindings.go @@ -0,0 +1,3 @@ +//go:generate sh -c "abigen --bin ERC1967Proxy.bin --abi ERC1967Proxy.abi --pkg erc1967proxy --type ERC1967Proxy --out ERC1967Proxy.go" + +package erc1967proxy diff --git a/pkg/contracts/testdappv2/TestDAppV2.abi b/pkg/contracts/testdappv2/TestDAppV2.abi new file mode 100644 index 0000000000..568085dcc8 --- /dev/null +++ b/pkg/contracts/testdappv2/TestDAppV2.abi @@ -0,0 +1,206 @@ +[ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "amountWithMessage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "calledWithMessage", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "erc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "erc20Call", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "gasCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "getAmountWithMessage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "getCalledWithMessage", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "origin", + "type": "bytes" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "chainID", + "type": "uint256" + } + ], + "internalType": "struct TestDAppV2.zContext", + "name": "_context", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + } + ], + "name": "onCrossChainCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "amount", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "revertMessage", + "type": "bytes" + } + ], + "internalType": "struct TestDAppV2.RevertContext", + "name": "revertContext", + "type": "tuple" + } + ], + "name": "onRevert", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "simpleCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/pkg/contracts/testdappv2/TestDAppV2.bin b/pkg/contracts/testdappv2/TestDAppV2.bin new file mode 100644 index 0000000000..2c35399c09 --- /dev/null +++ b/pkg/contracts/testdappv2/TestDAppV2.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50610e3a806100206000396000f3fe60806040526004361061008a5760003560e01c8063a799911f11610059578063a799911f14610162578063c7a339a91461017e578063de43156e146101a7578063e2842ed7146101d0578063f592cbfb1461020d57610091565b806336e980a0146100965780634297a263146100bf578063660b9de0146100fc5780639291fe261461012557610091565b3661009157005b600080fd5b3480156100a257600080fd5b506100bd60048036038101906100b89190610900565b61024a565b005b3480156100cb57600080fd5b506100e660048036038101906100e19190610864565b610274565b6040516100f39190610b35565b60405180910390f35b34801561010857600080fd5b50610123600480360381019061011e9190610949565b61028c565b005b34801561013157600080fd5b5061014c60048036038101906101479190610900565b610347565b6040516101599190610b35565b60405180910390f35b61017c60048036038101906101779190610900565b61038a565b005b34801561018a57600080fd5b506101a560048036038101906101a09190610891565b6103b3565b005b3480156101b357600080fd5b506101ce60048036038101906101c99190610992565b610476565b005b3480156101dc57600080fd5b506101f760048036038101906101f29190610864565b61056f565b6040516102049190610b1a565b60405180910390f35b34801561021957600080fd5b50610234600480360381019061022f9190610900565b61058f565b6040516102419190610b1a565b60405180910390f35b610253816105de565b1561025d57600080fd5b61026681610634565b610271816000610688565b50565b60016020528060005260406000206000915090505481565b6102e781806040019061029f9190610b50565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050610634565b6103448180604001906102fa9190610b50565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506000610688565b50565b6000600160008360405160200161035e9190610ab7565b604051602081830303815290604052805190602001208152602001908152602001600020549050919050565b610393816105de565b1561039d57600080fd5b6103a681610634565b6103b08134610688565b50565b6103bc816105de565b156103c657600080fd5b8273ffffffffffffffffffffffffffffffffffffffff166323b872dd3330856040518463ffffffff1660e01b815260040161040393929190610ae3565b602060405180830381600087803b15801561041d57600080fd5b505af1158015610431573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104559190610837565b61045e57600080fd5b61046781610634565b6104718183610688565b505050565b6104c382828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506105de565b156104cd57600080fd5b61051a82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050610634565b61056882828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505084610688565b5050505050565b60006020528060005260406000206000915054906101000a900460ff1681565b6000806000836040516020016105a59190610ab7565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900460ff169050919050565b60006040516020016105ef90610ace565b60405160208183030381529060405280519060200120826040516020016106169190610ab7565b60405160208183030381529060405280519060200120149050919050565b60016000808360405160200161064a9190610ab7565b60405160208183030381529060405280519060200120815260200190815260200160002060006101000a81548160ff02191690831515021790555050565b80600160008460405160200161069e9190610ab7565b604051602081830303815290604052805190602001208152602001908152602001600020819055505050565b60006106dd6106d884610bd8565b610bb3565b9050828152602081018484840111156106f9576106f8610d48565b5b610704848285610c83565b509392505050565b60008135905061071b81610d91565b92915050565b60008151905061073081610da8565b92915050565b60008135905061074581610dbf565b92915050565b60008083601f84011261076157610760610d2a565b5b8235905067ffffffffffffffff81111561077e5761077d610d25565b5b60208301915083600182028301111561079a57610799610d3e565b5b9250929050565b6000813590506107b081610dd6565b92915050565b600082601f8301126107cb576107ca610d2a565b5b81356107db8482602086016106ca565b91505092915050565b6000606082840312156107fa576107f9610d34565b5b81905092915050565b60006060828403121561081957610818610d34565b5b81905092915050565b60008135905061083181610ded565b92915050565b60006020828403121561084d5761084c610d52565b5b600061085b84828501610721565b91505092915050565b60006020828403121561087a57610879610d52565b5b600061088884828501610736565b91505092915050565b6000806000606084860312156108aa576108a9610d52565b5b60006108b8868287016107a1565b93505060206108c986828701610822565b925050604084013567ffffffffffffffff8111156108ea576108e9610d4d565b5b6108f6868287016107b6565b9150509250925092565b60006020828403121561091657610915610d52565b5b600082013567ffffffffffffffff81111561093457610933610d4d565b5b610940848285016107b6565b91505092915050565b60006020828403121561095f5761095e610d52565b5b600082013567ffffffffffffffff81111561097d5761097c610d4d565b5b610989848285016107e4565b91505092915050565b6000806000806000608086880312156109ae576109ad610d52565b5b600086013567ffffffffffffffff8111156109cc576109cb610d4d565b5b6109d888828901610803565b95505060206109e98882890161070c565b94505060406109fa88828901610822565b935050606086013567ffffffffffffffff811115610a1b57610a1a610d4d565b5b610a278882890161074b565b92509250509295509295909350565b610a3f81610c1f565b82525050565b610a4e81610c31565b82525050565b6000610a5f82610c09565b610a698185610c14565b9350610a79818560208601610c92565b80840191505092915050565b6000610a92600683610c14565b9150610a9d82610d68565b600682019050919050565b610ab181610c79565b82525050565b6000610ac38284610a54565b915081905092915050565b6000610ad982610a85565b9150819050919050565b6000606082019050610af86000830186610a36565b610b056020830185610a36565b610b126040830184610aa8565b949350505050565b6000602082019050610b2f6000830184610a45565b92915050565b6000602082019050610b4a6000830184610aa8565b92915050565b60008083356001602003843603038112610b6d57610b6c610d39565b5b80840192508235915067ffffffffffffffff821115610b8f57610b8e610d2f565b5b602083019250600182023603831315610bab57610baa610d43565b5b509250929050565b6000610bbd610bce565b9050610bc98282610cc5565b919050565b6000604051905090565b600067ffffffffffffffff821115610bf357610bf2610cf6565b5b610bfc82610d57565b9050602081019050919050565b600081519050919050565b600081905092915050565b6000610c2a82610c59565b9050919050565b60008115159050919050565b6000819050919050565b6000610c5282610c1f565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b83811015610cb0578082015181840152602081019050610c95565b83811115610cbf576000848401525b50505050565b610cce82610d57565b810181811067ffffffffffffffff82111715610ced57610cec610cf6565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f7265766572740000000000000000000000000000000000000000000000000000600082015250565b610d9a81610c1f565b8114610da557600080fd5b50565b610db181610c31565b8114610dbc57600080fd5b50565b610dc881610c3d565b8114610dd357600080fd5b50565b610ddf81610c47565b8114610dea57600080fd5b50565b610df681610c79565b8114610e0157600080fd5b5056fea2646970667358221220d6765b67214e8cadf15b569ed63c4c4628bd833acf6926d71b9b6a6011fbb78064736f6c63430008070033 diff --git a/pkg/contracts/testdappv2/TestDAppV2.go b/pkg/contracts/testdappv2/TestDAppV2.go new file mode 100644 index 0000000000..9afe3b5c9d --- /dev/null +++ b/pkg/contracts/testdappv2/TestDAppV2.go @@ -0,0 +1,467 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package testdappv2 + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// TestDAppV2RevertContext is an auto generated low-level Go binding around an user-defined struct. +type TestDAppV2RevertContext struct { + Asset common.Address + Amount uint64 + RevertMessage []byte +} + +// TestDAppV2zContext is an auto generated low-level Go binding around an user-defined struct. +type TestDAppV2zContext struct { + Origin []byte + Sender common.Address + ChainID *big.Int +} + +// TestDAppV2MetaData contains all meta data concerning the TestDAppV2 contract. +var TestDAppV2MetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"amountWithMessage\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"calledWithMessage\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contractIERC20\",\"name\":\"erc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"erc20Call\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"gasCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"getAmountWithMessage\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"getCalledWithMessage\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes\",\"name\":\"origin\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainID\",\"type\":\"uint256\"}],\"internalType\":\"structTestDAppV2.zContext\",\"name\":\"_context\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"_zrc20\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"}],\"name\":\"onCrossChainCall\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"asset\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"amount\",\"type\":\"uint64\"},{\"internalType\":\"bytes\",\"name\":\"revertMessage\",\"type\":\"bytes\"}],\"internalType\":\"structTestDAppV2.RevertContext\",\"name\":\"revertContext\",\"type\":\"tuple\"}],\"name\":\"onRevert\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"simpleCall\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]", + Bin: "0x608060405234801561001057600080fd5b50610e3a806100206000396000f3fe60806040526004361061008a5760003560e01c8063a799911f11610059578063a799911f14610162578063c7a339a91461017e578063de43156e146101a7578063e2842ed7146101d0578063f592cbfb1461020d57610091565b806336e980a0146100965780634297a263146100bf578063660b9de0146100fc5780639291fe261461012557610091565b3661009157005b600080fd5b3480156100a257600080fd5b506100bd60048036038101906100b89190610900565b61024a565b005b3480156100cb57600080fd5b506100e660048036038101906100e19190610864565b610274565b6040516100f39190610b35565b60405180910390f35b34801561010857600080fd5b50610123600480360381019061011e9190610949565b61028c565b005b34801561013157600080fd5b5061014c60048036038101906101479190610900565b610347565b6040516101599190610b35565b60405180910390f35b61017c60048036038101906101779190610900565b61038a565b005b34801561018a57600080fd5b506101a560048036038101906101a09190610891565b6103b3565b005b3480156101b357600080fd5b506101ce60048036038101906101c99190610992565b610476565b005b3480156101dc57600080fd5b506101f760048036038101906101f29190610864565b61056f565b6040516102049190610b1a565b60405180910390f35b34801561021957600080fd5b50610234600480360381019061022f9190610900565b61058f565b6040516102419190610b1a565b60405180910390f35b610253816105de565b1561025d57600080fd5b61026681610634565b610271816000610688565b50565b60016020528060005260406000206000915090505481565b6102e781806040019061029f9190610b50565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050610634565b6103448180604001906102fa9190610b50565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506000610688565b50565b6000600160008360405160200161035e9190610ab7565b604051602081830303815290604052805190602001208152602001908152602001600020549050919050565b610393816105de565b1561039d57600080fd5b6103a681610634565b6103b08134610688565b50565b6103bc816105de565b156103c657600080fd5b8273ffffffffffffffffffffffffffffffffffffffff166323b872dd3330856040518463ffffffff1660e01b815260040161040393929190610ae3565b602060405180830381600087803b15801561041d57600080fd5b505af1158015610431573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104559190610837565b61045e57600080fd5b61046781610634565b6104718183610688565b505050565b6104c382828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506105de565b156104cd57600080fd5b61051a82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050610634565b61056882828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505084610688565b5050505050565b60006020528060005260406000206000915054906101000a900460ff1681565b6000806000836040516020016105a59190610ab7565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900460ff169050919050565b60006040516020016105ef90610ace565b60405160208183030381529060405280519060200120826040516020016106169190610ab7565b60405160208183030381529060405280519060200120149050919050565b60016000808360405160200161064a9190610ab7565b60405160208183030381529060405280519060200120815260200190815260200160002060006101000a81548160ff02191690831515021790555050565b80600160008460405160200161069e9190610ab7565b604051602081830303815290604052805190602001208152602001908152602001600020819055505050565b60006106dd6106d884610bd8565b610bb3565b9050828152602081018484840111156106f9576106f8610d48565b5b610704848285610c83565b509392505050565b60008135905061071b81610d91565b92915050565b60008151905061073081610da8565b92915050565b60008135905061074581610dbf565b92915050565b60008083601f84011261076157610760610d2a565b5b8235905067ffffffffffffffff81111561077e5761077d610d25565b5b60208301915083600182028301111561079a57610799610d3e565b5b9250929050565b6000813590506107b081610dd6565b92915050565b600082601f8301126107cb576107ca610d2a565b5b81356107db8482602086016106ca565b91505092915050565b6000606082840312156107fa576107f9610d34565b5b81905092915050565b60006060828403121561081957610818610d34565b5b81905092915050565b60008135905061083181610ded565b92915050565b60006020828403121561084d5761084c610d52565b5b600061085b84828501610721565b91505092915050565b60006020828403121561087a57610879610d52565b5b600061088884828501610736565b91505092915050565b6000806000606084860312156108aa576108a9610d52565b5b60006108b8868287016107a1565b93505060206108c986828701610822565b925050604084013567ffffffffffffffff8111156108ea576108e9610d4d565b5b6108f6868287016107b6565b9150509250925092565b60006020828403121561091657610915610d52565b5b600082013567ffffffffffffffff81111561093457610933610d4d565b5b610940848285016107b6565b91505092915050565b60006020828403121561095f5761095e610d52565b5b600082013567ffffffffffffffff81111561097d5761097c610d4d565b5b610989848285016107e4565b91505092915050565b6000806000806000608086880312156109ae576109ad610d52565b5b600086013567ffffffffffffffff8111156109cc576109cb610d4d565b5b6109d888828901610803565b95505060206109e98882890161070c565b94505060406109fa88828901610822565b935050606086013567ffffffffffffffff811115610a1b57610a1a610d4d565b5b610a278882890161074b565b92509250509295509295909350565b610a3f81610c1f565b82525050565b610a4e81610c31565b82525050565b6000610a5f82610c09565b610a698185610c14565b9350610a79818560208601610c92565b80840191505092915050565b6000610a92600683610c14565b9150610a9d82610d68565b600682019050919050565b610ab181610c79565b82525050565b6000610ac38284610a54565b915081905092915050565b6000610ad982610a85565b9150819050919050565b6000606082019050610af86000830186610a36565b610b056020830185610a36565b610b126040830184610aa8565b949350505050565b6000602082019050610b2f6000830184610a45565b92915050565b6000602082019050610b4a6000830184610aa8565b92915050565b60008083356001602003843603038112610b6d57610b6c610d39565b5b80840192508235915067ffffffffffffffff821115610b8f57610b8e610d2f565b5b602083019250600182023603831315610bab57610baa610d43565b5b509250929050565b6000610bbd610bce565b9050610bc98282610cc5565b919050565b6000604051905090565b600067ffffffffffffffff821115610bf357610bf2610cf6565b5b610bfc82610d57565b9050602081019050919050565b600081519050919050565b600081905092915050565b6000610c2a82610c59565b9050919050565b60008115159050919050565b6000819050919050565b6000610c5282610c1f565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b83811015610cb0578082015181840152602081019050610c95565b83811115610cbf576000848401525b50505050565b610cce82610d57565b810181811067ffffffffffffffff82111715610ced57610cec610cf6565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f7265766572740000000000000000000000000000000000000000000000000000600082015250565b610d9a81610c1f565b8114610da557600080fd5b50565b610db181610c31565b8114610dbc57600080fd5b50565b610dc881610c3d565b8114610dd357600080fd5b50565b610ddf81610c47565b8114610dea57600080fd5b50565b610df681610c79565b8114610e0157600080fd5b5056fea2646970667358221220d6765b67214e8cadf15b569ed63c4c4628bd833acf6926d71b9b6a6011fbb78064736f6c63430008070033", +} + +// TestDAppV2ABI is the input ABI used to generate the binding from. +// Deprecated: Use TestDAppV2MetaData.ABI instead. +var TestDAppV2ABI = TestDAppV2MetaData.ABI + +// TestDAppV2Bin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use TestDAppV2MetaData.Bin instead. +var TestDAppV2Bin = TestDAppV2MetaData.Bin + +// DeployTestDAppV2 deploys a new Ethereum contract, binding an instance of TestDAppV2 to it. +func DeployTestDAppV2(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *TestDAppV2, error) { + parsed, err := TestDAppV2MetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(TestDAppV2Bin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &TestDAppV2{TestDAppV2Caller: TestDAppV2Caller{contract: contract}, TestDAppV2Transactor: TestDAppV2Transactor{contract: contract}, TestDAppV2Filterer: TestDAppV2Filterer{contract: contract}}, nil +} + +// TestDAppV2 is an auto generated Go binding around an Ethereum contract. +type TestDAppV2 struct { + TestDAppV2Caller // Read-only binding to the contract + TestDAppV2Transactor // Write-only binding to the contract + TestDAppV2Filterer // Log filterer for contract events +} + +// TestDAppV2Caller is an auto generated read-only Go binding around an Ethereum contract. +type TestDAppV2Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TestDAppV2Transactor is an auto generated write-only Go binding around an Ethereum contract. +type TestDAppV2Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TestDAppV2Filterer is an auto generated log filtering Go binding around an Ethereum contract events. +type TestDAppV2Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TestDAppV2Session is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type TestDAppV2Session struct { + Contract *TestDAppV2 // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TestDAppV2CallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type TestDAppV2CallerSession struct { + Contract *TestDAppV2Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// TestDAppV2TransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type TestDAppV2TransactorSession struct { + Contract *TestDAppV2Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TestDAppV2Raw is an auto generated low-level Go binding around an Ethereum contract. +type TestDAppV2Raw struct { + Contract *TestDAppV2 // Generic contract binding to access the raw methods on +} + +// TestDAppV2CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type TestDAppV2CallerRaw struct { + Contract *TestDAppV2Caller // Generic read-only contract binding to access the raw methods on +} + +// TestDAppV2TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type TestDAppV2TransactorRaw struct { + Contract *TestDAppV2Transactor // Generic write-only contract binding to access the raw methods on +} + +// NewTestDAppV2 creates a new instance of TestDAppV2, bound to a specific deployed contract. +func NewTestDAppV2(address common.Address, backend bind.ContractBackend) (*TestDAppV2, error) { + contract, err := bindTestDAppV2(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &TestDAppV2{TestDAppV2Caller: TestDAppV2Caller{contract: contract}, TestDAppV2Transactor: TestDAppV2Transactor{contract: contract}, TestDAppV2Filterer: TestDAppV2Filterer{contract: contract}}, nil +} + +// NewTestDAppV2Caller creates a new read-only instance of TestDAppV2, bound to a specific deployed contract. +func NewTestDAppV2Caller(address common.Address, caller bind.ContractCaller) (*TestDAppV2Caller, error) { + contract, err := bindTestDAppV2(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &TestDAppV2Caller{contract: contract}, nil +} + +// NewTestDAppV2Transactor creates a new write-only instance of TestDAppV2, bound to a specific deployed contract. +func NewTestDAppV2Transactor(address common.Address, transactor bind.ContractTransactor) (*TestDAppV2Transactor, error) { + contract, err := bindTestDAppV2(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &TestDAppV2Transactor{contract: contract}, nil +} + +// NewTestDAppV2Filterer creates a new log filterer instance of TestDAppV2, bound to a specific deployed contract. +func NewTestDAppV2Filterer(address common.Address, filterer bind.ContractFilterer) (*TestDAppV2Filterer, error) { + contract, err := bindTestDAppV2(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &TestDAppV2Filterer{contract: contract}, nil +} + +// bindTestDAppV2 binds a generic wrapper to an already deployed contract. +func bindTestDAppV2(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := TestDAppV2MetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_TestDAppV2 *TestDAppV2Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TestDAppV2.Contract.TestDAppV2Caller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_TestDAppV2 *TestDAppV2Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TestDAppV2.Contract.TestDAppV2Transactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TestDAppV2 *TestDAppV2Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TestDAppV2.Contract.TestDAppV2Transactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_TestDAppV2 *TestDAppV2CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _TestDAppV2.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_TestDAppV2 *TestDAppV2TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TestDAppV2.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_TestDAppV2 *TestDAppV2TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _TestDAppV2.Contract.contract.Transact(opts, method, params...) +} + +// AmountWithMessage is a free data retrieval call binding the contract method 0x4297a263. +// +// Solidity: function amountWithMessage(bytes32 ) view returns(uint256) +func (_TestDAppV2 *TestDAppV2Caller) AmountWithMessage(opts *bind.CallOpts, arg0 [32]byte) (*big.Int, error) { + var out []interface{} + err := _TestDAppV2.contract.Call(opts, &out, "amountWithMessage", arg0) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// AmountWithMessage is a free data retrieval call binding the contract method 0x4297a263. +// +// Solidity: function amountWithMessage(bytes32 ) view returns(uint256) +func (_TestDAppV2 *TestDAppV2Session) AmountWithMessage(arg0 [32]byte) (*big.Int, error) { + return _TestDAppV2.Contract.AmountWithMessage(&_TestDAppV2.CallOpts, arg0) +} + +// AmountWithMessage is a free data retrieval call binding the contract method 0x4297a263. +// +// Solidity: function amountWithMessage(bytes32 ) view returns(uint256) +func (_TestDAppV2 *TestDAppV2CallerSession) AmountWithMessage(arg0 [32]byte) (*big.Int, error) { + return _TestDAppV2.Contract.AmountWithMessage(&_TestDAppV2.CallOpts, arg0) +} + +// CalledWithMessage is a free data retrieval call binding the contract method 0xe2842ed7. +// +// Solidity: function calledWithMessage(bytes32 ) view returns(bool) +func (_TestDAppV2 *TestDAppV2Caller) CalledWithMessage(opts *bind.CallOpts, arg0 [32]byte) (bool, error) { + var out []interface{} + err := _TestDAppV2.contract.Call(opts, &out, "calledWithMessage", arg0) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// CalledWithMessage is a free data retrieval call binding the contract method 0xe2842ed7. +// +// Solidity: function calledWithMessage(bytes32 ) view returns(bool) +func (_TestDAppV2 *TestDAppV2Session) CalledWithMessage(arg0 [32]byte) (bool, error) { + return _TestDAppV2.Contract.CalledWithMessage(&_TestDAppV2.CallOpts, arg0) +} + +// CalledWithMessage is a free data retrieval call binding the contract method 0xe2842ed7. +// +// Solidity: function calledWithMessage(bytes32 ) view returns(bool) +func (_TestDAppV2 *TestDAppV2CallerSession) CalledWithMessage(arg0 [32]byte) (bool, error) { + return _TestDAppV2.Contract.CalledWithMessage(&_TestDAppV2.CallOpts, arg0) +} + +// GetAmountWithMessage is a free data retrieval call binding the contract method 0x9291fe26. +// +// Solidity: function getAmountWithMessage(string message) view returns(uint256) +func (_TestDAppV2 *TestDAppV2Caller) GetAmountWithMessage(opts *bind.CallOpts, message string) (*big.Int, error) { + var out []interface{} + err := _TestDAppV2.contract.Call(opts, &out, "getAmountWithMessage", message) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetAmountWithMessage is a free data retrieval call binding the contract method 0x9291fe26. +// +// Solidity: function getAmountWithMessage(string message) view returns(uint256) +func (_TestDAppV2 *TestDAppV2Session) GetAmountWithMessage(message string) (*big.Int, error) { + return _TestDAppV2.Contract.GetAmountWithMessage(&_TestDAppV2.CallOpts, message) +} + +// GetAmountWithMessage is a free data retrieval call binding the contract method 0x9291fe26. +// +// Solidity: function getAmountWithMessage(string message) view returns(uint256) +func (_TestDAppV2 *TestDAppV2CallerSession) GetAmountWithMessage(message string) (*big.Int, error) { + return _TestDAppV2.Contract.GetAmountWithMessage(&_TestDAppV2.CallOpts, message) +} + +// GetCalledWithMessage is a free data retrieval call binding the contract method 0xf592cbfb. +// +// Solidity: function getCalledWithMessage(string message) view returns(bool) +func (_TestDAppV2 *TestDAppV2Caller) GetCalledWithMessage(opts *bind.CallOpts, message string) (bool, error) { + var out []interface{} + err := _TestDAppV2.contract.Call(opts, &out, "getCalledWithMessage", message) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// GetCalledWithMessage is a free data retrieval call binding the contract method 0xf592cbfb. +// +// Solidity: function getCalledWithMessage(string message) view returns(bool) +func (_TestDAppV2 *TestDAppV2Session) GetCalledWithMessage(message string) (bool, error) { + return _TestDAppV2.Contract.GetCalledWithMessage(&_TestDAppV2.CallOpts, message) +} + +// GetCalledWithMessage is a free data retrieval call binding the contract method 0xf592cbfb. +// +// Solidity: function getCalledWithMessage(string message) view returns(bool) +func (_TestDAppV2 *TestDAppV2CallerSession) GetCalledWithMessage(message string) (bool, error) { + return _TestDAppV2.Contract.GetCalledWithMessage(&_TestDAppV2.CallOpts, message) +} + +// Erc20Call is a paid mutator transaction binding the contract method 0xc7a339a9. +// +// Solidity: function erc20Call(address erc20, uint256 amount, string message) returns() +func (_TestDAppV2 *TestDAppV2Transactor) Erc20Call(opts *bind.TransactOpts, erc20 common.Address, amount *big.Int, message string) (*types.Transaction, error) { + return _TestDAppV2.contract.Transact(opts, "erc20Call", erc20, amount, message) +} + +// Erc20Call is a paid mutator transaction binding the contract method 0xc7a339a9. +// +// Solidity: function erc20Call(address erc20, uint256 amount, string message) returns() +func (_TestDAppV2 *TestDAppV2Session) Erc20Call(erc20 common.Address, amount *big.Int, message string) (*types.Transaction, error) { + return _TestDAppV2.Contract.Erc20Call(&_TestDAppV2.TransactOpts, erc20, amount, message) +} + +// Erc20Call is a paid mutator transaction binding the contract method 0xc7a339a9. +// +// Solidity: function erc20Call(address erc20, uint256 amount, string message) returns() +func (_TestDAppV2 *TestDAppV2TransactorSession) Erc20Call(erc20 common.Address, amount *big.Int, message string) (*types.Transaction, error) { + return _TestDAppV2.Contract.Erc20Call(&_TestDAppV2.TransactOpts, erc20, amount, message) +} + +// GasCall is a paid mutator transaction binding the contract method 0xa799911f. +// +// Solidity: function gasCall(string message) payable returns() +func (_TestDAppV2 *TestDAppV2Transactor) GasCall(opts *bind.TransactOpts, message string) (*types.Transaction, error) { + return _TestDAppV2.contract.Transact(opts, "gasCall", message) +} + +// GasCall is a paid mutator transaction binding the contract method 0xa799911f. +// +// Solidity: function gasCall(string message) payable returns() +func (_TestDAppV2 *TestDAppV2Session) GasCall(message string) (*types.Transaction, error) { + return _TestDAppV2.Contract.GasCall(&_TestDAppV2.TransactOpts, message) +} + +// GasCall is a paid mutator transaction binding the contract method 0xa799911f. +// +// Solidity: function gasCall(string message) payable returns() +func (_TestDAppV2 *TestDAppV2TransactorSession) GasCall(message string) (*types.Transaction, error) { + return _TestDAppV2.Contract.GasCall(&_TestDAppV2.TransactOpts, message) +} + +// OnCrossChainCall is a paid mutator transaction binding the contract method 0xde43156e. +// +// Solidity: function onCrossChainCall((bytes,address,uint256) _context, address _zrc20, uint256 amount, bytes message) returns() +func (_TestDAppV2 *TestDAppV2Transactor) OnCrossChainCall(opts *bind.TransactOpts, _context TestDAppV2zContext, _zrc20 common.Address, amount *big.Int, message []byte) (*types.Transaction, error) { + return _TestDAppV2.contract.Transact(opts, "onCrossChainCall", _context, _zrc20, amount, message) +} + +// OnCrossChainCall is a paid mutator transaction binding the contract method 0xde43156e. +// +// Solidity: function onCrossChainCall((bytes,address,uint256) _context, address _zrc20, uint256 amount, bytes message) returns() +func (_TestDAppV2 *TestDAppV2Session) OnCrossChainCall(_context TestDAppV2zContext, _zrc20 common.Address, amount *big.Int, message []byte) (*types.Transaction, error) { + return _TestDAppV2.Contract.OnCrossChainCall(&_TestDAppV2.TransactOpts, _context, _zrc20, amount, message) +} + +// OnCrossChainCall is a paid mutator transaction binding the contract method 0xde43156e. +// +// Solidity: function onCrossChainCall((bytes,address,uint256) _context, address _zrc20, uint256 amount, bytes message) returns() +func (_TestDAppV2 *TestDAppV2TransactorSession) OnCrossChainCall(_context TestDAppV2zContext, _zrc20 common.Address, amount *big.Int, message []byte) (*types.Transaction, error) { + return _TestDAppV2.Contract.OnCrossChainCall(&_TestDAppV2.TransactOpts, _context, _zrc20, amount, message) +} + +// OnRevert is a paid mutator transaction binding the contract method 0x660b9de0. +// +// Solidity: function onRevert((address,uint64,bytes) revertContext) returns() +func (_TestDAppV2 *TestDAppV2Transactor) OnRevert(opts *bind.TransactOpts, revertContext TestDAppV2RevertContext) (*types.Transaction, error) { + return _TestDAppV2.contract.Transact(opts, "onRevert", revertContext) +} + +// OnRevert is a paid mutator transaction binding the contract method 0x660b9de0. +// +// Solidity: function onRevert((address,uint64,bytes) revertContext) returns() +func (_TestDAppV2 *TestDAppV2Session) OnRevert(revertContext TestDAppV2RevertContext) (*types.Transaction, error) { + return _TestDAppV2.Contract.OnRevert(&_TestDAppV2.TransactOpts, revertContext) +} + +// OnRevert is a paid mutator transaction binding the contract method 0x660b9de0. +// +// Solidity: function onRevert((address,uint64,bytes) revertContext) returns() +func (_TestDAppV2 *TestDAppV2TransactorSession) OnRevert(revertContext TestDAppV2RevertContext) (*types.Transaction, error) { + return _TestDAppV2.Contract.OnRevert(&_TestDAppV2.TransactOpts, revertContext) +} + +// SimpleCall is a paid mutator transaction binding the contract method 0x36e980a0. +// +// Solidity: function simpleCall(string message) returns() +func (_TestDAppV2 *TestDAppV2Transactor) SimpleCall(opts *bind.TransactOpts, message string) (*types.Transaction, error) { + return _TestDAppV2.contract.Transact(opts, "simpleCall", message) +} + +// SimpleCall is a paid mutator transaction binding the contract method 0x36e980a0. +// +// Solidity: function simpleCall(string message) returns() +func (_TestDAppV2 *TestDAppV2Session) SimpleCall(message string) (*types.Transaction, error) { + return _TestDAppV2.Contract.SimpleCall(&_TestDAppV2.TransactOpts, message) +} + +// SimpleCall is a paid mutator transaction binding the contract method 0x36e980a0. +// +// Solidity: function simpleCall(string message) returns() +func (_TestDAppV2 *TestDAppV2TransactorSession) SimpleCall(message string) (*types.Transaction, error) { + return _TestDAppV2.Contract.SimpleCall(&_TestDAppV2.TransactOpts, message) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_TestDAppV2 *TestDAppV2Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _TestDAppV2.contract.RawTransact(opts, nil) // calldata is disallowed for receive function +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_TestDAppV2 *TestDAppV2Session) Receive() (*types.Transaction, error) { + return _TestDAppV2.Contract.Receive(&_TestDAppV2.TransactOpts) +} + +// Receive is a paid mutator transaction binding the contract receive function. +// +// Solidity: receive() payable returns() +func (_TestDAppV2 *TestDAppV2TransactorSession) Receive() (*types.Transaction, error) { + return _TestDAppV2.Contract.Receive(&_TestDAppV2.TransactOpts) +} diff --git a/pkg/contracts/testdappv2/TestDAppV2.json b/pkg/contracts/testdappv2/TestDAppV2.json new file mode 100644 index 0000000000..bcac60c189 --- /dev/null +++ b/pkg/contracts/testdappv2/TestDAppV2.json @@ -0,0 +1,209 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "amountWithMessage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "calledWithMessage", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "erc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "erc20Call", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "gasCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "getAmountWithMessage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "getCalledWithMessage", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "origin", + "type": "bytes" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "chainID", + "type": "uint256" + } + ], + "internalType": "struct TestDAppV2.zContext", + "name": "_context", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_zrc20", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "message", + "type": "bytes" + } + ], + "name": "onCrossChainCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint64", + "name": "amount", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "revertMessage", + "type": "bytes" + } + ], + "internalType": "struct TestDAppV2.RevertContext", + "name": "revertContext", + "type": "tuple" + } + ], + "name": "onRevert", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "simpleCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "bin": "608060405234801561001057600080fd5b50610e3a806100206000396000f3fe60806040526004361061008a5760003560e01c8063a799911f11610059578063a799911f14610162578063c7a339a91461017e578063de43156e146101a7578063e2842ed7146101d0578063f592cbfb1461020d57610091565b806336e980a0146100965780634297a263146100bf578063660b9de0146100fc5780639291fe261461012557610091565b3661009157005b600080fd5b3480156100a257600080fd5b506100bd60048036038101906100b89190610900565b61024a565b005b3480156100cb57600080fd5b506100e660048036038101906100e19190610864565b610274565b6040516100f39190610b35565b60405180910390f35b34801561010857600080fd5b50610123600480360381019061011e9190610949565b61028c565b005b34801561013157600080fd5b5061014c60048036038101906101479190610900565b610347565b6040516101599190610b35565b60405180910390f35b61017c60048036038101906101779190610900565b61038a565b005b34801561018a57600080fd5b506101a560048036038101906101a09190610891565b6103b3565b005b3480156101b357600080fd5b506101ce60048036038101906101c99190610992565b610476565b005b3480156101dc57600080fd5b506101f760048036038101906101f29190610864565b61056f565b6040516102049190610b1a565b60405180910390f35b34801561021957600080fd5b50610234600480360381019061022f9190610900565b61058f565b6040516102419190610b1a565b60405180910390f35b610253816105de565b1561025d57600080fd5b61026681610634565b610271816000610688565b50565b60016020528060005260406000206000915090505481565b6102e781806040019061029f9190610b50565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050610634565b6103448180604001906102fa9190610b50565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506000610688565b50565b6000600160008360405160200161035e9190610ab7565b604051602081830303815290604052805190602001208152602001908152602001600020549050919050565b610393816105de565b1561039d57600080fd5b6103a681610634565b6103b08134610688565b50565b6103bc816105de565b156103c657600080fd5b8273ffffffffffffffffffffffffffffffffffffffff166323b872dd3330856040518463ffffffff1660e01b815260040161040393929190610ae3565b602060405180830381600087803b15801561041d57600080fd5b505af1158015610431573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104559190610837565b61045e57600080fd5b61046781610634565b6104718183610688565b505050565b6104c382828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506105de565b156104cd57600080fd5b61051a82828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050610634565b61056882828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505084610688565b5050505050565b60006020528060005260406000206000915054906101000a900460ff1681565b6000806000836040516020016105a59190610ab7565b60405160208183030381529060405280519060200120815260200190815260200160002060009054906101000a900460ff169050919050565b60006040516020016105ef90610ace565b60405160208183030381529060405280519060200120826040516020016106169190610ab7565b60405160208183030381529060405280519060200120149050919050565b60016000808360405160200161064a9190610ab7565b60405160208183030381529060405280519060200120815260200190815260200160002060006101000a81548160ff02191690831515021790555050565b80600160008460405160200161069e9190610ab7565b604051602081830303815290604052805190602001208152602001908152602001600020819055505050565b60006106dd6106d884610bd8565b610bb3565b9050828152602081018484840111156106f9576106f8610d48565b5b610704848285610c83565b509392505050565b60008135905061071b81610d91565b92915050565b60008151905061073081610da8565b92915050565b60008135905061074581610dbf565b92915050565b60008083601f84011261076157610760610d2a565b5b8235905067ffffffffffffffff81111561077e5761077d610d25565b5b60208301915083600182028301111561079a57610799610d3e565b5b9250929050565b6000813590506107b081610dd6565b92915050565b600082601f8301126107cb576107ca610d2a565b5b81356107db8482602086016106ca565b91505092915050565b6000606082840312156107fa576107f9610d34565b5b81905092915050565b60006060828403121561081957610818610d34565b5b81905092915050565b60008135905061083181610ded565b92915050565b60006020828403121561084d5761084c610d52565b5b600061085b84828501610721565b91505092915050565b60006020828403121561087a57610879610d52565b5b600061088884828501610736565b91505092915050565b6000806000606084860312156108aa576108a9610d52565b5b60006108b8868287016107a1565b93505060206108c986828701610822565b925050604084013567ffffffffffffffff8111156108ea576108e9610d4d565b5b6108f6868287016107b6565b9150509250925092565b60006020828403121561091657610915610d52565b5b600082013567ffffffffffffffff81111561093457610933610d4d565b5b610940848285016107b6565b91505092915050565b60006020828403121561095f5761095e610d52565b5b600082013567ffffffffffffffff81111561097d5761097c610d4d565b5b610989848285016107e4565b91505092915050565b6000806000806000608086880312156109ae576109ad610d52565b5b600086013567ffffffffffffffff8111156109cc576109cb610d4d565b5b6109d888828901610803565b95505060206109e98882890161070c565b94505060406109fa88828901610822565b935050606086013567ffffffffffffffff811115610a1b57610a1a610d4d565b5b610a278882890161074b565b92509250509295509295909350565b610a3f81610c1f565b82525050565b610a4e81610c31565b82525050565b6000610a5f82610c09565b610a698185610c14565b9350610a79818560208601610c92565b80840191505092915050565b6000610a92600683610c14565b9150610a9d82610d68565b600682019050919050565b610ab181610c79565b82525050565b6000610ac38284610a54565b915081905092915050565b6000610ad982610a85565b9150819050919050565b6000606082019050610af86000830186610a36565b610b056020830185610a36565b610b126040830184610aa8565b949350505050565b6000602082019050610b2f6000830184610a45565b92915050565b6000602082019050610b4a6000830184610aa8565b92915050565b60008083356001602003843603038112610b6d57610b6c610d39565b5b80840192508235915067ffffffffffffffff821115610b8f57610b8e610d2f565b5b602083019250600182023603831315610bab57610baa610d43565b5b509250929050565b6000610bbd610bce565b9050610bc98282610cc5565b919050565b6000604051905090565b600067ffffffffffffffff821115610bf357610bf2610cf6565b5b610bfc82610d57565b9050602081019050919050565b600081519050919050565b600081905092915050565b6000610c2a82610c59565b9050919050565b60008115159050919050565b6000819050919050565b6000610c5282610c1f565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b82818337600083830152505050565b60005b83811015610cb0578082015181840152602081019050610c95565b83811115610cbf576000848401525b50505050565b610cce82610d57565b810181811067ffffffffffffffff82111715610ced57610cec610cf6565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f7265766572740000000000000000000000000000000000000000000000000000600082015250565b610d9a81610c1f565b8114610da557600080fd5b50565b610db181610c31565b8114610dbc57600080fd5b50565b610dc881610c3d565b8114610dd357600080fd5b50565b610ddf81610c47565b8114610dea57600080fd5b50565b610df681610c79565b8114610e0157600080fd5b5056fea2646970667358221220d6765b67214e8cadf15b569ed63c4c4628bd833acf6926d71b9b6a6011fbb78064736f6c63430008070033" +} diff --git a/pkg/contracts/testdappv2/TestDAppV2.sol b/pkg/contracts/testdappv2/TestDAppV2.sol new file mode 100644 index 0000000000..4ab39a05a0 --- /dev/null +++ b/pkg/contracts/testdappv2/TestDAppV2.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +interface IERC20 { + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); +} + +contract TestDAppV2 { + struct zContext { + bytes origin; + address sender; + uint256 chainID; + } + + /// @notice Struct containing revert context passed to onRevert. + /// @param asset Address of asset, empty if it's gas token. + /// @param amount Amount specified with the transaction. + /// @param revertMessage Arbitrary data sent back in onRevert. + struct RevertContext { + address asset; + uint64 amount; + bytes revertMessage; + } + + // these structures allow to assess contract calls + mapping(bytes32 => bool) public calledWithMessage; + mapping(bytes32 => uint256) public amountWithMessage; + + function setCalledWithMessage(string memory message) internal { + calledWithMessage[keccak256(abi.encodePacked(message))] = true; + } + function setAmountWithMessage(string memory message, uint256 amount) internal { + amountWithMessage[keccak256(abi.encodePacked(message))] = amount; + } + + function getCalledWithMessage(string memory message) public view returns (bool) { + return calledWithMessage[keccak256(abi.encodePacked(message))]; + } + + function getAmountWithMessage(string memory message) public view returns (uint256) { + return amountWithMessage[keccak256(abi.encodePacked(message))]; + } + + // Universal contract interface + function onCrossChainCall( + zContext calldata _context, + address _zrc20, + uint256 amount, + bytes calldata message + ) + external + { + require(!isRevertMessage(string(message))); + + setCalledWithMessage(string(message)); + setAmountWithMessage(string(message), amount); + } + + // called with gas token + function gasCall(string memory message) external payable { + // Revert if the message is "revert" + require(!isRevertMessage(message)); + + setCalledWithMessage(message); + setAmountWithMessage(message, msg.value); + } + + // called with ERC20 token + function erc20Call(IERC20 erc20, uint256 amount, string memory message) external { + require(!isRevertMessage(message)); + require(erc20.transferFrom(msg.sender, address(this), amount)); + + setCalledWithMessage(message); + setAmountWithMessage(message, amount); + } + + // called without token + function simpleCall(string memory message) external { + require(!isRevertMessage(message)); + + setCalledWithMessage(message); + setAmountWithMessage(message, 0); + } + + // used to make functions revert + function isRevertMessage(string memory message) internal pure returns (bool) { + return keccak256(abi.encodePacked(message)) == keccak256(abi.encodePacked("revert")); + } + + // Revertable interface + function onRevert(RevertContext calldata revertContext) external { + setCalledWithMessage(string(revertContext.revertMessage)); + setAmountWithMessage(string(revertContext.revertMessage), 0); + } + + receive() external payable {} +} \ No newline at end of file diff --git a/pkg/contracts/testdappv2/bindings.go b/pkg/contracts/testdappv2/bindings.go new file mode 100644 index 0000000000..73843ba2d2 --- /dev/null +++ b/pkg/contracts/testdappv2/bindings.go @@ -0,0 +1,8 @@ +//go:generate sh -c "solc TestDAppV2.sol --combined-json abi,bin | jq '.contracts.\"TestDAppV2.sol:TestDAppV2\"' > TestDAppV2.json" +//go:generate sh -c "cat TestDAppV2.json | jq .abi > TestDAppV2.abi" +//go:generate sh -c "cat TestDAppV2.json | jq .bin | tr -d '\"' > TestDAppV2.bin" +//go:generate sh -c "abigen --abi TestDAppV2.abi --bin TestDAppV2.bin --pkg testdappv2 --type TestDAppV2 --out TestDAppV2.go" + +package testdappv2 + +var _ TestDAppV2 diff --git a/pkg/crypto/address.go b/pkg/crypto/address.go new file mode 100644 index 0000000000..4d0e9bc5a6 --- /dev/null +++ b/pkg/crypto/address.go @@ -0,0 +1,12 @@ +package crypto + +import ( + "github.com/ethereum/go-ethereum/common" + + "github.com/zeta-chain/zetacore/pkg/constant" +) + +// IsEmptyAddress returns true if the address is empty +func IsEmptyAddress(address common.Address) bool { + return address == (common.Address{}) || address.Hex() == constant.EVMZeroAddress +} diff --git a/pkg/crypto/address_test.go b/pkg/crypto/address_test.go new file mode 100644 index 0000000000..162481730d --- /dev/null +++ b/pkg/crypto/address_test.go @@ -0,0 +1,37 @@ +package crypto + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/constant" + "testing" +) + +func TestIsEmptyAddress(t *testing.T) { + tests := []struct { + name string + address common.Address + want bool + }{ + { + name: "empty address", + address: common.Address{}, + want: true, + }, + { + name: "zero address", + address: common.HexToAddress(constant.EVMZeroAddress), + want: true, + }, + { + name: "non empty address", + address: common.HexToAddress("0x1"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.EqualValues(t, tt.want, IsEmptyAddress(tt.address)) + }) + } +} diff --git a/pkg/crypto/aes256_gcm.go b/pkg/crypto/aes256_gcm.go new file mode 100644 index 0000000000..e4fba7de7c --- /dev/null +++ b/pkg/crypto/aes256_gcm.go @@ -0,0 +1,120 @@ +package crypto + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + io "io" + + "github.com/pkg/errors" +) + +// EncryptAES256GCMBase64 encrypts the given string plaintext using AES-256-GCM with the given password and returns the base64-encoded ciphertext. +func EncryptAES256GCMBase64(plaintext string, password string) (string, error) { + // validate the input + if plaintext == "" { + return "", errors.New("plaintext must not be empty") + } + if password == "" { + return "", errors.New("password must not be empty") + } + + // encrypt the plaintext + ciphertext, err := EncryptAES256GCM([]byte(plaintext), password) + if err != nil { + return "", errors.Wrap(err, "failed to encrypt string plaintext") + } + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +// DecryptAES256GCMBase64 decrypts the given base64-encoded ciphertext using AES-256-GCM with the given password. +func DecryptAES256GCMBase64(ciphertextBase64 string, password string) (string, error) { + // validate the input + if ciphertextBase64 == "" { + return "", errors.New("ciphertext must not be empty") + } + if password == "" { + return "", errors.New("password must not be empty") + } + + // decode the base64-encoded ciphertext + ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64) + if err != nil { + return "", errors.Wrap(err, "failed to decode base64 ciphertext") + } + + // decrypt the ciphertext + plaintext, err := DecryptAES256GCM(ciphertext, password) + if err != nil { + return "", errors.Wrap(err, "failed to decrypt ciphertext") + } + return string(plaintext), nil +} + +// EncryptAES256GCM encrypts the given plaintext using AES-256-GCM with the given password. +func EncryptAES256GCM(plaintext []byte, password string) ([]byte, error) { + // create AES cipher + block, err := aes.NewCipher(getAESKey(password)) + if err != nil { + return nil, err + } + + // create GCM mode + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + // generate random nonce + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + + // encrypt the plaintext + ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) + + return ciphertext, nil +} + +// DecryptAES256GCM decrypts the given ciphertext using AES-256-GCM with the given password. +func DecryptAES256GCM(ciphertext []byte, password string) ([]byte, error) { + // create AES cipher + block, err := aes.NewCipher(getAESKey(password)) + if err != nil { + return nil, err + } + + // create GCM mode + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + // get the nonce size + nonceSize := gcm.NonceSize() + if len(ciphertext) < nonceSize { + return nil, errors.New("ciphertext too short") + } + + // extract the nonce from the ciphertext + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + + // decrypt the ciphertext + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + + return plaintext, nil +} + +// getAESKey uses SHA-256 to create a 32-byte key for AES encryption. +func getAESKey(key string) []byte { + h := sha256.New() + h.Write([]byte(key)) + + return h.Sum(nil) +} diff --git a/pkg/crypto/aes256_gcm_test.go b/pkg/crypto/aes256_gcm_test.go new file mode 100644 index 0000000000..92c1e4f1f0 --- /dev/null +++ b/pkg/crypto/aes256_gcm_test.go @@ -0,0 +1,203 @@ +package crypto_test + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/crypto" +) + +func Test_EncryptDecryptAES256GCM(t *testing.T) { + tests := []struct { + name string + plaintext string + encryptPass string + decryptPass string + modifyFunc func([]byte) []byte + fail bool + errMsg string + }{ + { + name: "Successful encryption and decryption", + plaintext: "Hello, World!", + encryptPass: "my_password", + decryptPass: "my_password", + fail: false, + }, + { + name: "Decryption with incorrect key should fail", + plaintext: "Hello, World!", + encryptPass: "my_password", + decryptPass: "my_password2", + fail: true, + }, + { + name: "Decryption with ciphertext too short should fail", + plaintext: "Hello, World!", + encryptPass: "my_password", + decryptPass: "my_password", + modifyFunc: func(ciphertext []byte) []byte { + // truncate the ciphertext, nonce size is 12 bytes + return ciphertext[:10] + }, + fail: true, + errMsg: "ciphertext too short", + }, + { + name: "Decryption with corrupted ciphertext should fail", + plaintext: "Hello, World!", + encryptPass: "my_password", + decryptPass: "my_password", + modifyFunc: func(ciphertext []byte) []byte { + // flip the last bit of the ciphertext + ciphertext[len(ciphertext)-1] ^= 0x01 + return ciphertext + }, + fail: true, + }, + { + name: "Decryption with incorrect nonce should fail", + plaintext: "Hello, World!", + encryptPass: "my_password", + decryptPass: "my_password", + modifyFunc: func(ciphertext []byte) []byte { + // flip the first bit of the nonce + ciphertext[0] ^= 0x01 + return ciphertext + }, + fail: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + encrypted, err := crypto.EncryptAES256GCM([]byte(tt.plaintext), tt.encryptPass) + require.NoError(t, err) + + // modify the encrypted data if needed + if tt.modifyFunc != nil { + encrypted = tt.modifyFunc(encrypted) + } + + // decrypt the data + decrypted, err := crypto.DecryptAES256GCM(encrypted, tt.decryptPass) + if tt.fail { + require.Error(t, err) + if tt.errMsg != "" { + require.Contains(t, err.Error(), tt.errMsg) + } + return + } + + require.True(t, bytes.Equal(decrypted, []byte(tt.plaintext)), "decrypted plaintext does not match") + }) + } +} + +func Test_EncryptAES256GCMBase64(t *testing.T) { + tests := []struct { + name string + plaintext string + encryptPass string + decryptPass string + errorMessage string + }{ + { + name: "Successful encryption and decryption", + plaintext: "Hello, World!", + encryptPass: "my_password", + decryptPass: "my_password", + }, + { + name: "Encryption with empty plaintext should fail", + plaintext: "", + errorMessage: "plaintext must not be empty", + }, + { + name: "Encryption with empty password should fail", + plaintext: "Hello, World!", + encryptPass: "", + errorMessage: "password must not be empty", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // encrypt the data + ciphertextBase64, err := crypto.EncryptAES256GCMBase64(tt.plaintext, tt.encryptPass) + if tt.errorMessage != "" { + require.ErrorContains(t, err, tt.errorMessage) + return + } + + // decrypt the data + decrypted, err := crypto.DecryptAES256GCMBase64(ciphertextBase64, tt.decryptPass) + require.NoError(t, err) + + require.Equal(t, tt.plaintext, decrypted) + }) + } +} + +func Test_DecryptAES256GCMBase64(t *testing.T) { + tests := []struct { + name string + ciphertextBase64 string + plaintext string + decryptKey string + modifyFunc func(string) string + errorMessage string + }{ + { + name: "Successful decryption", + ciphertextBase64: "CXLWgHdVeZQwVOZZyHeZ5n5VB+eVSLaWFF0v0QOm9DyB7XSiHDwhNwQ=", + plaintext: "Hello, World!", + decryptKey: "my_password", + }, + { + name: "Decryption with empty ciphertext should fail", + ciphertextBase64: "", + decryptKey: "my_password", + errorMessage: "ciphertext must not be empty", + }, + { + name: "Decryption with empty password should fail", + ciphertextBase64: "CXLWgHdVeZQwVOZZyHeZ5n5VB+eVSLaWFF0v0QOm9DyB7XSiHDwhNwQ=", + decryptKey: "", + errorMessage: "password must not be empty", + }, + { + name: "Decryption with invalid base64 ciphertext should fail", + ciphertextBase64: "CXLWgHdVeZQwVOZZyHeZ5n5VB*eVSLaWFF0v0QOm9DyB7XSiHDwhNwQ=", // use '*' instead of '+' + decryptKey: "my_password", + errorMessage: "failed to decode base64 ciphertext", + }, + { + name: "Decryption with incorrect password should fail", + ciphertextBase64: "CXLWgHdVeZQwVOZZyHeZ5n5VB+eVSLaWFF0v0QOm9DyB7XSiHDwhNwQ=", + decryptKey: "my_password2", + errorMessage: "failed to decrypt ciphertext", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ciphertextBase64 := tt.ciphertextBase64 + + // modify the encrypted data if needed + if tt.modifyFunc != nil { + ciphertextBase64 = tt.modifyFunc(ciphertextBase64) + } + + // decrypt the data + decrypted, err := crypto.DecryptAES256GCMBase64(ciphertextBase64, tt.decryptKey) + if tt.errorMessage != "" { + require.ErrorContains(t, err, tt.errorMessage) + return + } + + require.Equal(t, tt.plaintext, decrypted) + }) + } +} diff --git a/pkg/crypto/privkey.go b/pkg/crypto/privkey.go new file mode 100644 index 0000000000..2acbf1c609 --- /dev/null +++ b/pkg/crypto/privkey.go @@ -0,0 +1,23 @@ +package crypto + +import ( + fmt "fmt" + + "github.com/gagliardetto/solana-go" + "github.com/pkg/errors" +) + +// SolanaPrivateKeyFromString converts a base58 encoded private key to a solana.PrivateKey +func SolanaPrivateKeyFromString(privKeyBase58 string) (*solana.PrivateKey, error) { + privateKey, err := solana.PrivateKeyFromBase58(privKeyBase58) + if err != nil { + return nil, errors.Wrap(err, "invalid base58 private key") + } + + // Solana private keys are 64 bytes long + if len(privateKey) != 64 { + return nil, fmt.Errorf("invalid private key length: %d", len(privateKey)) + } + + return &privateKey, nil +} diff --git a/pkg/crypto/privkey_test.go b/pkg/crypto/privkey_test.go new file mode 100644 index 0000000000..cf8921b454 --- /dev/null +++ b/pkg/crypto/privkey_test.go @@ -0,0 +1,67 @@ +package crypto_test + +import ( + "testing" + + "github.com/gagliardetto/solana-go" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/crypto" +) + +func Test_SolanaPrivateKeyFromString(t *testing.T) { + tests := []struct { + name string + input string + output *solana.PrivateKey + errMsg string + }{ + { + name: "valid private key", + input: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ", + output: func() *solana.PrivateKey { + privKey, _ := solana.PrivateKeyFromBase58( + "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ", + ) + return &privKey + }(), + }, + { + name: "invalid private key - too short", + input: "oR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ", + output: nil, + errMsg: "invalid private key length: 38", + }, + { + name: "invalid private key - too long", + input: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQdJ", + output: nil, + errMsg: "invalid private key length: 66", + }, + { + name: "invalid private key - bad base58 encoding", + input: "!!!InvalidBase58!!!", + output: nil, + errMsg: "invalid base58 private key", + }, + { + name: "invalid private key - empty string", + input: "", + output: nil, + errMsg: "invalid base58 private key", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := crypto.SolanaPrivateKeyFromString(tt.input) + if tt.errMsg != "" { + require.ErrorContains(t, err, tt.errMsg) + require.Nil(t, result) + return + } + + require.NoError(t, err) + require.Equal(t, tt.output.String(), result.String()) + }) + } +} diff --git a/pkg/os/console.go b/pkg/os/console.go new file mode 100644 index 0000000000..c4a7c505c7 --- /dev/null +++ b/pkg/os/console.go @@ -0,0 +1,47 @@ +package os + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +// PromptPassword prompts the user for a password with the given title +func PromptPassword(passwordTitle string) (string, error) { + reader := bufio.NewReader(os.Stdin) + + return readPassword(reader, passwordTitle) +} + +// PromptPasswords is a convenience function that prompts the user for multiple passwords +func PromptPasswords(passwordTitles []string) ([]string, error) { + reader := bufio.NewReader(os.Stdin) + passwords := make([]string, len(passwordTitles)) + + // iterate over password titles and prompt for each + for i, title := range passwordTitles { + password, err := readPassword(reader, title) + if err != nil { + return nil, err + } + passwords[i] = password + } + + return passwords, nil +} + +// readPassword is a helper function that reads a password from bufio.Reader +func readPassword(reader *bufio.Reader, passwordTitle string) (string, error) { + const delimitor = '\n' + + // prompt for password + fmt.Printf("%s Password: ", passwordTitle) + password, err := reader.ReadString(delimitor) + if err != nil { + return "", err + } + + // trim leading and trailing spaces + return strings.TrimSpace(password), nil +} diff --git a/pkg/os/console_test.go b/pkg/os/console_test.go new file mode 100644 index 0000000000..d5733744d2 --- /dev/null +++ b/pkg/os/console_test.go @@ -0,0 +1,111 @@ +package os_test + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" + zetaos "github.com/zeta-chain/zetacore/pkg/os" +) + +func Test_PromptPassword(t *testing.T) { + tests := []struct { + name string + input string + output string + }{ + { + name: "Valid password", + input: " pass123\n", + output: "pass123", + }, + { + name: "Empty password", + input: "\n", + output: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a pipe to simulate stdin + r, w, err := os.Pipe() + require.NoError(t, err) + + // Write the test input to the pipe + _, err = w.Write([]byte(tt.input)) + require.NoError(t, err) + w.Close() // Close the write end of the pipe + + // Backup the original stdin and restore it after the test + oldStdin := os.Stdin + defer func() { os.Stdin = oldStdin }() + + // Redirect stdin to the read end of the pipe + os.Stdin = r + + // Call the function with the test case data + password, err := zetaos.PromptPassword("anyTitle") + + // Check the returned passwords + require.NoError(t, err) + require.Equal(t, tt.output, password) + }) + } +} + +// Test function for PromptPasswords +func Test_PromptPasswords(t *testing.T) { + tests := []struct { + name string + passwordTitles []string + input string + expected []string + }{ + { + name: "Single password prompt", + passwordTitles: []string{"HotKey"}, + input: " pass123\n", + expected: []string{"pass123"}, + }, + { + name: "Multiple password prompts", + passwordTitles: []string{"HotKey", "TSS", "RelayerKey"}, + input: "pass_hotkey\npass_tss\npass_relayer\n", + expected: []string{"pass_hotkey", "pass_tss", "pass_relayer"}, + }, + { + name: "Empty input for passwords is allowed", + passwordTitles: []string{"HotKey", "TSS", "RelayerKey"}, + input: "\n\n\n", + expected: []string{"", "", ""}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a pipe to simulate stdin + r, w, err := os.Pipe() + require.NoError(t, err) + + // Write the test input to the pipe + _, err = w.Write([]byte(tt.input)) + require.NoError(t, err) + w.Close() // Close the write end of the pipe + + // Backup the original stdin and restore it after the test + oldStdin := os.Stdin + defer func() { os.Stdin = oldStdin }() + + // Redirect stdin to the read end of the pipe + os.Stdin = r + + // Call the function with the test case data + passwords, err := zetaos.PromptPasswords(tt.passwordTitles) + + // Check the returned passwords + require.NoError(t, err) + require.Equal(t, tt.expected, passwords) + }) + } +} diff --git a/pkg/os/path.go b/pkg/os/path.go new file mode 100644 index 0000000000..abf8368c64 --- /dev/null +++ b/pkg/os/path.go @@ -0,0 +1,33 @@ +package os + +import ( + "os" + "os/user" + "path/filepath" + "strings" +) + +// ExpandHomeDir expands a leading tilde in the path to the home directory of the current user. +// ~someuser/tmp will not be expanded. +func ExpandHomeDir(p string) (string, error) { + if p == "~" || + strings.HasPrefix(p, "~/") || + strings.HasPrefix(p, "~\\") { + usr, err := user.Current() + if err != nil { + return p, err + } + + p = filepath.Join(usr.HomeDir, p[1:]) + } + return filepath.Clean(p), nil +} + +// FileExists checks if a file exists. +func FileExists(filePath string) bool { + _, err := os.Stat(filePath) + if os.IsNotExist(err) { + return false + } + return err == nil +} diff --git a/pkg/os/path_test.go b/pkg/os/path_test.go new file mode 100644 index 0000000000..d02c55ef4e --- /dev/null +++ b/pkg/os/path_test.go @@ -0,0 +1,83 @@ +package os_test + +import ( + "os" + "os/user" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + zetaos "github.com/zeta-chain/zetacore/pkg/os" + "github.com/zeta-chain/zetacore/testutil/sample" +) + +func TestResolveHome(t *testing.T) { + usr, err := user.Current() + require.NoError(t, err) + + testCases := []struct { + name string + pathIn string + expected string + fail bool + }{ + { + name: `should resolve home with leading "~/"`, + pathIn: "~/tmp/file.json", + expected: filepath.Clean(filepath.Join(usr.HomeDir, "tmp/file.json")), + }, + { + name: "should resolve '~'", + pathIn: `~`, + expected: filepath.Clean(filepath.Join(usr.HomeDir, "")), + }, + { + name: "should not resolve '~someuser/tmp'", + pathIn: `~someuser/tmp`, + expected: `~someuser/tmp`, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + pathOut, err := zetaos.ExpandHomeDir(tc.pathIn) + require.NoError(t, err) + require.Equal(t, tc.expected, pathOut) + }) + } +} + +func TestFileExists(t *testing.T) { + path := sample.CreateTempDir(t) + + // create a test file + existingFile := filepath.Join(path, "test.txt") + _, err := os.Create(existingFile) + require.NoError(t, err) + + testCases := []struct { + name string + file string + expected bool + }{ + { + name: "should return true for existing file", + file: existingFile, + expected: true, + }, + { + name: "should return false for non-existing file", + file: filepath.Join(path, "non-existing.txt"), + expected: false, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + exists := zetaos.FileExists(tc.file) + require.Equal(t, tc.expected, exists) + }) + } +} diff --git a/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto b/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto index 88ca547482..41028156c6 100644 --- a/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto +++ b/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto @@ -89,6 +89,28 @@ message Status { string status_message = 2; int64 lastUpdate_timestamp = 3; bool isAbortRefunded = 4; + // when the CCTX was created. only populated on new transactions. + int64 created_timestamp = 5; +} + +// ProtocolContractVersion represents the version of the protocol contract used +// for cctx workflow +enum ProtocolContractVersion { + option (gogoproto.goproto_enum_stringer) = true; + V1 = 0; + V2 = 1; +} + +// RevertOptions represents the options for reverting a cctx +message RevertOptions { + string revert_address = 1; + bool call_on_revert = 2; + string abort_address = 3; + bytes revert_message = 4; + string revert_gas_limit = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; } message CrossChainTx { @@ -103,4 +125,6 @@ message CrossChainTx { Status cctx_status = 8; InboundParams inbound_params = 9; repeated OutboundParams outbound_params = 10; + ProtocolContractVersion protocol_contract_version = 11; + RevertOptions revert_options = 12 [ (gogoproto.nullable) = false ]; } diff --git a/proto/zetachain/zetacore/crosschain/events.proto b/proto/zetachain/zetacore/crosschain/events.proto index 3778b3bdce..b39423fff4 100644 --- a/proto/zetachain/zetacore/crosschain/events.proto +++ b/proto/zetachain/zetacore/crosschain/events.proto @@ -68,3 +68,16 @@ message EventERC20Whitelist { string whitelist_cctx_index = 1; string zrc20_address = 2; } + +message EventERC20CustodyFundsMigration { + string new_custody_address = 1; + string erc20_address = 2; + string amount = 3; + string cctx_index = 4; +} + +message EventERC20CustodyPausing { + int64 chain_id = 1; + bool pause = 2; + string cctx_index = 3; +} diff --git a/proto/zetachain/zetacore/crosschain/tx.proto b/proto/zetachain/zetacore/crosschain/tx.proto index 2bff84f733..a34ff32fb9 100644 --- a/proto/zetachain/zetacore/crosschain/tx.proto +++ b/proto/zetachain/zetacore/crosschain/tx.proto @@ -6,6 +6,7 @@ import "zetachain/zetacore/pkg/chains/chains.proto"; import "zetachain/zetacore/pkg/coin/coin.proto"; import "zetachain/zetacore/pkg/proofs/proofs.proto"; import "zetachain/zetacore/crosschain/rate_limiter_flags.proto"; +import "zetachain/zetacore/crosschain/cross_chain_tx.proto"; option go_package = "github.com/zeta-chain/zetacore/x/crosschain/types"; @@ -33,6 +34,12 @@ service Msg { rpc UpdateRateLimiterFlags(MsgUpdateRateLimiterFlags) returns (MsgUpdateRateLimiterFlagsResponse); + + rpc MigrateERC20CustodyFunds(MsgMigrateERC20CustodyFunds) + returns (MsgMigrateERC20CustodyFundsResponse); + + rpc UpdateERC20CustodyPauseStatus(MsgUpdateERC20CustodyPauseStatus) + returns (MsgUpdateERC20CustodyPauseStatusResponse); } message MsgMigrateTssFunds { @@ -162,6 +169,12 @@ message MsgVoteInbound { string asset = 14; // event index of the sent asset in the observed tx uint64 event_index = 15; + + // protocol contract version to use for the cctx workflow + ProtocolContractVersion protocol_contract_version = 16; + + // revert options provided by the sender + RevertOptions revert_options = 17 [ (gogoproto.nullable) = false ]; } message MsgVoteInboundResponse {} @@ -188,3 +201,27 @@ message MsgUpdateRateLimiterFlags { } message MsgUpdateRateLimiterFlagsResponse {} + +message MsgMigrateERC20CustodyFunds { + string creator = 1; + int64 chain_id = 2; + string new_custody_address = 3; + string erc20_address = 4; + string amount = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; +} + +message MsgMigrateERC20CustodyFundsResponse { string cctx_index = 1; } + +message MsgUpdateERC20CustodyPauseStatus { + string creator = 1; + int64 chain_id = 2; + + // pause or unpause + // true = pause, false = unpause + bool pause = 3; +} + +message MsgUpdateERC20CustodyPauseStatusResponse { string cctx_index = 1; } diff --git a/proto/zetachain/zetacore/pkg/coin/coin.proto b/proto/zetachain/zetacore/pkg/coin/coin.proto index 4b43786228..e80fb4abb0 100644 --- a/proto/zetachain/zetacore/pkg/coin/coin.proto +++ b/proto/zetachain/zetacore/pkg/coin/coin.proto @@ -8,7 +8,8 @@ option go_package = "github.com/zeta-chain/zetacore/pkg/coin"; enum CoinType { option (gogoproto.goproto_enum_stringer) = true; Zeta = 0; - Gas = 1; // Ether, BNB, Matic, Klay, BTC, etc - ERC20 = 2; // ERC20 token - Cmd = 3; // not a real coin, rather a command + Gas = 1; // Ether, BNB, Matic, Klay, BTC, etc + ERC20 = 2; // ERC20 token + Cmd = 3; // no asset, used for admin command + NoAssetCall = 4; // no asset, used for contract call } diff --git a/rpc/namespaces/ethereum/debug/api.go b/rpc/namespaces/ethereum/debug/api.go index 9a3942e1de..9608fa7d67 100644 --- a/rpc/namespaces/ethereum/debug/api.go +++ b/rpc/namespaces/ethereum/debug/api.go @@ -37,6 +37,7 @@ import ( stderrors "github.com/pkg/errors" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + zetaos "github.com/zeta-chain/zetacore/pkg/os" "github.com/zeta-chain/zetacore/rpc/backend" rpctypes "github.com/zeta-chain/zetacore/rpc/types" ) @@ -199,7 +200,7 @@ func (a *API) StartCPUProfile(file string) error { a.logger.Debug("CPU profiling already in progress") return errors.New("CPU profiling already in progress") default: - fp, err := ExpandHome(file) + fp, err := zetaos.ExpandHomeDir(file) if err != nil { a.logger.Debug("failed to get filepath for the CPU profile file", "error", err.Error()) return err diff --git a/rpc/namespaces/ethereum/debug/trace.go b/rpc/namespaces/ethereum/debug/trace.go index 28ba1c8043..ae35b16fc2 100644 --- a/rpc/namespaces/ethereum/debug/trace.go +++ b/rpc/namespaces/ethereum/debug/trace.go @@ -25,6 +25,8 @@ import ( "runtime/trace" stderrors "github.com/pkg/errors" + + zetaos "github.com/zeta-chain/zetacore/pkg/os" ) // StartGoTrace turns on tracing, writing to the given file. @@ -37,7 +39,7 @@ func (a *API) StartGoTrace(file string) error { a.logger.Debug("trace already in progress") return errors.New("trace already in progress") } - fp, err := ExpandHome(file) + fp, err := zetaos.ExpandHomeDir(file) if err != nil { a.logger.Debug("failed to get filepath for the CPU profile file", "error", err.Error()) return err diff --git a/rpc/namespaces/ethereum/debug/utils.go b/rpc/namespaces/ethereum/debug/utils.go index 0b041aba02..be318ddf2e 100644 --- a/rpc/namespaces/ethereum/debug/utils.go +++ b/rpc/namespaces/ethereum/debug/utils.go @@ -17,13 +17,12 @@ package debug import ( "os" - "os/user" - "path/filepath" "runtime/pprof" - "strings" "github.com/cometbft/cometbft/libs/log" "github.com/cosmos/cosmos-sdk/server" + + zetaos "github.com/zeta-chain/zetacore/pkg/os" ) // isCPUProfileConfigurationActivated checks if cpuprofile was configured via flag @@ -33,25 +32,11 @@ func isCPUProfileConfigurationActivated(ctx *server.Context) bool { return ctx.Viper.GetString("cpu-profile") != "" } -// ExpandHome expands home directory in file paths. -// ~someuser/tmp will not be expanded. -func ExpandHome(p string) (string, error) { - if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { - usr, err := user.Current() - if err != nil { - return p, err - } - home := usr.HomeDir - p = home + p[1:] - } - return filepath.Clean(p), nil -} - // writeProfile writes the data to a file func writeProfile(name, file string, log log.Logger) error { p := pprof.Lookup(name) log.Info("Writing profile records", "count", p.Count(), "type", name, "dump", file) - fp, err := ExpandHome(file) + fp, err := zetaos.ExpandHomeDir(file) if err != nil { return err } diff --git a/server/start.go b/server/start.go index d1bd3227a1..0301408f91 100644 --- a/server/start.go +++ b/server/start.go @@ -58,7 +58,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" - ethdebug "github.com/zeta-chain/zetacore/rpc/namespaces/ethereum/debug" + zetaos "github.com/zeta-chain/zetacore/pkg/os" "github.com/zeta-chain/zetacore/server/config" srvflags "github.com/zeta-chain/zetacore/server/flags" ) @@ -337,7 +337,7 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, opts StartOpt logger := ctx.Logger if cpuProfile := ctx.Viper.GetString(srvflags.CPUProfile); cpuProfile != "" { - fp, err := ethdebug.ExpandHome(cpuProfile) + fp, err := zetaos.ExpandHomeDir(cpuProfile) if err != nil { ctx.Logger.Debug("failed to get filepath for the CPU profile file", "error", err.Error()) return err diff --git a/testutil/keeper/crosschain.go b/testutil/keeper/crosschain.go index b90328663f..e7650075f4 100644 --- a/testutil/keeper/crosschain.go +++ b/testutil/keeper/crosschain.go @@ -390,6 +390,7 @@ func MockRevertForHandleEVMDeposit( mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{VmError: "reverted"}, false, errDeposit) } diff --git a/testutil/keeper/fungible.go b/testutil/keeper/fungible.go index c1a25a75b1..a4e4dc406e 100644 --- a/testutil/keeper/fungible.go +++ b/testutil/keeper/fungible.go @@ -243,7 +243,7 @@ func (m *FungibleMockEVMKeeper) SetupMockEVMKeeperForSystemContractDeployment() mock.Anything, mock.Anything, ).Return(gasRes, nil) - m.MockEVMSuccessCallTimes(5) + m.MockEVMSuccessCallTimes(7) m.On( "GetAccount", mock.Anything, diff --git a/testutil/keeper/mocks/crosschain/fungible.go b/testutil/keeper/mocks/crosschain/fungible.go index 70f56c1f59..0988e905b7 100644 --- a/testutil/keeper/mocks/crosschain/fungible.go +++ b/testutil/keeper/mocks/crosschain/fungible.go @@ -8,8 +8,12 @@ import ( common "github.com/ethereum/go-ethereum/common" coin "github.com/zeta-chain/zetacore/pkg/coin" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + + fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" mock "github.com/stretchr/testify/mock" @@ -416,6 +420,24 @@ func (_m *CrosschainFungibleKeeper) GetUniswapV2Router02Address(ctx types.Contex return r0, r1 } +// ProcessV2RevertDeposit provides a mock function with given fields: ctx, amount, chainID, coinType, asset, revertAddress, callOnRevert, revertMessage +func (_m *CrosschainFungibleKeeper) ProcessV2RevertDeposit(ctx types.Context, amount *big.Int, chainID int64, coinType coin.CoinType, asset string, revertAddress common.Address, callOnRevert bool, revertMessage []byte) error { + ret := _m.Called(ctx, amount, chainID, coinType, asset, revertAddress, callOnRevert, revertMessage) + + if len(ret) == 0 { + panic("no return value specified for ProcessV2RevertDeposit") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.Context, *big.Int, int64, coin.CoinType, string, common.Address, bool, []byte) error); ok { + r0 = rf(ctx, amount, chainID, coinType, asset, revertAddress, callOnRevert, revertMessage) + } else { + r0 = ret.Error(0) + } + + return r0 +} + // QueryGasLimit provides a mock function with given fields: ctx, contract func (_m *CrosschainFungibleKeeper) QueryGasLimit(ctx types.Context, contract common.Address) (*big.Int, error) { ret := _m.Called(ctx, contract) @@ -677,9 +699,9 @@ func (_m *CrosschainFungibleKeeper) ZETARevertAndCallContract(ctx types.Context, return r0, r1 } -// ZRC20DepositAndCallContract provides a mock function with given fields: ctx, from, to, amount, senderChainID, data, coinType, asset -func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Context, from []byte, to common.Address, amount *big.Int, senderChainID int64, data []byte, coinType coin.CoinType, asset string) (*evmtypes.MsgEthereumTxResponse, bool, error) { - ret := _m.Called(ctx, from, to, amount, senderChainID, data, coinType, asset) +// ZRC20DepositAndCallContract provides a mock function with given fields: ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion +func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Context, from []byte, to common.Address, amount *big.Int, senderChainID int64, data []byte, coinType coin.CoinType, asset string, protocolContractVersion crosschaintypes.ProtocolContractVersion) (*evmtypes.MsgEthereumTxResponse, bool, error) { + ret := _m.Called(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) if len(ret) == 0 { panic("no return value specified for ZRC20DepositAndCallContract") @@ -688,25 +710,25 @@ func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Contex var r0 *evmtypes.MsgEthereumTxResponse var r1 bool var r2 error - if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string) (*evmtypes.MsgEthereumTxResponse, bool, error)); ok { - return rf(ctx, from, to, amount, senderChainID, data, coinType, asset) + if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string, crosschaintypes.ProtocolContractVersion) (*evmtypes.MsgEthereumTxResponse, bool, error)); ok { + return rf(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) } - if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string) *evmtypes.MsgEthereumTxResponse); ok { - r0 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) + if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string, crosschaintypes.ProtocolContractVersion) *evmtypes.MsgEthereumTxResponse); ok { + r0 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*evmtypes.MsgEthereumTxResponse) } } - if rf, ok := ret.Get(1).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string) bool); ok { - r1 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) + if rf, ok := ret.Get(1).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string, crosschaintypes.ProtocolContractVersion) bool); ok { + r1 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) } else { r1 = ret.Get(1).(bool) } - if rf, ok := ret.Get(2).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string) error); ok { - r2 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) + if rf, ok := ret.Get(2).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, coin.CoinType, string, crosschaintypes.ProtocolContractVersion) error); ok { + r2 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset, protocolContractVersion) } else { r2 = ret.Error(2) } diff --git a/testutil/sample/crosschain.go b/testutil/sample/crosschain.go index c76ced9767..6742c2d2f1 100644 --- a/testutil/sample/crosschain.go +++ b/testutil/sample/crosschain.go @@ -14,7 +14,7 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" - zrc20 "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" @@ -53,33 +53,6 @@ func RateLimiterFlags() types.RateLimiterFlags { } } -// CustomRateLimiterFlags creates a custom rate limiter flags with the given parameters -func CustomRateLimiterFlags( - enabled bool, - window int64, - rate math.Uint, - conversions []types.Conversion, -) types.RateLimiterFlags { - return types.RateLimiterFlags{ - Enabled: enabled, - Window: window, - Rate: rate, - Conversions: conversions, - } -} - -func AssetRate() types.AssetRate { - r := Rand() - - return types.AssetRate{ - ChainId: r.Int63(), - Asset: EthAddress().Hex(), - Decimals: uint32(r.Uint64()), - CoinType: coin.CoinType_ERC20, - Rate: sdk.NewDec(r.Int63()), - } -} - // CustomAssetRate creates a custom asset rate with the given parameters func CustomAssetRate( chainID int64, @@ -131,6 +104,19 @@ func GasPrice(t *testing.T, index string) *types.GasPrice { } } +func GasPriceWithChainID(t *testing.T, chainID int64) types.GasPrice { + r := newRandFromStringSeed(t, fmt.Sprintf("%d", chainID)) + + return types.GasPrice{ + Creator: AccAddress(), + ChainId: chainID, + Signers: []string{AccAddress(), AccAddress()}, + BlockNums: []uint64{r.Uint64(), r.Uint64()}, + Prices: []uint64{r.Uint64(), r.Uint64()}, + MedianIndex: 0, + } +} + func InboundParams(r *rand.Rand) *types.InboundParams { return &types.InboundParams{ Sender: EthAddress().String(), @@ -196,10 +182,13 @@ func OutboundParamsValidChainID(r *rand.Rand) *types.OutboundParams { func Status(t *testing.T, index string) *types.Status { r := newRandFromStringSeed(t, index) + createdAt := r.Int63() + return &types.Status{ Status: types.CctxStatus(r.Intn(100)), StatusMessage: String(), - LastUpdateTimestamp: r.Int63(), + CreatedTimestamp: createdAt, + LastUpdateTimestamp: createdAt, } } @@ -211,13 +200,15 @@ func CrossChainTx(t *testing.T, index string) *types.CrossChainTx { r := newRandFromStringSeed(t, index) return &types.CrossChainTx{ - Creator: AccAddress(), - Index: GetCctxIndexFromString(index), - ZetaFees: math.NewUint(uint64(r.Int63())), - RelayedMessage: StringRandom(r, 32), - CctxStatus: Status(t, index), - InboundParams: InboundParams(r), - OutboundParams: []*types.OutboundParams{OutboundParams(r), OutboundParams(r)}, + Creator: AccAddress(), + Index: GetCctxIndexFromString(index), + ZetaFees: math.NewUint(uint64(r.Int63())), + RelayedMessage: StringRandom(r, 32), + CctxStatus: Status(t, index), + InboundParams: InboundParams(r), + OutboundParams: []*types.OutboundParams{OutboundParams(r), OutboundParams(r)}, + ProtocolContractVersion: types.ProtocolContractVersion_V1, + RevertOptions: types.NewEmptyRevertOptions(), } } @@ -302,7 +293,7 @@ func ZRC20Withdrawal(to []byte, value *big.Int) *zrc20.ZRC20Withdrawal { From: EthAddress(), To: to, Value: value, - Gasfee: big.NewInt(Int64InRange(100000, 10000000)), + GasFee: big.NewInt(Int64InRange(100000, 10000000)), ProtocolFlatFee: big.NewInt(Int64InRange(100000, 10000000)), } } diff --git a/testutil/sample/crypto.go b/testutil/sample/crypto.go index a46310fb25..906b8f6ee0 100644 --- a/testutil/sample/crypto.go +++ b/testutil/sample/crypto.go @@ -57,11 +57,18 @@ func EthAddress() ethcommon.Address { return ethcommon.BytesToAddress(sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).Bytes()) } +// SolanaPrivateKey returns a sample solana private key +func SolanaPrivateKey(t *testing.T) solana.PrivateKey { + privKey, err := solana.NewRandomPrivateKey() + require.NoError(t, err) + return privKey +} + // SolanaAddress returns a sample solana address func SolanaAddress(t *testing.T) string { - keypair, err := solana.NewRandomPrivateKey() + privKey, err := solana.NewRandomPrivateKey() require.NoError(t, err) - return keypair.PublicKey().String() + return privKey.PublicKey().String() } // SolanaSignature returns a sample solana signature diff --git a/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts b/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts index c4839a61f5..a0ab4b06ca 100644 --- a/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts @@ -80,6 +80,24 @@ export declare enum TxFinalizationStatus { Executed = 2, } +/** + * ProtocolContractVersion represents the version of the protocol contract used + * for cctx workflow + * + * @generated from enum zetachain.zetacore.crosschain.ProtocolContractVersion + */ +export declare enum ProtocolContractVersion { + /** + * @generated from enum value: V1 = 0; + */ + V1 = 0, + + /** + * @generated from enum value: V2 = 1; + */ + V2 = 1, +} + /** * @generated from message zetachain.zetacore.crosschain.InboundParams */ @@ -315,6 +333,13 @@ export declare class Status extends Message { */ isAbortRefunded: boolean; + /** + * when the CCTX was created. only populated on new transactions. + * + * @generated from field: int64 created_timestamp = 5; + */ + createdTimestamp: bigint; + constructor(data?: PartialMessage); static readonly runtime: typeof proto3; @@ -330,6 +355,52 @@ export declare class Status extends Message { static equals(a: Status | PlainMessage | undefined, b: Status | PlainMessage | undefined): boolean; } +/** + * RevertOptions represents the options for reverting a cctx + * + * @generated from message zetachain.zetacore.crosschain.RevertOptions + */ +export declare class RevertOptions extends Message { + /** + * @generated from field: string revert_address = 1; + */ + revertAddress: string; + + /** + * @generated from field: bool call_on_revert = 2; + */ + callOnRevert: boolean; + + /** + * @generated from field: string abort_address = 3; + */ + abortAddress: string; + + /** + * @generated from field: bytes revert_message = 4; + */ + revertMessage: Uint8Array; + + /** + * @generated from field: string revert_gas_limit = 5; + */ + revertGasLimit: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.RevertOptions"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): RevertOptions; + + static fromJson(jsonValue: JsonValue, options?: Partial): RevertOptions; + + static fromJsonString(jsonString: string, options?: Partial): RevertOptions; + + static equals(a: RevertOptions | PlainMessage | undefined, b: RevertOptions | PlainMessage | undefined): boolean; +} + /** * @generated from message zetachain.zetacore.crosschain.CrossChainTx */ @@ -371,6 +442,16 @@ export declare class CrossChainTx extends Message { */ outboundParams: OutboundParams[]; + /** + * @generated from field: zetachain.zetacore.crosschain.ProtocolContractVersion protocol_contract_version = 11; + */ + protocolContractVersion: ProtocolContractVersion; + + /** + * @generated from field: zetachain.zetacore.crosschain.RevertOptions revert_options = 12; + */ + revertOptions?: RevertOptions; + constructor(data?: PartialMessage); static readonly runtime: typeof proto3; diff --git a/typescript/zetachain/zetacore/crosschain/events_pb.d.ts b/typescript/zetachain/zetacore/crosschain/events_pb.d.ts index 2a71867f7b..83ad9499f6 100644 --- a/typescript/zetachain/zetacore/crosschain/events_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/events_pb.d.ts @@ -354,3 +354,76 @@ export declare class EventERC20Whitelist extends Message { static equals(a: EventERC20Whitelist | PlainMessage | undefined, b: EventERC20Whitelist | PlainMessage | undefined): boolean; } +/** + * @generated from message zetachain.zetacore.crosschain.EventERC20CustodyFundsMigration + */ +export declare class EventERC20CustodyFundsMigration extends Message { + /** + * @generated from field: string new_custody_address = 1; + */ + newCustodyAddress: string; + + /** + * @generated from field: string erc20_address = 2; + */ + erc20Address: string; + + /** + * @generated from field: string amount = 3; + */ + amount: string; + + /** + * @generated from field: string cctx_index = 4; + */ + cctxIndex: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.EventERC20CustodyFundsMigration"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): EventERC20CustodyFundsMigration; + + static fromJson(jsonValue: JsonValue, options?: Partial): EventERC20CustodyFundsMigration; + + static fromJsonString(jsonString: string, options?: Partial): EventERC20CustodyFundsMigration; + + static equals(a: EventERC20CustodyFundsMigration | PlainMessage | undefined, b: EventERC20CustodyFundsMigration | PlainMessage | undefined): boolean; +} + +/** + * @generated from message zetachain.zetacore.crosschain.EventERC20CustodyPausing + */ +export declare class EventERC20CustodyPausing extends Message { + /** + * @generated from field: int64 chain_id = 1; + */ + chainId: bigint; + + /** + * @generated from field: bool pause = 2; + */ + pause: boolean; + + /** + * @generated from field: string cctx_index = 3; + */ + cctxIndex: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.EventERC20CustodyPausing"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): EventERC20CustodyPausing; + + static fromJson(jsonValue: JsonValue, options?: Partial): EventERC20CustodyPausing; + + static fromJsonString(jsonString: string, options?: Partial): EventERC20CustodyPausing; + + static equals(a: EventERC20CustodyPausing | PlainMessage | undefined, b: EventERC20CustodyPausing | PlainMessage | undefined): boolean; +} + diff --git a/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts b/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts index e15cf71780..657a772384 100644 --- a/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts @@ -8,6 +8,7 @@ import { Message, proto3 } from "@bufbuild/protobuf"; import type { CoinType } from "../pkg/coin/coin_pb.js"; import type { Proof } from "../pkg/proofs/proofs_pb.js"; import type { ReceiveStatus } from "../pkg/chains/chains_pb.js"; +import type { ProtocolContractVersion, RevertOptions } from "./cross_chain_tx_pb.js"; import type { RateLimiterFlags } from "./rate_limiter_flags_pb.js"; /** @@ -641,6 +642,20 @@ export declare class MsgVoteInbound extends Message { */ eventIndex: bigint; + /** + * protocol contract version to use for the cctx workflow + * + * @generated from field: zetachain.zetacore.crosschain.ProtocolContractVersion protocol_contract_version = 16; + */ + protocolContractVersion: ProtocolContractVersion; + + /** + * revert options provided by the sender + * + * @generated from field: zetachain.zetacore.crosschain.RevertOptions revert_options = 17; + */ + revertOptions?: RevertOptions; + constructor(data?: PartialMessage); static readonly runtime: typeof proto3; @@ -826,3 +841,132 @@ export declare class MsgUpdateRateLimiterFlagsResponse extends Message | undefined, b: MsgUpdateRateLimiterFlagsResponse | PlainMessage | undefined): boolean; } +/** + * @generated from message zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFunds + */ +export declare class MsgMigrateERC20CustodyFunds extends Message { + /** + * @generated from field: string creator = 1; + */ + creator: string; + + /** + * @generated from field: int64 chain_id = 2; + */ + chainId: bigint; + + /** + * @generated from field: string new_custody_address = 3; + */ + newCustodyAddress: string; + + /** + * @generated from field: string erc20_address = 4; + */ + erc20Address: string; + + /** + * @generated from field: string amount = 5; + */ + amount: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFunds"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): MsgMigrateERC20CustodyFunds; + + static fromJson(jsonValue: JsonValue, options?: Partial): MsgMigrateERC20CustodyFunds; + + static fromJsonString(jsonString: string, options?: Partial): MsgMigrateERC20CustodyFunds; + + static equals(a: MsgMigrateERC20CustodyFunds | PlainMessage | undefined, b: MsgMigrateERC20CustodyFunds | PlainMessage | undefined): boolean; +} + +/** + * @generated from message zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFundsResponse + */ +export declare class MsgMigrateERC20CustodyFundsResponse extends Message { + /** + * @generated from field: string cctx_index = 1; + */ + cctxIndex: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFundsResponse"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): MsgMigrateERC20CustodyFundsResponse; + + static fromJson(jsonValue: JsonValue, options?: Partial): MsgMigrateERC20CustodyFundsResponse; + + static fromJsonString(jsonString: string, options?: Partial): MsgMigrateERC20CustodyFundsResponse; + + static equals(a: MsgMigrateERC20CustodyFundsResponse | PlainMessage | undefined, b: MsgMigrateERC20CustodyFundsResponse | PlainMessage | undefined): boolean; +} + +/** + * @generated from message zetachain.zetacore.crosschain.MsgUpdateERC20CustodyPauseStatus + */ +export declare class MsgUpdateERC20CustodyPauseStatus extends Message { + /** + * @generated from field: string creator = 1; + */ + creator: string; + + /** + * @generated from field: int64 chain_id = 2; + */ + chainId: bigint; + + /** + * pause or unpause + * true = pause, false = unpause + * + * @generated from field: bool pause = 3; + */ + pause: boolean; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.MsgUpdateERC20CustodyPauseStatus"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): MsgUpdateERC20CustodyPauseStatus; + + static fromJson(jsonValue: JsonValue, options?: Partial): MsgUpdateERC20CustodyPauseStatus; + + static fromJsonString(jsonString: string, options?: Partial): MsgUpdateERC20CustodyPauseStatus; + + static equals(a: MsgUpdateERC20CustodyPauseStatus | PlainMessage | undefined, b: MsgUpdateERC20CustodyPauseStatus | PlainMessage | undefined): boolean; +} + +/** + * @generated from message zetachain.zetacore.crosschain.MsgUpdateERC20CustodyPauseStatusResponse + */ +export declare class MsgUpdateERC20CustodyPauseStatusResponse extends Message { + /** + * @generated from field: string cctx_index = 1; + */ + cctxIndex: string; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.crosschain.MsgUpdateERC20CustodyPauseStatusResponse"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): MsgUpdateERC20CustodyPauseStatusResponse; + + static fromJson(jsonValue: JsonValue, options?: Partial): MsgUpdateERC20CustodyPauseStatusResponse; + + static fromJsonString(jsonString: string, options?: Partial): MsgUpdateERC20CustodyPauseStatusResponse; + + static equals(a: MsgUpdateERC20CustodyPauseStatusResponse | PlainMessage | undefined, b: MsgUpdateERC20CustodyPauseStatusResponse | PlainMessage | undefined): boolean; +} + diff --git a/typescript/zetachain/zetacore/pkg/coin/coin_pb.d.ts b/typescript/zetachain/zetacore/pkg/coin/coin_pb.d.ts index 2ad47ca416..f3b177ebb2 100644 --- a/typescript/zetachain/zetacore/pkg/coin/coin_pb.d.ts +++ b/typescript/zetachain/zetacore/pkg/coin/coin_pb.d.ts @@ -27,10 +27,17 @@ export declare enum CoinType { ERC20 = 2, /** - * not a real coin, rather a command + * no asset, used for admin command * * @generated from enum value: Cmd = 3; */ Cmd = 3, + + /** + * no asset, used for contract call + * + * @generated from enum value: NoAssetCall = 4; + */ + NoAssetCall = 4, } diff --git a/x/authority/types/authorization_list.go b/x/authority/types/authorization_list.go index 58f3d80063..1437ed030c 100644 --- a/x/authority/types/authorization_list.go +++ b/x/authority/types/authorization_list.go @@ -23,6 +23,8 @@ var ( } // AdminPolicyMessages keeps track of the message URLs that can, by default, only be executed by admin policy address AdminPolicyMessages = []string{ + "/zetachain.zetacore.crosschain.MsgUpdateERC20CustodyPauseStatus", + "/zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFunds", "/zetachain.zetacore.crosschain.MsgMigrateTssFunds", "/zetachain.zetacore.crosschain.MsgUpdateTssAddress", "/zetachain.zetacore.crosschain.MsgWhitelistERC20", diff --git a/x/authority/types/authorization_list_test.go b/x/authority/types/authorization_list_test.go index 2322caa798..4244e273fe 100644 --- a/x/authority/types/authorization_list_test.go +++ b/x/authority/types/authorization_list_test.go @@ -414,6 +414,8 @@ func TestDefaultAuthorizationsList(t *testing.T) { // AdminPolicyMessageList is a list of messages that can be authorized by the admin policy var AdminPolicyMessageList = []string{ + sdk.MsgTypeURL(&crosschaintypes.MsgUpdateERC20CustodyPauseStatus{}), + sdk.MsgTypeURL(&crosschaintypes.MsgMigrateERC20CustodyFunds{}), sdk.MsgTypeURL(&crosschaintypes.MsgMigrateTssFunds{}), sdk.MsgTypeURL(&crosschaintypes.MsgUpdateTssAddress{}), sdk.MsgTypeURL(&crosschaintypes.MsgWhitelistERC20{}), diff --git a/x/crosschain/client/cli/tx_vote_inbound.go b/x/crosschain/client/cli/tx_vote_inbound.go index 3581b01e0c..61d6deab2e 100644 --- a/x/crosschain/client/cli/tx_vote_inbound.go +++ b/x/crosschain/client/cli/tx_vote_inbound.go @@ -17,9 +17,9 @@ import ( func CmdVoteInbound() *cobra.Command { cmd := &cobra.Command{ Use: "vote-inbound [sender] [senderChainID] [txOrigin] [receiver] [receiverChainID] [amount] [message" + - "] [inboundHash] [inBlockHeight] [coinType] [asset] [eventIndex]", + "] [inboundHash] [inBlockHeight] [coinType] [asset] [eventIndex] [protocolContractVersion]", Short: "Broadcast message to vote an inbound", - Args: cobra.ExactArgs(12), + Args: cobra.ExactArgs(13), RunE: func(cmd *cobra.Command, args []string) error { argsSender := args[0] argsSenderChain, err := strconv.ParseInt(args[1], 10, 64) @@ -62,6 +62,11 @@ func CmdVoteInbound() *cobra.Command { return err } + protocolContractVersion, err := parseProtocolContractVersion(args[12]) + if err != nil { + return err + } + msg := types.NewMsgVoteInbound( clientCtx.GetFromAddress().String(), argsSender, @@ -77,6 +82,7 @@ func CmdVoteInbound() *cobra.Command { argsCoinType, argsAsset, uint(argsEventIndex), + protocolContractVersion, ) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) @@ -87,3 +93,16 @@ func CmdVoteInbound() *cobra.Command { return cmd } + +func parseProtocolContractVersion(version string) (types.ProtocolContractVersion, error) { + switch version { + case "V1": + return types.ProtocolContractVersion_V1, nil + case "V2": + return types.ProtocolContractVersion_V2, nil + default: + return types.ProtocolContractVersion_V1, fmt.Errorf( + "invalid protocol contract version, specify either V1 or V2", + ) + } +} diff --git a/x/crosschain/client/querytests/cctx.go b/x/crosschain/client/querytests/cctx.go deleted file mode 100644 index 0fe177e6ca..0000000000 --- a/x/crosschain/client/querytests/cctx.go +++ /dev/null @@ -1,122 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - "github.com/cosmos/cosmos-sdk/client/flags" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/zeta-chain/zetacore/x/crosschain/client/cli" - "github.com/zeta-chain/zetacore/x/crosschain/types" -) - -func (s *CliTestSuite) TestListCCTX() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.CrossChainTxs - request := func(next []byte, offset, limit uint64, total bool) []string { - args := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - if next == nil { - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset)) - } else { - args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next)) - } - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit)) - if total { - args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal)) - } - return args - } - - s.Run("ByOffset", func() { - step := 2 - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(nil, uint64(i), uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListSend(), args) - s.Require().NoError(err) - var resp types.QueryAllCctxResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - for j := i; j < len(objs) && j < i+step; j++ { - s.Require().Equal(objs[j], resp.CrossChainTx[j-i]) - } - } - }) - s.Run("ByKey", func() { - step := 2 - var next []byte - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(next, 0, uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListSend(), args) - s.Require().NoError(err) - var resp types.QueryAllCctxResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - for j := i; j < len(objs) && j < i+step; j++ { - s.Assert().Equal(objs[j], resp.CrossChainTx[j-i]) - } - next = resp.Pagination.NextKey - } - }) - s.Run("Total", func() { - args := request(nil, 0, uint64(len(objs)), true) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListSend(), args) - s.Require().NoError(err) - var resp types.QueryAllCctxResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NoError(err) - // #nosec G115 always in range - s.Require().Equal(len(objs), int(resp.Pagination.Total)) - s.Require().Equal(objs, resp.CrossChainTx) - }) -} - -func (s *CliTestSuite) TestShowSend() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.CrossChainTxs - common := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - for _, tc := range []struct { - desc string - id string - args []string - err error - obj *types.CrossChainTx - }{ - { - desc: "found", - id: objs[0].Index, - args: common, - obj: objs[0], - }, - { - desc: "not found", - id: "not_found", - args: common, - err: status.Error(codes.InvalidArgument, "not found"), - }, - } { - tc := tc - s.Run(tc.desc, func() { - args := []string{tc.id} - args = append(args, tc.args...) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowSend(), args) - if tc.err != nil { - stat, ok := status.FromError(tc.err) - s.Require().True(ok) - s.Require().ErrorIs(stat.Err(), tc.err) - } else { - s.Require().NoError(err) - var resp types.QueryGetCctxResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NotNil(resp.CrossChainTx) - s.Require().Equal(tc.obj, resp.CrossChainTx) - } - }) - } -} diff --git a/x/crosschain/client/querytests/cli_test.go b/x/crosschain/client/querytests/cli_test.go deleted file mode 100644 index 777fc81528..0000000000 --- a/x/crosschain/client/querytests/cli_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package querytests - -import ( - "testing" - - tmdb "github.com/cometbft/cometbft-db" - "github.com/cosmos/cosmos-sdk/baseapp" - servertypes "github.com/cosmos/cosmos-sdk/server/types" - pruningtypes "github.com/cosmos/cosmos-sdk/store/pruning/types" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - "github.com/cosmos/cosmos-sdk/types/module/testutil" - "github.com/stretchr/testify/suite" - - "github.com/zeta-chain/zetacore/app" - "github.com/zeta-chain/zetacore/testutil/network" -) - -func TestCLIQuerySuite(t *testing.T) { - cfg := network.DefaultConfig(NewTestNetworkFixture) - suite.Run(t, NewCLITestSuite(cfg)) -} - -func NewTestNetworkFixture() network.TestFixture { - encoding := app.MakeEncodingConfig() - appCtr := func(val network.ValidatorI) servertypes.Application { - return app.New( - val.GetCtx().Logger, tmdb.NewMemDB(), nil, true, map[int64]bool{}, val.GetCtx().Config.RootDir, 0, - encoding, - simtestutil.EmptyAppOptions{}, - baseapp.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)), - baseapp.SetMinGasPrices(val.GetAppConfig().MinGasPrices), - baseapp.SetChainID("athens_8888-2"), - ) - } - - return network.TestFixture{ - AppConstructor: appCtr, - GenesisState: app.ModuleBasics.DefaultGenesis(encoding.Codec), - EncodingConfig: testutil.TestEncodingConfig{ - InterfaceRegistry: encoding.InterfaceRegistry, - Codec: encoding.Codec, - TxConfig: encoding.TxConfig, - Amino: encoding.Amino, - }, - } -} diff --git a/x/crosschain/client/querytests/gas_price.go b/x/crosschain/client/querytests/gas_price.go deleted file mode 100644 index 7a0c6422c8..0000000000 --- a/x/crosschain/client/querytests/gas_price.go +++ /dev/null @@ -1,122 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - "github.com/cosmos/cosmos-sdk/client/flags" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/zeta-chain/zetacore/x/crosschain/client/cli" - "github.com/zeta-chain/zetacore/x/crosschain/types" -) - -func (s *CliTestSuite) TestShowGasPrice() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.GasPriceList - common := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - for _, tc := range []struct { - desc string - id string - args []string - err error - obj *types.GasPrice - }{ - { - desc: "found", - id: objs[0].Index, - args: common, - obj: objs[0], - }, - { - desc: "not found", - id: "not_found", - args: common, - err: status.Error(codes.InvalidArgument, "not found"), - }, - } { - tc := tc - s.Run(tc.desc, func() { - args := []string{tc.id} - args = append(args, tc.args...) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowGasPrice(), args) - if tc.err != nil { - stat, ok := status.FromError(tc.err) - s.Require().True(ok) - s.Require().ErrorIs(stat.Err(), tc.err) - } else { - s.Require().NoError(err) - var resp types.QueryGetGasPriceResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NotNil(resp.GasPrice) - s.Require().Equal(tc.obj, resp.GasPrice) - } - }) - } -} - -func (s *CliTestSuite) TestListGasPrice() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.GasPriceList - request := func(next []byte, offset, limit uint64, total bool) []string { - args := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - if next == nil { - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset)) - } else { - args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next)) - } - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit)) - if total { - args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal)) - } - return args - } - s.Run("ByOffset", func() { - step := 2 - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(nil, uint64(i), uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListGasPrice(), args) - s.Require().NoError(err) - var resp types.QueryAllGasPriceResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - for j := i; j < len(objs) && j < i+step; j++ { - s.Assert().Equal(objs[j], resp.GasPrice[j-i]) - } - } - }) - s.Run("ByKey", func() { - step := 2 - var next []byte - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(next, 0, uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListGasPrice(), args) - s.Require().NoError(err) - var resp types.QueryAllGasPriceResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - for j := i; j < len(objs) && j < i+step; j++ { - s.Assert().Equal(objs[j], resp.GasPrice[j-i]) - } - next = resp.Pagination.NextKey - } - }) - s.Run("Total", func() { - // #nosec G115 always in range - args := request(nil, 0, uint64(len(objs)), true) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListGasPrice(), args) - s.Require().NoError(err) - var resp types.QueryAllGasPriceResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NoError(err) - // #nosec G115 always in range - s.Require().Equal(len(objs), int(resp.Pagination.Total)) - s.Require().Equal(objs, resp.GasPrice) - }) -} diff --git a/x/crosschain/client/querytests/inbound_hash.go b/x/crosschain/client/querytests/inbound_hash.go deleted file mode 100644 index 9f6d48ef53..0000000000 --- a/x/crosschain/client/querytests/inbound_hash.go +++ /dev/null @@ -1,137 +0,0 @@ -package querytests - -import ( - "fmt" - "strconv" - - tmcli "github.com/cometbft/cometbft/libs/cli" - "github.com/cosmos/cosmos-sdk/client/flags" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/zeta-chain/zetacore/testutil/nullify" - "github.com/zeta-chain/zetacore/x/crosschain/client/cli" - "github.com/zeta-chain/zetacore/x/crosschain/types" -) - -func (s *CliTestSuite) TestShowInboundHashToCctx() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.InboundHashToCctxList - common := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - for _, tc := range []struct { - desc string - idInboundHash string - - args []string - err error - obj types.InboundHashToCctx - }{ - { - desc: "found", - idInboundHash: objs[0].InboundHash, - - args: common, - obj: objs[0], - }, - { - desc: "not found", - idInboundHash: strconv.Itoa(100000), - - args: common, - err: status.Error(codes.NotFound, "not found"), - }, - } { - s.Run(tc.desc, func() { - args := []string{ - tc.idInboundHash, - } - args = append(args, tc.args...) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowInboundHashToCctx(), args) - if tc.err != nil { - stat, ok := status.FromError(tc.err) - s.Require().True(ok) - s.Require().ErrorIs(stat.Err(), tc.err) - } else { - s.Require().NoError(err) - var resp types.QueryGetInboundHashToCctxResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NotNil(resp.InboundHashToCctx) - tc := tc - s.Require().Equal(nullify.Fill(&tc.obj), - nullify.Fill(&resp.InboundHashToCctx), - ) - } - }) - } -} - -func (s *CliTestSuite) TestListInboundHashToCctx() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.InboundHashToCctxList - cctxCount := len(s.crosschainState.CrossChainTxs) - request := func(next []byte, offset, limit uint64, total bool) []string { - args := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - if next == nil { - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset)) - } else { - args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next)) - } - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit)) - if total { - args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal)) - } - return args - } - s.Run("ByOffset", func() { - step := 2 - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(nil, uint64(i), uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListInboundHashToCctx(), args) - s.Require().NoError(err) - var resp types.QueryAllInboundHashToCctxResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().LessOrEqual(len(resp.InboundHashToCctx), step) - s.Require().Subset(nullify.Fill(objs), - nullify.Fill(resp.InboundHashToCctx), - ) - } - }) - s.Run("ByKey", func() { - step := 2 - var next []byte - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(next, 0, uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListInboundHashToCctx(), args) - s.Require().NoError(err) - var resp types.QueryAllInboundHashToCctxResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().LessOrEqual(len(resp.InboundHashToCctx), step) - s.Require().Subset(nullify.Fill(objs), - nullify.Fill(resp.InboundHashToCctx), - ) - next = resp.Pagination.NextKey - } - }) - s.Run("Total", func() { - // #nosec G115 always in range - args := request(nil, 0, uint64(len(objs)), true) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListInboundHashToCctx(), args) - s.Require().NoError(err) - var resp types.QueryAllInboundHashToCctxResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NoError(err) - // saving CCTX also adds a new mapping - // #nosec G115 always in range - s.Require().Equal(len(objs)+cctxCount, int(resp.Pagination.Total)) - s.Require().ElementsMatch(nullify.Fill(objs), - nullify.Fill(resp.InboundHashToCctx), - ) - }) -} diff --git a/x/crosschain/client/querytests/inbound_tracker.go b/x/crosschain/client/querytests/inbound_tracker.go deleted file mode 100644 index 162b552865..0000000000 --- a/x/crosschain/client/querytests/inbound_tracker.go +++ /dev/null @@ -1,104 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - "github.com/cosmos/cosmos-sdk/client/flags" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - - "github.com/zeta-chain/zetacore/testutil/nullify" - "github.com/zeta-chain/zetacore/x/crosschain/client/cli" - "github.com/zeta-chain/zetacore/x/crosschain/types" -) - -func (s *CliTestSuite) TestListInboundTrackers() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.InboundTrackerList - s.Run("List all trackers", func() { - args := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListInboundTrackers(), args) - s.Require().NoError(err) - var resp types.QueryAllInboundTrackersResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().Equal(len(objs), len(resp.InboundTracker)) - s.Require().ElementsMatch(nullify.Fill(objs), nullify.Fill(resp.InboundTracker)) - }) -} - -func (s *CliTestSuite) TestListInboundTrackersByChain() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.InboundTrackerList - request := func(next []byte, offset, limit uint64, total bool, chainID int) []string { - args := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - if next == nil { - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset)) - } else { - args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next)) - } - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit)) - if total { - args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal)) - } - args = append(args, fmt.Sprintf("%d", chainID)) - return args - } - s.Run("ByOffset", func() { - step := 2 - for i := 0; i < len(objs); i += step { - // #nosec G115 always positive - args := request(nil, uint64(i), uint64(step), false, 5) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListInboundTrackerByChain(), args) - s.Require().NoError(err) - var resp types.QueryAllInboundTrackerByChainResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().LessOrEqual(len(resp.InboundTracker), step) - s.Require().Subset(nullify.Fill(objs), - nullify.Fill(resp.InboundTracker), - ) - } - }) - s.Run("ByKey", func() { - step := 2 - var next []byte - for i := 0; i < len(objs); i += step { - // #nosec G115 always positive - args := request(next, 0, uint64(step), false, 5) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListInboundTrackerByChain(), args) - s.Require().NoError(err) - var resp types.QueryAllInboundTrackerByChainResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().LessOrEqual(len(resp.InboundTracker), step) - s.Require().Subset( - nullify.Fill(objs), - nullify.Fill(resp.InboundTracker), - ) - next = resp.Pagination.NextKey - } - }) - s.Run("Total", func() { - args := request(nil, 0, uint64(len(objs)), true, 5) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListInboundTrackerByChain(), args) - s.Require().NoError(err) - var resp types.QueryAllInboundTrackerByChainResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NoError(err) - s.Require().Equal(uint64(len(objs)), resp.Pagination.Total) - s.Require().ElementsMatch(nullify.Fill(objs), - nullify.Fill(resp.InboundTracker), - ) - }) - s.Run("Incorrect Chain ID ", func() { - args := request(nil, 0, uint64(len(objs)), true, 15) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListInboundTrackerByChain(), args) - s.Require().NoError(err) - var resp types.QueryAllInboundTrackerByChainResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NoError(err) - s.Require().Equal(uint64(0), resp.Pagination.Total) - }) -} diff --git a/x/crosschain/client/querytests/last_block_height.go b/x/crosschain/client/querytests/last_block_height.go deleted file mode 100644 index b7bf12f015..0000000000 --- a/x/crosschain/client/querytests/last_block_height.go +++ /dev/null @@ -1,122 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - "github.com/cosmos/cosmos-sdk/client/flags" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/zeta-chain/zetacore/x/crosschain/client/cli" - "github.com/zeta-chain/zetacore/x/crosschain/types" -) - -func (s *CliTestSuite) TestShowLastBlockHeight() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.LastBlockHeightList - common := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - for _, tc := range []struct { - desc string - id string - args []string - err error - obj *types.LastBlockHeight - }{ - { - desc: "found", - id: objs[0].Index, - args: common, - obj: objs[0], - }, - { - desc: "not found", - id: "not_found", - args: common, - err: status.Error(codes.InvalidArgument, "not found"), - }, - } { - tc := tc - s.Run(tc.desc, func() { - args := []string{tc.id} - args = append(args, tc.args...) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowLastBlockHeight(), args) - if tc.err != nil { - stat, ok := status.FromError(tc.err) - s.Require().True(ok) - s.Require().ErrorIs(stat.Err(), tc.err) - } else { - s.Require().NoError(err) - var resp types.QueryGetLastBlockHeightResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NotNil(resp.LastBlockHeight) - s.Require().Equal(tc.obj, resp.LastBlockHeight) - } - }) - } -} - -func (s *CliTestSuite) TestListLastBlockHeight() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.LastBlockHeightList - request := func(next []byte, offset, limit uint64, total bool) []string { - args := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - if next == nil { - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset)) - } else { - args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next)) - } - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit)) - if total { - args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal)) - } - return args - } - s.Run("ByOffset", func() { - step := 2 - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(nil, uint64(i), uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListLastBlockHeight(), args) - s.Require().NoError(err) - var resp types.QueryAllLastBlockHeightResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - for j := i; j < len(objs) && j < i+step; j++ { - s.Assert().Equal(objs[j], resp.LastBlockHeight[j-i]) - } - } - }) - s.Run("ByKey", func() { - step := 2 - var next []byte - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(next, 0, uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListLastBlockHeight(), args) - s.Require().NoError(err) - var resp types.QueryAllLastBlockHeightResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - for j := i; j < len(objs) && j < i+step; j++ { - s.Assert().Equal(objs[j], resp.LastBlockHeight[j-i]) - } - next = resp.Pagination.NextKey - } - }) - s.Run("Total", func() { - // #nosec G115 always in range - args := request(nil, 0, uint64(len(objs)), true) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListLastBlockHeight(), args) - s.Require().NoError(err) - var resp types.QueryAllLastBlockHeightResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NoError(err) - // #nosec G115 always in range - s.Require().Equal(len(objs), int(resp.Pagination.Total)) - s.Require().Equal(objs, resp.LastBlockHeight) - }) -} diff --git a/x/crosschain/client/querytests/outbound_tracker.go b/x/crosschain/client/querytests/outbound_tracker.go deleted file mode 100644 index 6a6ded0b6d..0000000000 --- a/x/crosschain/client/querytests/outbound_tracker.go +++ /dev/null @@ -1,80 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - "github.com/cosmos/cosmos-sdk/client/flags" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - - "github.com/zeta-chain/zetacore/testutil/nullify" - "github.com/zeta-chain/zetacore/x/crosschain/client/cli" - "github.com/zeta-chain/zetacore/x/crosschain/types" -) - -func (s *CliTestSuite) TestListOutboundTracker() { - ctx := s.network.Validators[0].ClientCtx - objs := s.crosschainState.OutboundTrackerList - request := func(next []byte, offset, limit uint64, total bool) []string { - args := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - if next == nil { - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset)) - } else { - args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next)) - } - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit)) - if total { - args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal)) - } - return args - } - s.Run("ByOffset", func() { - step := 2 - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(nil, uint64(i), uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListOutboundTracker(), args) - s.Require().NoError(err) - var resp types.QueryAllOutboundTrackerResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().LessOrEqual(len(resp.OutboundTracker), step) - s.Require().Subset(nullify.Fill(objs), - nullify.Fill(resp.OutboundTracker), - ) - } - }) - s.Run("ByKey", func() { - step := 2 - var next []byte - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(next, 0, uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListOutboundTracker(), args) - s.Require().NoError(err) - var resp types.QueryAllOutboundTrackerResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().LessOrEqual(len(resp.OutboundTracker), step) - s.Require().Subset( - nullify.Fill(objs), - nullify.Fill(resp.OutboundTracker), - ) - next = resp.Pagination.NextKey - } - }) - s.Run("Total", func() { - // #nosec G115 always in range - args := request(nil, 0, uint64(len(objs)), true) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListOutboundTracker(), args) - s.Require().NoError(err) - var resp types.QueryAllOutboundTrackerResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NoError(err) - // #nosec G115 always in range - s.Require().Equal(len(objs), int(resp.Pagination.Total)) - s.Require().ElementsMatch(nullify.Fill(objs), - nullify.Fill(resp.OutboundTracker), - ) - }) -} diff --git a/x/crosschain/keeper/abci.go b/x/crosschain/keeper/abci.go index 494603f473..391b417fb0 100644 --- a/x/crosschain/keeper/abci.go +++ b/x/crosschain/keeper/abci.go @@ -171,7 +171,6 @@ func CheckAndUpdateCctxGasPrice( // set new gas price and last update timestamp cctx.GetCurrentOutboundParam().GasPrice = newGasPrice.String() cctx.GetCurrentOutboundParam().GasPriorityFee = newPriorityFee.String() - cctx.CctxStatus.LastUpdateTimestamp = ctx.BlockHeader().Time.Unix() k.SetCrossChainTx(ctx, cctx) return gasPriceIncrease, additionalFees, nil diff --git a/x/crosschain/keeper/abci_test.go b/x/crosschain/keeper/abci_test.go index 7ac38cd917..03da0573f6 100644 --- a/x/crosschain/keeper/abci_test.go +++ b/x/crosschain/keeper/abci_test.go @@ -128,6 +128,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) { cctx: types.CrossChainTx{ Index: "a1", CctxStatus: &types.Status{ + CreatedTimestamp: sampleTimestamp.Unix(), LastUpdateTimestamp: sampleTimestamp.Unix(), }, OutboundParams: []*types.OutboundParams{ @@ -152,6 +153,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) { cctx: types.CrossChainTx{ Index: "a2", CctxStatus: &types.Status{ + CreatedTimestamp: sampleTimestamp.Unix(), LastUpdateTimestamp: sampleTimestamp.Unix(), }, OutboundParams: []*types.OutboundParams{ @@ -180,6 +182,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) { cctx: types.CrossChainTx{ Index: "a3", CctxStatus: &types.Status{ + CreatedTimestamp: sampleTimestamp.Unix(), LastUpdateTimestamp: sampleTimestamp.Unix(), }, OutboundParams: []*types.OutboundParams{ @@ -208,6 +211,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) { cctx: types.CrossChainTx{ Index: "b0", CctxStatus: &types.Status{ + CreatedTimestamp: sampleTimestamp.Unix(), LastUpdateTimestamp: sampleTimestamp.Unix(), }, OutboundParams: []*types.OutboundParams{ @@ -235,6 +239,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) { cctx: types.CrossChainTx{ Index: "b1", CctxStatus: &types.Status{ + CreatedTimestamp: sampleTimestamp.Unix(), LastUpdateTimestamp: sampleTimestamp.Unix(), }, OutboundParams: []*types.OutboundParams{ @@ -257,6 +262,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) { cctx: types.CrossChainTx{ Index: "b2", CctxStatus: &types.Status{ + CreatedTimestamp: sampleTimestamp.Unix(), LastUpdateTimestamp: sampleTimestamp.Unix(), }, OutboundParams: []*types.OutboundParams{ @@ -279,6 +285,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) { cctx: types.CrossChainTx{ Index: "b3", CctxStatus: &types.Status{ + CreatedTimestamp: sampleTimestamp.Unix(), LastUpdateTimestamp: sampleTimestamp.Unix(), }, OutboundParams: []*types.OutboundParams{ @@ -301,6 +308,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) { cctx: types.CrossChainTx{ Index: "c1", CctxStatus: &types.Status{ + CreatedTimestamp: sampleTimestamp.Unix(), LastUpdateTimestamp: sampleTimestamp.Unix(), }, OutboundParams: []*types.OutboundParams{ @@ -322,6 +330,7 @@ func TestCheckAndUpdateCctxGasPrice(t *testing.T) { cctx: types.CrossChainTx{ Index: "c2", CctxStatus: &types.Status{ + CreatedTimestamp: sampleTimestamp.Unix(), LastUpdateTimestamp: sampleTimestamp.Unix(), }, OutboundParams: []*types.OutboundParams{ diff --git a/x/crosschain/keeper/cctx.go b/x/crosschain/keeper/cctx.go index b89b517653..f9cf7ee570 100644 --- a/x/crosschain/keeper/cctx.go +++ b/x/crosschain/keeper/cctx.go @@ -54,15 +54,20 @@ func (k Keeper) SetCctxAndNonceToCctxAndInboundHashToCctx(ctx sdk.Context, cctx } } -// SetCrossChainTx set a specific send in the store from its index +// SetCrossChainTx set a specific cctx in the store from its index func (k Keeper) SetCrossChainTx(ctx sdk.Context, cctx types.CrossChainTx) { + // only set the update timestamp if the block height is >0 to allow + // for a genesis import + if cctx.CctxStatus != nil && ctx.BlockHeight() > 0 { + cctx.CctxStatus.LastUpdateTimestamp = ctx.BlockHeader().Time.Unix() + } p := types.KeyPrefix(fmt.Sprintf("%s", types.CCTXKey)) store := prefix.NewStore(ctx.KVStore(k.storeKey), p) b := k.cdc.MustMarshal(&cctx) store.Set(types.KeyPrefix(cctx.Index), b) } -// GetCrossChainTx returns a send from its index +// GetCrossChainTx returns a cctx from its index func (k Keeper) GetCrossChainTx(ctx sdk.Context, index string) (val types.CrossChainTx, found bool) { p := types.KeyPrefix(fmt.Sprintf("%s", types.CCTXKey)) store := prefix.NewStore(ctx.KVStore(k.storeKey), p) @@ -76,6 +81,7 @@ func (k Keeper) GetCrossChainTx(ctx sdk.Context, index string) (val types.CrossC return val, true } +// GetAllCrossChainTx returns all cctxs func (k Keeper) GetAllCrossChainTx(ctx sdk.Context) (list []types.CrossChainTx) { p := types.KeyPrefix(fmt.Sprintf("%s", types.CCTXKey)) store := prefix.NewStore(ctx.KVStore(k.storeKey), p) @@ -93,7 +99,7 @@ func (k Keeper) GetAllCrossChainTx(ctx sdk.Context) (list []types.CrossChainTx) return list } -// RemoveCrossChainTx removes a send from the store +// RemoveCrossChainTx removes a cctx from the store func (k Keeper) RemoveCrossChainTx(ctx sdk.Context, index string) { p := types.KeyPrefix(fmt.Sprintf("%s", types.CCTXKey)) store := prefix.NewStore(ctx.KVStore(k.storeKey), p) diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go index 1391bbaf9d..d4715271da 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go @@ -10,6 +10,7 @@ import ( tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" @@ -44,7 +45,7 @@ func (k Keeper) ValidateOutboundZEVM( if depositErr != nil && isContractReverted { tmpCtxRevert, commitRevert := ctx.CacheContext() // contract call reverted; should refund via a revert tx - err := k.validateFailedOutbound( + err := k.processFailedOutboundOnExternalChain( tmpCtxRevert, cctx, types.CctxStatus_PendingOutbound, @@ -52,14 +53,14 @@ func (k Keeper) ValidateOutboundZEVM( cctx.InboundParams.Amount, ) if err != nil { - cctx.SetAbort(err.Error()) + cctx.SetAbort(fmt.Sprintf("%s : %s", depositErr, err.Error())) return types.CctxStatus_Aborted } commitRevert() return types.CctxStatus_PendingRevert } - k.validateSuccessfulOutbound(ctx, cctx, "", false) + k.processSuccessfulOutbound(ctx, cctx, "", false) return types.CctxStatus_OutboundMined } @@ -75,12 +76,9 @@ func (k Keeper) ValidateOutboundObservers( err := func() error { switch ballotStatus { case observertypes.BallotStatus_BallotFinalized_SuccessObservation: - k.validateSuccessfulOutbound(tmpCtx, cctx, valueReceived, true) + k.processSuccessfulOutbound(tmpCtx, cctx, valueReceived, true) case observertypes.BallotStatus_BallotFinalized_FailureObservation: - err := k.validateFailedOutboundObservers(tmpCtx, cctx, valueReceived) - if err != nil { - return err - } + return k.processFailedOutboundObservers(tmpCtx, cctx, valueReceived) } return nil }() @@ -95,7 +93,7 @@ func (k Keeper) ValidateOutboundObservers( return nil } -// validateFailedOutboundObservers processes a failed outbound transaction for observers. It does the following things in one function: +// processFailedOutboundObservers processes a failed outbound transaction for observers. It does the following things in one function: // // 1. For Admin Tx or a withdrawal from Zeta chain, it aborts the CCTX // @@ -109,24 +107,28 @@ func (k Keeper) ValidateOutboundObservers( // // This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails // This is done because SaveSuccessfulOutbound does not set the cctx status -// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the validateFailedOutboundObservers function, so we can just return and error to trigger that -func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error { +// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the processFailedOutboundObservers function, so we can just return and error to trigger that +func (k Keeper) processFailedOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error { oldStatus := cctx.CctxStatus.Status // The following logic is used to handler the mentioned conditions separately. The reason being // All admin tx is created using a policy message, there is no associated inbound tx, therefore we do not need any revert logic // For transactions which originated from ZEVM, we can process the outbound in the same block as there is no TSS signing required for the revert // For all other transactions we need to create a revert tx and set the status to pending revert + if cctx.ProtocolContractVersion == types.ProtocolContractVersion_V2 { + return k.processFailedOutboundV2(ctx, cctx) + } + if cctx.InboundParams.CoinType == coin.CoinType_Cmd { // if the cctx is of coin type cmd or the sender chain is zeta chain, then we do not revert, the cctx is aborted cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - cctx.SetAbort("Outbound failed") + cctx.SetAbort("Outbound failed, cmd cctx reverted") } else if chains.IsZetaChain(cctx.InboundParams.SenderChainId, k.GetAuthorityKeeper().GetAdditionalChainList(ctx)) { switch cctx.InboundParams.CoinType { // Try revert if the coin-type is ZETA case coin.CoinType_Zeta: { - err := k.validateFailedOutboundObserversForZEVM(ctx, cctx) + err := k.processFailedZETAOutboundOnZEVM(ctx, cctx) if err != nil { return cosmoserrors.Wrap(err, "validateFailedOutboundObserversForZEVMTx") } @@ -135,13 +137,13 @@ func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.Cro default: { cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - cctx.SetAbort("Outbound failed") + cctx.SetAbort("Outbound failed for non-ZETA cctx") } } } else { - err := k.validateFailedOutbound(ctx, cctx, oldStatus, "Outbound failed, start revert", cctx.GetCurrentOutboundParam().Amount) + err := k.processFailedOutboundOnExternalChain(ctx, cctx, oldStatus, "Outbound failed, start revert", cctx.GetCurrentOutboundParam().Amount) if err != nil { - return cosmoserrors.Wrap(err, "validateFailedOutbound") + return cosmoserrors.Wrap(err, "processFailedOutboundOnExternalChain") } } newStatus := cctx.CctxStatus.Status.String() @@ -149,8 +151,8 @@ func (k Keeper) validateFailedOutboundObservers(ctx sdk.Context, cctx *types.Cro return nil } -// validateFailedOutbound processes the failed outbound transaction -func (k Keeper) validateFailedOutbound( +// processFailedOutboundOnExternalChain processes the failed outbound transaction where the receiver is an external chain. +func (k Keeper) processFailedOutboundOnExternalChain( ctx sdk.Context, cctx *types.CrossChainTx, oldStatus types.CctxStatus, @@ -201,7 +203,7 @@ func (k Keeper) validateFailedOutbound( return nil } -// validateSuccessfulOutbound processes a successful outbound transaction. It does the following things in one function: +// processSuccessfulOutbound processes a successful outbound transaction. It does the following things in one function: // // 1. Change the status of the CCTX from // - PendingRevert to Reverted @@ -213,8 +215,8 @@ func (k Keeper) validateFailedOutbound( // // This function sets CCTX status, in cases where the outbound tx is successful, but tx itself fails // This is done because SaveSuccessfulOutbound does not set the cctx status -// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the validateFailedOutboundObservers function, so we can just return and error to trigger that -func (k Keeper) validateSuccessfulOutbound( +// For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the processFailedOutboundObservers function, so we can just return and error to trigger that +func (k Keeper) processSuccessfulOutbound( ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string, @@ -236,8 +238,8 @@ func (k Keeper) validateSuccessfulOutbound( } } -// validateFailedOutboundObserversForZEVM processes the failed outbound transaction for ZEVM -func (k Keeper) validateFailedOutboundObserversForZEVM(ctx sdk.Context, cctx *types.CrossChainTx) error { +// processFailedZETAOutboundOnZEVM processes a failed ZETA outbound on ZEVM +func (k Keeper) processFailedZETAOutboundOnZEVM(ctx sdk.Context, cctx *types.CrossChainTx) error { indexBytes, err := cctx.GetCCTXIndexBytes() if err != nil { // Return err to save the failed outbound and set to aborted @@ -247,8 +249,8 @@ func (k Keeper) validateFailedOutboundObserversForZEVM(ctx sdk.Context, cctx *ty cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed // create new OutboundParams for the revert. We use the fixed gas limit for revert when calling zEVM - err = cctx.AddRevertOutbound(fungiblekeeper.ZEVMGasLimitDepositAndCall.Uint64()) - if err != nil { + + if err := cctx.AddRevertOutbound(fungiblekeeper.ZEVMGasLimitDepositAndCall.Uint64()); err != nil { // Return err to save the failed outbound ad set to aborted return fmt.Errorf("failed AddRevertOutbound: %s", err.Error()) } @@ -300,3 +302,71 @@ func (k Keeper) validateFailedOutboundObserversForZEVM(ctx sdk.Context, cctx *ty cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed return nil } + +// processFailedOutboundV2 processes a failed outbound transaction for protocol version 2 +// for revert, in V2 we have some assumption simplifying the logic +// - sender chain is always ZetaChain +// - all coin type use the same workflow +// TODO: consolidate logic with above function +// https://github.com/zeta-chain/node/issues/2627 +func (k Keeper) processFailedOutboundV2(ctx sdk.Context, cctx *types.CrossChainTx) error { + // check the sender is ZetaChain + zetaChain, err := chains.ZetaChainFromCosmosChainID(ctx.ChainID()) + if err != nil { + return errors.Wrap(err, "failed to get ZetaChain chainID") + } + if cctx.InboundParams.SenderChainId != zetaChain.ChainId { + return fmt.Errorf( + "sender chain for withdraw cctx is not ZetaChain expected %d got %d", + zetaChain.ChainId, + cctx.InboundParams.SenderChainId, + ) + } + + switch cctx.CctxStatus.Status { + case types.CctxStatus_PendingOutbound: + + // get the chain ID of the connected chain + chainID := cctx.GetCurrentOutboundParam().ReceiverChainId + + // add revert outbound + if err := cctx.AddRevertOutbound(fungiblekeeper.ZEVMGasLimitDepositAndCall.Uint64()); err != nil { + // Return err to save the failed outbound ad set to aborted + return errors.Wrap(err, "failed AddRevertOutbound") + } + + // update status + cctx.SetPendingRevert("Outbound failed, trying revert") + + // process the revert on ZEVM + if err := k.fungibleKeeper.ProcessV2RevertDeposit( + ctx, + cctx.GetCurrentOutboundParam().Amount.BigInt(), + chainID, + cctx.InboundParams.CoinType, + cctx.InboundParams.Asset, + ethcommon.HexToAddress(cctx.GetCurrentOutboundParam().Receiver), + cctx.RevertOptions.CallOnRevert, + cctx.RevertOptions.RevertMessage, + ); err != nil { + return errors.Wrap(err, "failed ProcessV2RevertDeposit") + } + + // tx is reverted + cctx.SetReverted("Outbound failed, revert executed") + + // add event for tendermint transaction hash format + if len(ctx.TxBytes()) > 0 { + hash := tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()) + ethTxHash := ethcommon.BytesToHash(hash) + cctx.GetCurrentOutboundParam().Hash = ethTxHash.String() + // #nosec G115 always positive + cctx.GetCurrentOutboundParam().ObservedExternalHeight = uint64(ctx.BlockHeight()) + } + cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed + case types.CctxStatus_PendingRevert: + cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed + cctx.SetAbort("Outbound failed: revert failed; abort TX") + } + return nil +} diff --git a/x/crosschain/keeper/cctx_test.go b/x/crosschain/keeper/cctx_test.go index 647016849e..c523814c69 100644 --- a/x/crosschain/keeper/cctx_test.go +++ b/x/crosschain/keeper/cctx_test.go @@ -37,6 +37,7 @@ func createNCctxWithStatus( items[i].ZetaFees = math.OneUint() items[i].InboundParams = &types.InboundParams{ObservedHash: fmt.Sprintf("%d", i), Amount: math.OneUint()} items[i].OutboundParams = []*types.OutboundParams{{Amount: math.ZeroUint()}} + items[i].RevertOptions = types.NewEmptyRevertOptions() keeper.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, items[i]) } @@ -79,6 +80,8 @@ func createNCctx(keeper *keeper.Keeper, ctx sdk.Context, n int) []types.CrossCha items[i].ZetaFees = math.OneUint() items[i].Index = fmt.Sprintf("%d", i) + items[i].RevertOptions = types.NewEmptyRevertOptions() + keeper.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, items[i]) } return items diff --git a/x/crosschain/keeper/cctx_utils.go b/x/crosschain/keeper/cctx_utils.go index 0d6f760fda..3303a99c87 100644 --- a/x/crosschain/keeper/cctx_utils.go +++ b/x/crosschain/keeper/cctx_utils.go @@ -12,7 +12,7 @@ import ( "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" - zetaObserverTypes "github.com/zeta-chain/zetacore/x/observer/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // SetObserverOutboundInfo sets the CCTX outbound nonce to the next available nonce for the TSS address, and updates the nonce of blockchain state. @@ -20,7 +20,7 @@ import ( func (k Keeper) SetObserverOutboundInfo(ctx sdk.Context, receiveChainID int64, cctx *types.CrossChainTx) error { chain, found := k.GetObserverKeeper().GetSupportedChainFromChainID(ctx, receiveChainID) if !found { - return zetaObserverTypes.ErrSupportedChains + return observertypes.ErrSupportedChains } nonce, found := k.GetObserverKeeper().GetChainNonces(ctx, receiveChainID) @@ -67,6 +67,12 @@ func (k Keeper) SetObserverOutboundInfo(ctx sdk.Context, receiveChainID int64, c // GetRevertGasLimit returns the gas limit for the revert transaction in a CCTX // It returns 0 if there is no error but the gas limit can't be determined from the CCTX data func (k Keeper) GetRevertGasLimit(ctx sdk.Context, cctx types.CrossChainTx) (uint64, error) { + // with V2 protocol, reverts on connected chains can eventually call a onRevert function which can require a higher gas limit + if cctx.ProtocolContractVersion == types.ProtocolContractVersion_V2 && cctx.RevertOptions.CallOnRevert && + !cctx.RevertOptions.RevertGasLimit.IsZero() { + return cctx.RevertOptions.RevertGasLimit.Uint64(), nil + } + if cctx.InboundParams == nil { return 0, nil } diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index 5178b0a22a..2b46cedcb6 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -69,13 +69,29 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo return false, err } } else { - // cointype is Gas or ERC20; then it could be a ZRC20 deposit/depositAndCall cctx. - parsedAddress, data, err := chains.ParseAddressAndData(cctx.RelayedMessage) - if err != nil { - return false, errors.Wrap(types.ErrUnableToParseAddress, err.Error()) - } - if parsedAddress != (ethcommon.Address{}) { - to = parsedAddress + var ( + message []byte + err error + ) + + // in protocol version 1, the destination of the deposit is the first 20 bytes of the message when the message is not empty + // in protocol version 2, the destination of the deposit is always the to address, the message is the data to be sent to the contract + if cctx.ProtocolContractVersion == types.ProtocolContractVersion_V1 { + var parsedAddress ethcommon.Address + parsedAddress, message, err = chains.ParseAddressAndData(cctx.RelayedMessage) + if err != nil { + return false, errors.Wrap(types.ErrUnableToParseAddress, err.Error()) + } + if parsedAddress != (ethcommon.Address{}) { + to = parsedAddress + } + } else if cctx.ProtocolContractVersion == types.ProtocolContractVersion_V2 { + if len(cctx.RelayedMessage) > 0 { + message, err = hex.DecodeString(cctx.RelayedMessage) + if err != nil { + return false, errors.Wrap(types.ErrUnableToDecodeMessageString, err.Error()) + } + } } from, err := chains.DecodeAddressFromChainID(inboundSenderChainID, inboundSender, k.GetAuthorityKeeper().GetAdditionalChainList(ctx)) @@ -89,9 +105,10 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo to, inboundAmount, inboundSenderChainID, - data, + message, inboundCoinType, cctx.InboundParams.Asset, + cctx.ProtocolContractVersion, ) if fungibletypes.IsContractReverted(evmTxResponse, err) || errShouldRevertCctx(err) { return true, err @@ -120,7 +137,7 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute("action", "DepositZRC20AndCallContract"), sdk.NewAttribute("contract", to.String()), - sdk.NewAttribute("data", hex.EncodeToString(data)), + sdk.NewAttribute("data", hex.EncodeToString(message)), sdk.NewAttribute("cctxIndex", cctx.Index), ), ) diff --git a/x/crosschain/keeper/evm_deposit_test.go b/x/crosschain/keeper/evm_deposit_test.go index 121d42f988..18bc103bc3 100644 --- a/x/crosschain/keeper/evm_deposit_test.go +++ b/x/crosschain/keeper/evm_deposit_test.go @@ -67,7 +67,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { fungibleMock.On("ZETADepositAndCallContract", ctx, ethcommon.HexToAddress(sender.String()), receiver, senderChainId, amount, mock.Anything, mock.Anything). Return(nil, errDeposit) - // call HandleEVMDeposit + // call HandleEVMDeposit cctx.InboundParams.Sender = sender.String() cctx.GetCurrentOutboundParam().Receiver = receiver.String() @@ -106,6 +106,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, nil) // call HandleEVMDeposit @@ -151,6 +152,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{ Logs: []*evmtypes.Log{ { @@ -213,6 +215,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{ Logs: []*evmtypes.Log{ { @@ -302,6 +305,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, errDeposit) // call HandleEVMDeposit @@ -346,6 +350,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{VmError: "reverted"}, false, errDeposit) // call HandleEVMDeposit @@ -389,6 +394,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, fungibletypes.ErrForeignCoinCapReached) // call HandleEVMDeposit @@ -432,6 +438,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, fungibletypes.ErrPausedZRC20) // call HandleEVMDeposit @@ -475,6 +482,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { mock.Anything, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, fungibletypes.ErrCallNonContract) // call HandleEVMDeposit @@ -544,6 +552,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { data, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, nil) cctx.GetCurrentOutboundParam().Receiver = sample.EthAddress().String() @@ -586,6 +595,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { data, coin.CoinType_ERC20, mock.Anything, + mock.Anything, ).Return(&evmtypes.MsgEthereumTxResponse{}, false, nil) cctx := sample.CrossChainTx(t, "foo") diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 3f50b95f3b..a4564c5d03 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -3,7 +3,6 @@ package keeper import ( "encoding/base64" "encoding/hex" - "errors" "fmt" "math/big" @@ -14,14 +13,17 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/pkg/errors" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" - connectorzevm "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zetaconnectorzevm.sol" - zrc20 "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + connectorzevm "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/zetaconnectorzevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/pkg/constant" + "github.com/zeta-chain/zetacore/pkg/crypto" "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -64,10 +66,12 @@ func (k Keeper) PostTxProcessing( // - clear the logs // TODO: implement unit tests // https://github.com/zeta-chain/node/issues/1759 +// TODO: refactor and simplify +// https://github.com/zeta-chain/node/issues/2627 func (k Keeper) ProcessLogs( ctx sdk.Context, logs []*ethtypes.Log, - emittingContract ethcommon.Address, + emittingAddress ethcommon.Address, txOrigin string, ) error { system, found := k.fungibleKeeper.GetSystemContract(ctx) @@ -78,47 +82,71 @@ func (k Keeper) ProcessLogs( if connectorZEVMAddr == (ethcommon.Address{}) { return fmt.Errorf("connectorZEVM address is empty") } + gatewayAddr := ethcommon.HexToAddress(system.Gateway) + // read the logs and process inbounds from emitted events + // run the processing for the v1 and the v2 protocol contracts for _, log := range logs { - eventZrc20Withdrawal, errZrc20 := ParseZRC20WithdrawalEvent(*log) - eventZetaSent, errZetaSent := ParseZetaSentEvent(*log, connectorZEVMAddr) - if errZrc20 != nil && errZetaSent != nil { - // This log does not contain any of the two events - continue + if !crypto.IsEmptyAddress(gatewayAddr) { + if err := k.ProcessZEVMInboundV2(ctx, log, gatewayAddr, emittingAddress, txOrigin); err != nil { + return errors.Wrap(err, "failed to process ZEVM inbound V2") + } } - if eventZrc20Withdrawal != nil && eventZetaSent != nil { - // This log contains both events, this is not possible - ctx.Logger(). - Error(fmt.Sprintf("ProcessLogs: log contains both ZRC20Withdrawal and ZetaSent events, %s , %s", log.Topics, log.Data)) - continue + if err := k.ProcessZEVMInboundV1(ctx, log, connectorZEVMAddr, emittingAddress, txOrigin); err != nil { + return errors.Wrap(err, "failed to process ZEVM inbound V1") } + } - // if eventZrc20Withdrawal is not nil we will try to validate it and see if it can be processed - if eventZrc20Withdrawal != nil { - // Check if the contract is a registered ZRC20 contract. If its not a registered ZRC20 contract, we can discard this event as it is not relevant - coin, foundCoin := k.fungibleKeeper.GetForeignCoins(ctx, eventZrc20Withdrawal.Raw.Address.Hex()) - if !foundCoin { - ctx.Logger(). - Info(fmt.Sprintf("cannot find foreign coin with contract address %s", eventZrc20Withdrawal.Raw.Address.Hex())) - continue - } + return nil +} - // If Validation fails, we will not process the event and return and error. This condition means that the event was correct, and emitted from a registered ZRC20 contract - // But the information entered by the user is incorrect. In this case we can return an error and roll back the transaction - if err := k.ValidateZrc20WithdrawEvent(ctx, eventZrc20Withdrawal, coin.ForeignChainId); err != nil { - return err - } - // If the event is valid, we will process it and create a new CCTX - // If the process fails, we will return an error and roll back the transaction - if err := k.ProcessZRC20WithdrawalEvent(ctx, eventZrc20Withdrawal, emittingContract, txOrigin); err != nil { - return err - } +// ProcessZEVMInboundV1 processes the logs emitted by the zEVM contract for V1 protocol contracts +// it parses logs from Connector and ZRC20 contracts and processes them accordingly +func (k Keeper) ProcessZEVMInboundV1( + ctx sdk.Context, + log *ethtypes.Log, + connectorZEVMAddr, + emittingAddress ethcommon.Address, + txOrigin string, +) error { + eventZRC20Withdrawal, errZrc20 := ParseZRC20WithdrawalEvent(*log) + eventZETASent, errZetaSent := ParseZetaSentEvent(*log, connectorZEVMAddr) + if errZrc20 != nil && errZetaSent != nil { + // This log does not contain any of the two events + return nil + } + if eventZRC20Withdrawal != nil && eventZETASent != nil { + // This log contains both events, this is not possible + ctx.Logger(). + Error(fmt.Sprintf("ProcessLogs: log contains both ZRC20Withdrawal and ZetaSent events, %s , %s", log.Topics, log.Data)) + return nil + } + + // if eventZrc20Withdrawal is not nil we will try to validate it and see if it can be processed + if eventZRC20Withdrawal != nil { + // Check if the contract is a registered ZRC20 contract. If its not a registered ZRC20 contract, we can discard this event as it is not relevant + coin, foundCoin := k.fungibleKeeper.GetForeignCoins(ctx, eventZRC20Withdrawal.Raw.Address.Hex()) + if !foundCoin { + ctx.Logger(). + Info(fmt.Sprintf("cannot find foreign coin with contract address %s", eventZRC20Withdrawal.Raw.Address.Hex())) + return nil } - // if eventZetaSent is not nil we will try to validate it and see if it can be processed - if eventZetaSent != nil { - if err := k.ProcessZetaSentEvent(ctx, eventZetaSent, emittingContract, txOrigin); err != nil { - return err - } + + // If Validation fails, we will not process the event and return and error. This condition means that the event was correct, and emitted from a registered ZRC20 contract + // But the information entered by the user is incorrect. In this case we can return an error and roll back the transaction + if err := k.ValidateZrc20WithdrawEvent(ctx, eventZRC20Withdrawal, coin.ForeignChainId); err != nil { + return err + } + // If the event is valid, we will process it and create a new CCTX + // If the process fails, we will return an error and roll back the transaction + if err := k.ProcessZRC20WithdrawalEvent(ctx, eventZRC20Withdrawal, emittingAddress, txOrigin); err != nil { + return err + } + } + // if eventZetaSent is not nil we will try to validate it and see if it can be processed + if eventZETASent != nil { + if err := k.ProcessZetaSentEvent(ctx, eventZETASent, emittingAddress, txOrigin); err != nil { + return err } } return nil @@ -178,6 +206,7 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( foreignCoin.CoinType, foreignCoin.Asset, event.Raw.Index, + types.ProtocolContractVersion_V1, ) cctx, err := k.ValidateInbound(ctx, msg, false) @@ -241,6 +270,7 @@ func (k Keeper) ProcessZetaSentEvent( amount := math.NewUintFromBigInt(event.ZetaValueAndGas) messageString := base64.StdEncoding.EncodeToString(event.Message) + // Bump gasLimit by event index (which is very unlikely to be larger than 1000) to always have different ZetaSent events msgs. msg := types.NewMsgVoteInbound( "", @@ -256,6 +286,7 @@ func (k Keeper) ProcessZetaSentEvent( coin.CoinType_Zeta, "", event.Raw.Index, + types.ProtocolContractVersion_V1, ) cctx, err := k.ValidateInbound(ctx, msg, true) @@ -275,36 +306,41 @@ func (k Keeper) ProcessZetaSentEvent( // It verifies event information for BTC chains and returns an error if the event is invalid func (k Keeper) ValidateZrc20WithdrawEvent(ctx sdk.Context, event *zrc20.ZRC20Withdrawal, chainID int64) error { // The event was parsed; that means the user has deposited tokens to the contract. + return k.validateZRC20Withdrawal(ctx, chainID, event.Value, event.To) +} +// validateZRC20Withdrawal validates the data of a ZRC20 Withdrawal event (version 1 or 2) +// it checks if the withdrawal amount is valid and the destination address is supported depending on the chain +func (k Keeper) validateZRC20Withdrawal(ctx sdk.Context, chainID int64, value *big.Int, to []byte) error { additionalChains := k.GetAuthorityKeeper().GetAdditionalChainList(ctx) if chains.IsBitcoinChain(chainID, additionalChains) { - if event.Value.Cmp(big.NewInt(constant.BTCWithdrawalDustAmount)) < 0 { + if value.Cmp(big.NewInt(constant.BTCWithdrawalDustAmount)) < 0 { return errorsmod.Wrapf( types.ErrInvalidWithdrawalAmount, "withdraw amount %s is less than dust amount %d", - event.Value.String(), + value.String(), constant.BTCWithdrawalDustAmount, ) } - addr, err := chains.DecodeBtcAddress(string(event.To), chainID) + addr, err := chains.DecodeBtcAddress(string(to), chainID) if err != nil { - return errorsmod.Wrapf(types.ErrInvalidAddress, "invalid address %s", string(event.To)) + return errorsmod.Wrapf(types.ErrInvalidAddress, "invalid address %s", string(to)) } if !chains.IsBtcAddressSupported(addr) { - return errorsmod.Wrapf(types.ErrInvalidAddress, "unsupported address %s", string(event.To)) + return errorsmod.Wrapf(types.ErrInvalidAddress, "unsupported address %s", string(to)) } } else if chains.IsSolanaChain(chainID, additionalChains) { - if event.Value.Cmp(big.NewInt(constant.SolanaWalletRentExempt)) < 0 { + if value.Cmp(big.NewInt(constant.SolanaWalletRentExempt)) < 0 { return errorsmod.Wrapf( types.ErrInvalidWithdrawalAmount, "withdraw amount %s is less than rent exempt %d", - event.Value.String(), + value.String(), constant.SolanaWalletRentExempt, ) } - _, err := chains.DecodeSolanaWalletAddress(string(event.To)) + _, err := chains.DecodeSolanaWalletAddress(string(to)) if err != nil { - return errorsmod.Wrapf(types.ErrInvalidAddress, "invalid address %s", string(event.To)) + return errorsmod.Wrapf(types.ErrInvalidAddress, "invalid address %s", string(to)) } } diff --git a/x/crosschain/keeper/gas_payment.go b/x/crosschain/keeper/gas_payment.go index a5a9b7d0fd..2a8331e059 100644 --- a/x/crosschain/keeper/gas_payment.go +++ b/x/crosschain/keeper/gas_payment.go @@ -169,11 +169,19 @@ func (k Keeper) PayGasInERC20AndUpdateCctx( if _, found := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, chainID); !found { return observertypes.ErrSupportedChains } + // get gas params gas, err := k.ChainGasParams(ctx, chainID) if err != nil { return cosmoserrors.Wrap(types.ErrCannotFindGasParams, err.Error()) } + + // with V2 protocol, reverts on connected chains can eventually call a onRevert function which can require a higher gas limit + if cctx.ProtocolContractVersion == types.ProtocolContractVersion_V2 && cctx.RevertOptions.CallOnRevert && + !cctx.RevertOptions.RevertGasLimit.IsZero() { + gas.GasLimit = cctx.RevertOptions.RevertGasLimit + } + outTxGasFee := gas.GasLimit.Mul(gas.GasPrice).Add(gas.ProtocolFlatFee) // get address of the zrc20 fc, found := k.fungibleKeeper.GetForeignCoinFromAsset(ctx, cctx.InboundParams.Asset, chainID) diff --git a/x/crosschain/keeper/grpc_query_cctx_test.go b/x/crosschain/keeper/grpc_query_cctx_test.go index 77e9bbc175..6112d5e58c 100644 --- a/x/crosschain/keeper/grpc_query_cctx_test.go +++ b/x/crosschain/keeper/grpc_query_cctx_test.go @@ -283,5 +283,8 @@ func TestKeeper_CctxByNonce(t *testing.T) { }) require.NoError(t, err) require.Equal(t, cctx, res.CrossChainTx) + + // ensure that LastUpdateTimestamp is set to current block time + require.Equal(t, res.CrossChainTx.CctxStatus.LastUpdateTimestamp, ctx.BlockTime().Unix()) }) } diff --git a/x/crosschain/keeper/grpc_query_inbound_hash_to_cctx_test.go b/x/crosschain/keeper/grpc_query_inbound_hash_to_cctx_test.go index f1b2862520..c99bd9d9fc 100644 --- a/x/crosschain/keeper/grpc_query_inbound_hash_to_cctx_test.go +++ b/x/crosschain/keeper/grpc_query_inbound_hash_to_cctx_test.go @@ -135,6 +135,7 @@ func createInTxHashToCctxWithCctxs(keeper *crosschainkeeper.Keeper, ctx sdk.Cont cctxs[i].ZetaFees = math.OneUint() cctxs[i].InboundParams = &types.InboundParams{ObservedHash: fmt.Sprintf("%d", i), Amount: math.OneUint()} cctxs[i].CctxStatus = &types.Status{Status: types.CctxStatus_PendingInbound} + cctxs[i].RevertOptions = types.NewEmptyRevertOptions() keeper.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctxs[i]) } diff --git a/x/crosschain/keeper/initiate_outbound_test.go b/x/crosschain/keeper/initiate_outbound_test.go index f4f24a3760..05da1daa0e 100644 --- a/x/crosschain/keeper/initiate_outbound_test.go +++ b/x/crosschain/keeper/initiate_outbound_test.go @@ -109,10 +109,10 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal( + require.Contains( t, - "chain not supported", cctx.CctxStatus.StatusMessage, + "chain not supported", ) }, ) @@ -149,10 +149,10 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal( + require.Contains( t, - "GetRevertGasLimit: foreign coin not found for sender chain", cctx.CctxStatus.StatusMessage, + "GetRevertGasLimit: foreign coin not found for sender chain", ) }) @@ -192,10 +192,10 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal( + require.Contains( t, - "chain not supported", cctx.CctxStatus.StatusMessage, + "chain not supported", ) }, ) @@ -237,10 +237,10 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal( + require.Contains( t, - "chain not supported", cctx.CctxStatus.StatusMessage, + "chain not supported", ) }, ) diff --git a/x/crosschain/keeper/msg_server_abort_stuck_cctx_test.go b/x/crosschain/keeper/msg_server_abort_stuck_cctx_test.go index cdc38b36f2..5e6df45d9b 100644 --- a/x/crosschain/keeper/msg_server_abort_stuck_cctx_test.go +++ b/x/crosschain/keeper/msg_server_abort_stuck_cctx_test.go @@ -74,6 +74,8 @@ func TestMsgServer_AbortStuckCCTX(t *testing.T) { require.True(t, found) require.Equal(t, crosschaintypes.CctxStatus_Aborted, cctxFound.CctxStatus.Status) require.Equal(t, crosschainkeeper.AbortMessage, cctxFound.CctxStatus.StatusMessage) + // ensure the last update timestamp is updated + require.Equal(t, cctxFound.CctxStatus.LastUpdateTimestamp, ctx.BlockTime().Unix()) }) t.Run("can abort a cctx in pending revert", func(t *testing.T) { diff --git a/x/crosschain/keeper/msg_server_add_inbound_tracker.go b/x/crosschain/keeper/msg_server_add_inbound_tracker.go index ae35036e88..13ad2bad1b 100644 --- a/x/crosschain/keeper/msg_server_add_inbound_tracker.go +++ b/x/crosschain/keeper/msg_server_add_inbound_tracker.go @@ -60,7 +60,7 @@ func (k msgServer) AddInboundTracker( func verifyProofAndInboundBody(ctx sdk.Context, k msgServer, msg *types.MsgAddInboundTracker) error { txBytes, err := k.GetLightclientKeeper().VerifyProof(ctx, msg.Proof, msg.ChainId, msg.BlockHash, msg.TxIndex) if err != nil { - return types.ErrProofVerificationFail.Wrapf(err.Error()) + return types.ErrProofVerificationFail.Wrap(err.Error()) } // get chain params and tss addresses to verify the inTx body @@ -72,14 +72,14 @@ func verifyProofAndInboundBody(ctx sdk.Context, k msgServer, msg *types.MsgAddIn BitcoinChainId: msg.ChainId, }) if err != nil { - return observertypes.ErrTssNotFound.Wrapf(err.Error()) + return observertypes.ErrTssNotFound.Wrap(err.Error()) } if tss == nil { return observertypes.ErrTssNotFound.Wrapf("tss address nil") } if err := types.VerifyInboundBody(*msg, txBytes, *chainParams, *tss); err != nil { - return types.ErrTxBodyVerificationFail.Wrapf(err.Error()) + return types.ErrTxBodyVerificationFail.Wrap(err.Error()) } return nil diff --git a/x/crosschain/keeper/msg_server_add_outbound_tracker.go b/x/crosschain/keeper/msg_server_add_outbound_tracker.go index b82fba7f79..9fdf5ac48d 100644 --- a/x/crosschain/keeper/msg_server_add_outbound_tracker.go +++ b/x/crosschain/keeper/msg_server_add_outbound_tracker.go @@ -132,7 +132,7 @@ func (k msgServer) AddOutboundTracker( func verifyProofAndOutboundBody(ctx sdk.Context, k msgServer, msg *types.MsgAddOutboundTracker) error { txBytes, err := k.lightclientKeeper.VerifyProof(ctx, msg.Proof, msg.ChainId, msg.BlockHash, msg.TxIndex) if err != nil { - return types.ErrProofVerificationFail.Wrapf(err.Error()) + return types.ErrProofVerificationFail.Wrap(err.Error()) } // get tss address @@ -145,14 +145,14 @@ func verifyProofAndOutboundBody(ctx sdk.Context, k msgServer, msg *types.MsgAddO BitcoinChainId: bitcoinChainID, }) if err != nil { - return observertypes.ErrTssNotFound.Wrapf(err.Error()) + return observertypes.ErrTssNotFound.Wrap(err.Error()) } if tss == nil { return observertypes.ErrTssNotFound.Wrapf("tss address nil") } if err := types.VerifyOutboundBody(*msg, txBytes, *tss); err != nil { - return types.ErrTxBodyVerificationFail.Wrapf(err.Error()) + return types.ErrTxBodyVerificationFail.Wrap(err.Error()) } return nil diff --git a/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds.go b/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds.go new file mode 100644 index 0000000000..56bc0a5ea1 --- /dev/null +++ b/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds.go @@ -0,0 +1,103 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// MigrateERC20CustodyFunds migrates the funds from the current ERC20Custody contract to the new ERC20Custody contract +func (k msgServer) MigrateERC20CustodyFunds( + goCtx context.Context, + msg *types.MsgMigrateERC20CustodyFunds, +) (*types.MsgMigrateERC20CustodyFundsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // check if authorized + err := k.GetAuthorityKeeper().CheckAuthorization(ctx, msg) + if err != nil { + return nil, errorsmod.Wrap(authoritytypes.ErrUnauthorized, err.Error()) + } + + // get the current TSS nonce allow to set a unique index for the CCTX + chainNonce, found := k.GetObserverKeeper().GetChainNonces(ctx, msg.ChainId) + if !found { + return nil, errorsmod.Wrap(types.ErrInvalidChainID, "cannot find current chain nonce") + } + currentNonce := chainNonce.Nonce + + // get the current TSS + tss, found := k.GetObserverKeeper().GetTSS(ctx) + if !found { + return nil, errorsmod.Wrap(types.ErrCannotFindTSSKeys, "cannot find current TSS") + } + + // get necessary parameters to create the cctx + params, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, msg.ChainId) + if !found { + return nil, errorsmod.Wrapf(types.ErrInvalidChainID, "chain params not found for chain id (%d)", msg.ChainId) + } + medianGasPrice, priorityFee, isFound := k.GetMedianGasValues(ctx, msg.ChainId) + if !isFound { + return nil, errorsmod.Wrapf( + types.ErrUnableToGetGasPrice, + "median gas price not found for chain id (%d)", + msg.ChainId, + ) + } + + // overpays gas price by 2x + medianGasPrice = medianGasPrice.MulUint64(types.ERC20CustodyMigrationGasMultiplierEVM) + priorityFee = priorityFee.MulUint64(types.ERC20CustodyMigrationGasMultiplierEVM) + + // should not happen + if priorityFee.GT(medianGasPrice) { + return nil, errorsmod.Wrapf( + types.ErrInvalidGasAmount, + "priorityFee %s is greater than median gasPrice %s", + priorityFee.String(), + medianGasPrice.String(), + ) + } + + // create the CCTX that allows to sign the fund migration + cctx := types.MigrateERC20CustodyFundsCmdCCTX( + msg.Creator, + msg.Erc20Address, + params.Erc20CustodyContractAddress, + msg.NewCustodyAddress, + msg.ChainId, + msg.Amount, + medianGasPrice.String(), + priorityFee.String(), + tss.TssPubkey, + currentNonce, + ) + + // save the cctx + err = k.SetObserverOutboundInfo(ctx, msg.ChainId, &cctx) + if err != nil { + return nil, err + } + k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx) + + err = ctx.EventManager().EmitTypedEvent( + &types.EventERC20CustodyFundsMigration{ + NewCustodyAddress: msg.NewCustodyAddress, + Erc20Address: msg.Erc20Address, + Amount: msg.Amount.String(), + CctxIndex: cctx.Index, + }, + ) + if err != nil { + return nil, errorsmod.Wrapf(err, "failed to emit event") + } + + return &types.MsgMigrateERC20CustodyFundsResponse{ + CctxIndex: cctx.Index, + }, nil +} diff --git a/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds_test.go b/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds_test.go new file mode 100644 index 0000000000..511ac5cdec --- /dev/null +++ b/x/crosschain/keeper/msg_server_migrate_erc20_custody_funds_test.go @@ -0,0 +1,338 @@ +package keeper_test + +import ( + "errors" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/constant" + testkeeper "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" + "github.com/zeta-chain/zetacore/x/crosschain/keeper" + "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "testing" +) + +func TestKeeper_MigrateERC20CustodyFunds(t *testing.T) { + t.Run("can create CCTX to migrate ERC20 custody funds", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + medianGasPrice, priorityFee, isFound := k.GetMedianGasValues(ctx, msg.ChainId) + require.True(t, isFound) + + // ACT + res, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.NoError(t, err) + + // check CCTX is created + cctx, found := k.GetCrossChainTx(ctx, res.CctxIndex) + require.True(t, found) + require.Equal(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Contains(t, cctx.RelayedMessage, constant.CmdMigrateERC20CustodyFunds) + require.Len(t, cctx.OutboundParams, 1) + require.EqualValues( + t, + medianGasPrice.MulUint64(types.ERC20CustodyMigrationGasMultiplierEVM).String(), + cctx.OutboundParams[0].GasPrice, + ) + require.EqualValues( + t, + priorityFee.MulUint64(types.ERC20CustodyMigrationGasMultiplierEVM).String(), + cctx.OutboundParams[0].GasPriorityFee, + ) + }) + + t.Run("should fail if not authorized", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + msgServer := keeper.NewMsgServerImpl(*k) + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: getValidEthChain().ChainId, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, errors.New("not authorized")) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, authoritytypes.ErrUnauthorized) + }) + + t.Run("should fail if can't find chain nonces", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + //zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) // not set + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrInvalidChainID) + }) + + t.Run("should fail if can't find current TSS", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + //zk.ObserverKeeper.SetTSS(ctx, tss) // not set + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrCannotFindTSSKeys) + }) + + t.Run("should fail if can't find chain params", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{}) // not set + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrInvalidChainID) + }) + + t.Run("should fail if can't find gas price", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + //k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) // not set + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrUnableToGetGasPrice) + }) + + t.Run("should fail if priority fees higher than gas price", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, types.GasPrice{ + Creator: sample.AccAddress(), + ChainId: chainID, + Signers: []string{sample.AccAddress()}, + BlockNums: []uint64{42}, + Prices: []uint64{42}, + PriorityFees: []uint64{43}, + MedianIndex: 0, + }) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrInvalidGasAmount) + }) + + t.Run("should fail if can't set outbound info", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chainID, + NewCustodyAddress: sample.EthAddress().Hex(), + Erc20Address: sample.EthAddress().Hex(), + Amount: sample.UintInRange(42, 100), + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{ + sample.ChainParams(chainID), + }, // set non supported chain params to fail + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.MigrateERC20CustodyFunds(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, observertypes.ErrSupportedChains) + }) +} diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds.go b/x/crosschain/keeper/msg_server_migrate_tss_funds.go index ed8c51b93d..7d48b48cdb 100644 --- a/x/crosschain/keeper/msg_server_migrate_tss_funds.go +++ b/x/crosschain/keeper/msg_server_migrate_tss_funds.go @@ -2,7 +2,6 @@ package keeper import ( "context" - "fmt" "sort" errorsmod "cosmossdk.io/errors" @@ -11,13 +10,7 @@ import ( tmtypes "github.com/cometbft/cometbft/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/crypto" - "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/coin" - "github.com/zeta-chain/zetacore/pkg/constant" - zetacrypto "github.com/zeta-chain/zetacore/pkg/crypto" - "github.com/zeta-chain/zetacore/pkg/gas" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -72,7 +65,7 @@ func (k msgServer) MigrateTssFunds( return nil, errorsmod.Wrap(types.ErrCannotMigrateTssFunds, "cannot migrate funds when there are pending nonces") } - err = k.MigrateTSSFundsForChain(ctx, msg.ChainId, msg.Amount, tss, tssHistory) + err = k.initiateMigrateTSSFundsCCTX(ctx, msg.Creator, msg.ChainId, msg.Amount, tss, tssHistory) if err != nil { return nil, errorsmod.Wrap(types.ErrCannotMigrateTssFunds, err.Error()) } @@ -80,8 +73,10 @@ func (k msgServer) MigrateTssFunds( return &types.MsgMigrateTssFundsResponse{}, nil } -func (k Keeper) MigrateTSSFundsForChain( +// initiateMigrateTSSFundsCCTX sets the CCTX for migrating the funds to initiate the migration outbound +func (k Keeper) initiateMigrateTSSFundsCCTX( ctx sdk.Context, + creator string, chainID int64, amount sdkmath.Uint, currentTss observertypes.TSS, @@ -93,126 +88,24 @@ func (k Keeper) MigrateTSSFundsForChain( if !isFound { return types.ErrUnableToGetGasPrice } - indexString := GetIndexStringForTssMigration( - currentTss.TssPubkey, - newTss.TssPubkey, + + // initialize the cmd CCTX + cctx, err := types.MigrateFundCmdCCTX( + ctx.BlockHeight(), + creator, + tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()).String(), chainID, amount, - ctx.BlockHeight(), + medianGasPrice, + priorityFee, + currentTss.TssPubkey, + newTss.TssPubkey, + k.GetAuthorityKeeper().GetAdditionalChainList(ctx), ) - - hash := crypto.Keccak256Hash([]byte(indexString)) - index := hash.Hex() - - // TODO : Use the `NewCCTX` method to create the cctx - // https://github.com/zeta-chain/node/issues/1909 - cctx := types.CrossChainTx{ - Creator: "", - Index: index, - ZetaFees: sdkmath.Uint{}, - RelayedMessage: fmt.Sprintf("%s:%s", constant.CmdMigrateTssFunds, "Funds Migrator Admin Cmd"), - CctxStatus: &types.Status{ - Status: types.CctxStatus_PendingOutbound, - StatusMessage: "", - LastUpdateTimestamp: 0, - }, - InboundParams: &types.InboundParams{ - Sender: "", - SenderChainId: chainID, - TxOrigin: "", - CoinType: coin.CoinType_Cmd, - Asset: "", - Amount: amount, - ObservedHash: tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()).String(), - ObservedExternalHeight: 0, - BallotIndex: "", - FinalizedZetaHeight: 0, - }, - OutboundParams: []*types.OutboundParams{{ - Receiver: "", - ReceiverChainId: chainID, - CoinType: coin.CoinType_Cmd, - Amount: amount, - TssNonce: 0, - GasLimit: 1_000_000, - GasPrice: medianGasPrice.MulUint64(2).String(), - GasPriorityFee: priorityFee.MulUint64(2).String(), - Hash: "", - BallotIndex: "", - ObservedExternalHeight: 0, - GasUsed: 0, - EffectiveGasPrice: sdkmath.Int{}, - EffectiveGasLimit: 0, - TssPubkey: currentTss.TssPubkey, - }}, - } - - // retrieve from authority keeper additional chains - additionalChains := k.GetAuthorityKeeper().GetAdditionalChainList(ctx) - - // Set the sender and receiver addresses for EVM chain - if chains.IsEVMChain(chainID, additionalChains) { - ethAddressOld, err := zetacrypto.GetTssAddrEVM(currentTss.TssPubkey) - if err != nil { - return err - } - ethAddressNew, err := zetacrypto.GetTssAddrEVM(newTss.TssPubkey) - if err != nil { - return err - } - cctx.InboundParams.Sender = ethAddressOld.String() - cctx.GetCurrentOutboundParam().Receiver = ethAddressNew.String() - // Tss migration is a send transaction, so the gas limit is set to 21000 - cctx.GetCurrentOutboundParam().GasLimit = gas.EVMSend - // Multiple current gas price with standard multiplier to add some buffer - multipliedGasPrice, err := gas.MultiplyGasPrice(medianGasPrice, types.TssMigrationGasMultiplierEVM) - if err != nil { - return err - } - evmFee := sdkmath.NewUint(cctx.GetCurrentOutboundParam().GasLimit).Mul(multipliedGasPrice) - if evmFee.GT(amount) { - return errorsmod.Wrap( - types.ErrInsufficientFundsTssMigration, - fmt.Sprintf( - "insufficient funds to pay for gas fee, amount: %s, gas fee: %s, chainid: %d", - amount.String(), - evmFee.String(), - chainID, - ), - ) - } - - cctx.GetCurrentOutboundParam().GasPrice = multipliedGasPrice.String() - cctx.GetCurrentOutboundParam().Amount = amount.Sub( - evmFee.Add(sdkmath.NewUintFromString(types.TSSMigrationBufferAmountEVM)), - ) - } - // Set the sender and receiver addresses for Bitcoin chain - if chains.IsBitcoinChain(chainID, additionalChains) { - bitcoinNetParams, err := chains.BitcoinNetParamsFromChainID(chainID) - if err != nil { - return err - } - btcAddressOld, err := zetacrypto.GetTssAddrBTC(currentTss.TssPubkey, bitcoinNetParams) - if err != nil { - return err - } - btcAddressNew, err := zetacrypto.GetTssAddrBTC(newTss.TssPubkey, bitcoinNetParams) - if err != nil { - return err - } - cctx.InboundParams.Sender = btcAddressOld - cctx.GetCurrentOutboundParam().Receiver = btcAddressNew - } - - if cctx.GetCurrentOutboundParam().Receiver == "" { - return errorsmod.Wrap(types.ErrReceiverIsEmpty, fmt.Sprintf("chain %d is not supported", chainID)) - } - - err := k.SetObserverOutboundInfo(ctx, chainID, &cctx) if err != nil { return err } + // The migrate funds can be run again to update the migration cctx index if the migration fails // This should be used after carefully calculating the amount again existingMigrationInfo, found := k.zetaObserverKeeper.GetFundMigrator(ctx, chainID) @@ -238,18 +131,9 @@ func (k Keeper) MigrateTSSFundsForChain( k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx) k.zetaObserverKeeper.SetFundMigrator(ctx, observertypes.TssFundMigratorInfo{ ChainId: chainID, - MigrationCctxIndex: index, + MigrationCctxIndex: cctx.Index, }) EmitEventInboundFinalized(ctx, &cctx) return nil } - -func GetIndexStringForTssMigration( - currentTssPubkey, newTssPubkey string, - chainID int64, - amount sdkmath.Uint, - height int64, -) string { - return fmt.Sprintf("%s-%s-%d-%s-%d", currentTssPubkey, newTssPubkey, chainID, amount.String(), height) -} diff --git a/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go b/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go index 1896a35f59..a715fcf4e6 100644 --- a/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go +++ b/x/crosschain/keeper/msg_server_migrate_tss_funds_test.go @@ -74,7 +74,7 @@ func setupTssMigrationParams( ChainId: chain.ChainId, Nonce: 1, }) - indexString := keeper.GetIndexStringForTssMigration( + indexString := crosschaintypes.GetTssMigrationCCTXIndexString( currentTss.TssPubkey, newTss.TssPubkey, chain.ChainId, @@ -110,10 +110,12 @@ func TestKeeper_MigrateTSSFundsForChain(t *testing.T) { keepertest.MockGetChainListEmpty(&authorityMock.Mock) _, err := msgServer.MigrateTssFunds(ctx, &msg) require.NoError(t, err) + hash := crypto.Keccak256Hash([]byte(indexString)) index := hash.Hex() cctx, found := k.GetCrossChainTx(ctx, index) require.True(t, found) + multipliedValue, err := gas.MultiplyGasPrice(gp, crosschaintypes.TssMigrationGasMultiplierEVM) require.NoError(t, err) require.Equal(t, multipliedValue.String(), cctx.GetCurrentOutboundParam().GasPrice) diff --git a/x/crosschain/keeper/msg_server_refund_aborted_tx.go b/x/crosschain/keeper/msg_server_refund_aborted_tx.go index 201b507af6..fb37dd1c4c 100644 --- a/x/crosschain/keeper/msg_server_refund_aborted_tx.go +++ b/x/crosschain/keeper/msg_server_refund_aborted_tx.go @@ -72,7 +72,7 @@ func (k msgServer) RefundAbortedCCTX( commit() // set the cctx as refunded - cctx.CctxStatus.AbortRefunded(ctx.BlockTime().Unix()) + cctx.CctxStatus.AbortRefunded() k.SetCrossChainTx(ctx, cctx) diff --git a/x/crosschain/keeper/msg_server_refund_aborted_tx_test.go b/x/crosschain/keeper/msg_server_refund_aborted_tx_test.go index 91ef323ad9..5e380acc81 100644 --- a/x/crosschain/keeper/msg_server_refund_aborted_tx_test.go +++ b/x/crosschain/keeper/msg_server_refund_aborted_tx_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "github.com/zeta-chain/zetacore/pkg/constant" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -449,7 +450,7 @@ func TestMsgServer_RefundAbortedCCTX(t *testing.T) { msg := crosschaintypes.MsgRefundAbortedCCTX{ Creator: admin, CctxIndex: cctx.Index, - RefundAddress: "0x0000000000000000000000000000000000000000", + RefundAddress: constant.EVMZeroAddress, } keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) _, err := msgServer.RefundAbortedCCTX(ctx, &msg) diff --git a/x/crosschain/keeper/msg_server_update_erc20_custody_pause_status.go b/x/crosschain/keeper/msg_server_update_erc20_custody_pause_status.go new file mode 100644 index 0000000000..4cda11f633 --- /dev/null +++ b/x/crosschain/keeper/msg_server_update_erc20_custody_pause_status.go @@ -0,0 +1,100 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// UpdateERC20CustodyPauseStatus creates a admin cmd cctx to update the pause status of the ERC20 custody contract +func (k msgServer) UpdateERC20CustodyPauseStatus( + goCtx context.Context, + msg *types.MsgUpdateERC20CustodyPauseStatus, +) (*types.MsgUpdateERC20CustodyPauseStatusResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // check if authorized + err := k.GetAuthorityKeeper().CheckAuthorization(ctx, msg) + if err != nil { + return nil, errorsmod.Wrap(authoritytypes.ErrUnauthorized, err.Error()) + } + + // get the current TSS nonce allow to set a unique index for the CCTX + chainNonce, found := k.GetObserverKeeper().GetChainNonces(ctx, msg.ChainId) + if !found { + return nil, errorsmod.Wrap(types.ErrInvalidChainID, "cannot find current chain nonce") + } + currentNonce := chainNonce.Nonce + + // get the current TSS + tss, found := k.GetObserverKeeper().GetTSS(ctx) + if !found { + return nil, errorsmod.Wrap(types.ErrCannotFindTSSKeys, "cannot find current TSS") + } + + // get necessary parameters to create the cctx + params, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, msg.ChainId) + if !found { + return nil, errorsmod.Wrapf(types.ErrInvalidChainID, "chain params not found for chain id (%d)", msg.ChainId) + } + medianGasPrice, priorityFee, isFound := k.GetMedianGasValues(ctx, msg.ChainId) + if !isFound { + return nil, errorsmod.Wrapf( + types.ErrUnableToGetGasPrice, + "median gas price not found for chain id (%d)", + msg.ChainId, + ) + } + + // overpays gas price by 2x + medianGasPrice = medianGasPrice.MulUint64(types.ERC20CustodyPausingGasMultiplierEVM) + priorityFee = priorityFee.MulUint64(types.ERC20CustodyPausingGasMultiplierEVM) + + // should not happen + if priorityFee.GT(medianGasPrice) { + return nil, errorsmod.Wrapf( + types.ErrInvalidGasAmount, + "priorityFee %s is greater than median gasPrice %s", + priorityFee.String(), + medianGasPrice.String(), + ) + } + + // create the CCTX that allows to sign the ERC20 custody pause status update + cctx := types.UpdateERC20CustodyPauseStatusCmdCCTX( + msg.Creator, + params.Erc20CustodyContractAddress, + msg.ChainId, + msg.Pause, + medianGasPrice.String(), + priorityFee.String(), + tss.TssPubkey, + currentNonce, + ) + + // save the cctx + err = k.SetObserverOutboundInfo(ctx, msg.ChainId, &cctx) + if err != nil { + return nil, err + } + k.SetCctxAndNonceToCctxAndInboundHashToCctx(ctx, cctx) + + err = ctx.EventManager().EmitTypedEvent( + &types.EventERC20CustodyPausing{ + ChainId: msg.ChainId, + Pause: msg.Pause, + CctxIndex: cctx.Index, + }, + ) + if err != nil { + return nil, errorsmod.Wrapf(err, "failed to emit event") + } + + return &types.MsgUpdateERC20CustodyPauseStatusResponse{ + CctxIndex: cctx.Index, + }, nil +} diff --git a/x/crosschain/keeper/msg_server_update_erc20_custody_pause_status_test.go b/x/crosschain/keeper/msg_server_update_erc20_custody_pause_status_test.go new file mode 100644 index 0000000000..a7b3db78bf --- /dev/null +++ b/x/crosschain/keeper/msg_server_update_erc20_custody_pause_status_test.go @@ -0,0 +1,322 @@ +package keeper_test + +import ( + "errors" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/constant" + testkeeper "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" + "github.com/zeta-chain/zetacore/x/crosschain/keeper" + "github.com/zeta-chain/zetacore/x/crosschain/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "testing" +) + +func TestKeeper_UpdateERC20CustodyPauseStatus(t *testing.T) { + t.Run("can create CCTX to pause ERC20 custody pause status", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chainID, + Pause: true, + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + medianGasPrice, priorityFee, isFound := k.GetMedianGasValues(ctx, msg.ChainId) + require.True(t, isFound) + + // ACT + res, err := msgServer.UpdateERC20CustodyPauseStatus(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.NoError(t, err) + + // check CCTX is created + cctx, found := k.GetCrossChainTx(ctx, res.CctxIndex) + require.True(t, found) + require.Equal(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Contains(t, cctx.RelayedMessage, constant.CmdUpdateERC20CustodyPauseStatus) + require.Len(t, cctx.OutboundParams, 1) + require.EqualValues( + t, + medianGasPrice.MulUint64(types.ERC20CustodyPausingGasMultiplierEVM).String(), + cctx.OutboundParams[0].GasPrice, + ) + require.EqualValues( + t, + priorityFee.MulUint64(types.ERC20CustodyPausingGasMultiplierEVM).String(), + cctx.OutboundParams[0].GasPriorityFee, + ) + }) + + t.Run("should fail if not authorized", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + msgServer := keeper.NewMsgServerImpl(*k) + + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: getValidEthChain().ChainId, + Pause: true, + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, errors.New("not authorized")) + + // ACT + _, err := msgServer.UpdateERC20CustodyPauseStatus(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, authoritytypes.ErrUnauthorized) + }) + + t.Run("should fail if can't find chain nonces", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chainID, + Pause: true, + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + //zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) // not set + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.UpdateERC20CustodyPauseStatus(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrInvalidChainID) + }) + + t.Run("should fail if can't find current TSS", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chainID, + Pause: true, + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + //zk.ObserverKeeper.SetTSS(ctx, tss) // not set + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.UpdateERC20CustodyPauseStatus(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrCannotFindTSSKeys) + }) + + t.Run("should fail if can't find chain params", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chainID, + Pause: true, + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{}) // not set + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.UpdateERC20CustodyPauseStatus(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrInvalidChainID) + }) + + t.Run("should fail if can't find gas price", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chainID, + Pause: true, + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + //k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) // not set + + // ACT + _, err := msgServer.UpdateERC20CustodyPauseStatus(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrUnableToGetGasPrice) + }) + + t.Run("should fail if priority fees higher than gas price", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chainID, + Pause: true, + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{sample.ChainParamsSupported(chainID)}, + }) + k.SetGasPrice(ctx, types.GasPrice{ + Creator: sample.AccAddress(), + ChainId: chainID, + Signers: []string{sample.AccAddress()}, + BlockNums: []uint64{42}, + Prices: []uint64{42}, + PriorityFees: []uint64{43}, + MedianIndex: 0, + }) + + // ACT + _, err := msgServer.UpdateERC20CustodyPauseStatus(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrInvalidGasAmount) + }) + + t.Run("should fail if can't set outbound info", func(t *testing.T) { + // ARRANGE + k, ctx, _, zk := testkeeper.CrosschainKeeperWithMocks(t, testkeeper.CrosschainMockOptions{ + UseAuthorityMock: true, + }) + + chainID := getValidEthChain().ChainId + msgServer := keeper.NewMsgServerImpl(*k) + tss := sample.Tss() + + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chainID, + Pause: true, + } + + // mock authority calls + authorityMock := testkeeper.GetCrosschainAuthorityMock(t, k) + testkeeper.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // set necessary values in observer + zk.ObserverKeeper.SetChainNonces(ctx, observertypes.ChainNonces{ChainId: chainID}) + zk.ObserverKeeper.SetPendingNonces(ctx, observertypes.PendingNonces{ChainId: chainID, Tss: tss.TssPubkey}) + zk.ObserverKeeper.SetTSS(ctx, tss) + zk.ObserverKeeper.SetChainParamsList(ctx, observertypes.ChainParamsList{ + ChainParams: []*observertypes.ChainParams{ + sample.ChainParams(chainID), + }, // set non supported chain params to fail + }) + k.SetGasPrice(ctx, sample.GasPriceWithChainID(t, chainID)) + + // ACT + _, err := msgServer.UpdateERC20CustodyPauseStatus(sdk.WrapSDKContext(ctx), &msg) + + // ASSERT + require.ErrorIs(t, err, observertypes.ErrSupportedChains) + }) +} diff --git a/x/crosschain/keeper/msg_server_whitelist_erc20.go b/x/crosschain/keeper/msg_server_whitelist_erc20.go index 8b6ad08b26..45cf836061 100644 --- a/x/crosschain/keeper/msg_server_whitelist_erc20.go +++ b/x/crosschain/keeper/msg_server_whitelist_erc20.go @@ -2,18 +2,14 @@ package keeper import ( "context" - "fmt" "math/big" errorsmod "cosmossdk.io/errors" - "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "github.com/zeta-chain/zetacore/pkg/coin" - "github.com/zeta-chain/zetacore/pkg/constant" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" "github.com/zeta-chain/zetacore/x/crosschain/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" @@ -101,7 +97,7 @@ func (k msgServer) WhitelistERC20( } // get necessary parameters to create the cctx - param, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, msg.ChainId) + params, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, msg.ChainId) if !found { return nil, errorsmod.Wrapf(types.ErrInvalidChainID, "chain params not found for chain id (%d)", msg.ChainId) } @@ -113,11 +109,10 @@ func (k msgServer) WhitelistERC20( msg.ChainId, ) } - // overpays gas price by 2x - const multiplier = 2 - medianGasPrice = medianGasPrice.MulUint64(multiplier) - priorityFee = priorityFee.MulUint64(multiplier) + // overpays gas price by 2x + medianGasPrice = medianGasPrice.MulUint64(types.ERC20CustodyWhitelistGasMultiplierEVM) + priorityFee = priorityFee.MulUint64(types.ERC20CustodyWhitelistGasMultiplierEVM) // should not happen if priorityFee.GT(medianGasPrice) { @@ -129,54 +124,18 @@ func (k msgServer) WhitelistERC20( ) } - // calculate the cctx index - // we use the deployed zrc20 contract address to generate a unique index - // since other parts of the system may use the zrc20 for the index, we add a message specific suffix - hash := crypto.Keccak256Hash(zrc20Addr.Bytes(), []byte("WhitelistERC20")) - index := hash.Hex() - - // create a cmd cctx to whitelist the erc20 on the external chain - // TODO : refactor this to use the `NewCCTX` function instead. - //https://github.com/zeta-chain/node/issues/1909 - cctx := types.CrossChainTx{ - Creator: msg.Creator, - Index: index, - ZetaFees: sdk.NewUint(0), - RelayedMessage: fmt.Sprintf("%s:%s", constant.CmdWhitelistERC20, msg.Erc20Address), - CctxStatus: &types.Status{ - Status: types.CctxStatus_PendingOutbound, - StatusMessage: "", - LastUpdateTimestamp: 0, - }, - InboundParams: &types.InboundParams{ - Sender: msg.Creator, - SenderChainId: 0, - TxOrigin: "", - CoinType: coin.CoinType_Cmd, - Asset: "", - Amount: math.Uint{}, - ObservedHash: hash.String(), // all Upper case Cosmos TX HEX, with no 0x prefix - ObservedExternalHeight: 0, - BallotIndex: "", - FinalizedZetaHeight: 0, - }, - OutboundParams: []*types.OutboundParams{ - { - Receiver: param.Erc20CustodyContractAddress, - ReceiverChainId: msg.ChainId, - CoinType: coin.CoinType_Cmd, - Amount: math.NewUint(0), - TssNonce: 0, - GasLimit: 100_000, - GasPrice: medianGasPrice.String(), - GasPriorityFee: priorityFee.String(), - Hash: "", - BallotIndex: "", - ObservedExternalHeight: 0, - TssPubkey: tss.TssPubkey, - }, - }, - } + // create the cctx + cctx := types.WhitelistERC20CmdCCTX( + msg.Creator, + zrc20Addr, + msg.Erc20Address, + params.Erc20CustodyContractAddress, + msg.ChainId, + medianGasPrice.String(), + priorityFee.String(), + tss.TssPubkey, + ) + err = k.SetObserverOutboundInfo(ctx, msg.ChainId, &cctx) if err != nil { return nil, err @@ -202,7 +161,7 @@ func (k msgServer) WhitelistERC20( err = ctx.EventManager().EmitTypedEvent( &types.EventERC20Whitelist{ Zrc20Address: zrc20Addr.Hex(), - WhitelistCctxIndex: index, + WhitelistCctxIndex: cctx.Index, }, ) if err != nil { @@ -211,6 +170,6 @@ func (k msgServer) WhitelistERC20( return &types.MsgWhitelistERC20Response{ Zrc20Address: zrc20Addr.Hex(), - CctxIndex: index, + CctxIndex: cctx.Index, }, nil } diff --git a/x/crosschain/keeper/utils_test.go b/x/crosschain/keeper/utils_test.go index d62987dfc0..fb59b7afb9 100644 --- a/x/crosschain/keeper/utils_test.go +++ b/x/crosschain/keeper/utils_test.go @@ -5,12 +5,17 @@ import ( "math/big" "testing" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + "github.com/zeta-chain/zetacore/pkg/contracts/erc1967proxy" + fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" + sdk "github.com/cosmos/cosmos-sdk/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" + evmkeeper "github.com/zeta-chain/ethermint/x/evm/keeper" - "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" "github.com/zeta-chain/zetacore/cmd/zetacored/config" "github.com/zeta-chain/zetacore/pkg/chains" @@ -67,6 +72,44 @@ func assertContractDeployment(t *testing.T, k *evmkeeper.Keeper, ctx sdk.Context require.NotEmpty(t, code) } +// deploy upgradable gateway contract and return its address +func deployGatewayContract( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk *evmkeeper.Keeper, + wzeta, admin common.Address, +) common.Address { + // Deploy the gateway contract + implAddr, err := k.DeployContract(ctx, gatewayzevm.GatewayZEVMMetaData) + require.NoError(t, err) + require.NotEmpty(t, implAddr) + assertContractDeployment(t, evmk, ctx, implAddr) + + // Deploy the proxy contract + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + require.NoError(t, err) + + // Encode the initializer data + initializerData, err := gatewayABI.Pack("initialize", wzeta, admin) + require.NoError(t, err) + + gatewayContract, err := k.DeployContract(ctx, erc1967proxy.ERC1967ProxyMetaData, implAddr, initializerData) + require.NoError(t, err) + require.NotEmpty(t, gatewayContract) + assertContractDeployment(t, evmk, ctx, gatewayContract) + + // store the gateway in the system contract object + sys, found := k.GetSystemContract(ctx) + if !found { + sys = fungibletypes.SystemContract{} + } + sys.Gateway = gatewayContract.Hex() + k.SetSystemContract(ctx, sys) + + return gatewayContract +} + // deploySystemContracts deploys the system contracts and returns their addresses. func deploySystemContracts( t *testing.T, @@ -101,6 +144,10 @@ func deploySystemContracts( require.NotEmpty(t, systemContract) assertContractDeployment(t, evmk, ctx, systemContract) + // deploy the gateway contract + contract := deployGatewayContract(t, ctx, k, evmk, wzeta, sample.EthAddress()) + require.NotEmpty(t, contract) + return } diff --git a/x/crosschain/keeper/v2_zevm_inbound.go b/x/crosschain/keeper/v2_zevm_inbound.go new file mode 100644 index 0000000000..158a8f0f40 --- /dev/null +++ b/x/crosschain/keeper/v2_zevm_inbound.go @@ -0,0 +1,278 @@ +package keeper + +import ( + "encoding/hex" + "fmt" + "math/big" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/x/crosschain/types" + fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +// ProcessZEVMInboundV2 processes the logs emitted by the zEVM contract for V2 protocol contracts +// it parses logs from GatewayZEVM contract and updates the crosschain module state +func (k Keeper) ProcessZEVMInboundV2( + ctx sdk.Context, + log *ethtypes.Log, + gatewayAddr, + from ethcommon.Address, + txOrigin string, +) error { + // try to parse a withdrawal event from the log + withdrawalEvent, gatewayEvent, err := k.parseGatewayEvent(*log, gatewayAddr) + if err == nil && (withdrawalEvent != nil || gatewayEvent != nil) { + var inbound *types.MsgVoteInbound + + // parse data from event and validate + var zrc20 ethcommon.Address + var value *big.Int + var receiver []byte + var contractAddress ethcommon.Address + if withdrawalEvent != nil { + zrc20 = withdrawalEvent.Zrc20 + value = withdrawalEvent.Value + receiver = withdrawalEvent.Receiver + contractAddress = withdrawalEvent.Raw.Address + } else { + zrc20 = gatewayEvent.Zrc20 + value = big.NewInt(0) + receiver = gatewayEvent.Receiver + contractAddress = gatewayEvent.Raw.Address + } + + k.Logger(ctx).Error(fmt.Sprintf("processing inbound. zrc20: %s", zrc20.Hex())) + + foreignCoin, found := k.fungibleKeeper.GetForeignCoins(ctx, zrc20.Hex()) + if !found { + ctx.Logger(). + Info(fmt.Sprintf("cannot find foreign coin with contract address %s", contractAddress.Hex())) + return nil + } + + // validate data of the withdrawal event + if err := k.validateZRC20Withdrawal(ctx, foreignCoin.ForeignChainId, value, receiver); err != nil { + return err + } + + // create inbound object depending on the event type + if withdrawalEvent != nil { + inbound, err = k.newWithdrawalInbound(ctx, from, txOrigin, foreignCoin, withdrawalEvent) + if err != nil { + return err + } + } else { + inbound, err = k.newCallInbound(ctx, from, txOrigin, foreignCoin, gatewayEvent) + if err != nil { + return err + } + } + + if inbound == nil { + return errors.New("ParseGatewayEvent: invalid log - no event found") + } + + // validate inbound for processing + cctx, err := k.ValidateInbound(ctx, inbound, false) + if err != nil { + return err + } + if cctx.CctxStatus.Status == types.CctxStatus_Aborted { + return errors.New("cctx aborted") + } + + EmitZRCWithdrawCreated(ctx, *cctx) + } + + return nil +} + +// parseGatewayEvent parses the event from the gateway contract +func (k Keeper) parseGatewayEvent( + log ethtypes.Log, + gatewayAddr ethcommon.Address, +) (*gatewayzevm.GatewayZEVMWithdrawn, *gatewayzevm.GatewayZEVMCalled, error) { + if len(log.Topics) == 0 { + return nil, nil, errors.New("ParseGatewayCallEvent: invalid log - no topics") + } + filterer, err := gatewayzevm.NewGatewayZEVMFilterer(log.Address, bind.ContractFilterer(nil)) + if err != nil { + return nil, nil, err + } + withdrawalEvent, err := k.parseGatewayWithdrawalEvent(log, gatewayAddr, filterer) + if err == nil { + return withdrawalEvent, nil, nil + } + callEvent, err := k.parseGatewayCallEvent(log, gatewayAddr, filterer) + if err == nil { + return nil, callEvent, nil + } + return nil, nil, errors.New("ParseGatewayEvent: invalid log - no event found") +} + +// parseGatewayWithdrawalEvent parses the GatewayZEVMWithdrawal event from the log +func (k Keeper) parseGatewayWithdrawalEvent( + log ethtypes.Log, + gatewayAddr ethcommon.Address, + filterer *gatewayzevm.GatewayZEVMFilterer, +) (*gatewayzevm.GatewayZEVMWithdrawn, error) { + event, err := filterer.ParseWithdrawn(log) + if err != nil { + return nil, err + } + if event.Raw.Address != gatewayAddr { + return nil, errors.New("ParseGatewayWithdrawalEvent: invalid log - wrong contract address") + } + return event, nil +} + +// parseGatewayCallEvent parses the GatewayZEVMCall event from the log +func (k Keeper) parseGatewayCallEvent( + log ethtypes.Log, + gatewayAddr ethcommon.Address, + filterer *gatewayzevm.GatewayZEVMFilterer, +) (*gatewayzevm.GatewayZEVMCalled, error) { + event, err := filterer.ParseCalled(log) + if err != nil { + return nil, err + } + if event.Raw.Address != gatewayAddr { + return nil, errors.New("ParseGatewayCallEvent: invalid log - wrong contract address") + } + return event, nil +} + +// newWithdrawalInbound creates a new inbound object for a withdrawal +// currently inbound data is represented with a MsgVoteInbound message +// TODO: replace with a more appropriate object +// https://github.com/zeta-chain/node/issues/2658 +func (k Keeper) newWithdrawalInbound( + ctx sdk.Context, + from ethcommon.Address, + txOrigin string, + foreignCoin fungibletypes.ForeignCoins, + event *gatewayzevm.GatewayZEVMWithdrawn, +) (*types.MsgVoteInbound, error) { + receiverChain, found := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, foreignCoin.ForeignChainId) + if !found { + return nil, errorsmod.Wrapf( + observertypes.ErrSupportedChains, + "chain with chainID %d not supported", + foreignCoin.ForeignChainId, + ) + } + + senderChain, err := chains.ZetaChainFromCosmosChainID(ctx.ChainID()) + if err != nil { + return nil, errors.Wrapf(err, "ProcessZEVMInboundV2: failed to convert chainID %s", ctx.ChainID()) + } + + toAddr, err := receiverChain.EncodeAddress(event.Receiver) + if err != nil { + return nil, errors.Wrapf(err, "cannot encode address %v", event.Receiver) + } + + gasLimit := event.GasLimit.Uint64() + if gasLimit == 0 { + gasLimitQueried, err := k.fungibleKeeper.QueryGasLimit( + ctx, + ethcommon.HexToAddress(foreignCoin.Zrc20ContractAddress), + ) + if err != nil { + return nil, errors.Wrap(err, "cannot query gas limit") + } + gasLimit = gasLimitQueried.Uint64() + } + + return types.NewMsgVoteInbound( + "", + from.Hex(), + senderChain.ChainId, + txOrigin, + toAddr, + foreignCoin.ForeignChainId, + math.NewUintFromBigInt(event.Value), + hex.EncodeToString(event.Message), + event.Raw.TxHash.String(), + event.Raw.BlockNumber, + gasLimit, + foreignCoin.CoinType, + foreignCoin.Asset, + event.Raw.Index, + types.ProtocolContractVersion_V2, + types.WithZEVMRevertOptions(event.RevertOptions), + ), nil +} + +// newCallInbound creates a new inbound object for a call +// currently inbound data is represented with a MsgVoteInbound message +// TODO: replace with a more appropriate object +// https://github.com/zeta-chain/node/issues/2658 +func (k Keeper) newCallInbound( + ctx sdk.Context, + from ethcommon.Address, + txOrigin string, + foreignCoin fungibletypes.ForeignCoins, + event *gatewayzevm.GatewayZEVMCalled, +) (*types.MsgVoteInbound, error) { + receiverChain, found := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, foreignCoin.ForeignChainId) + if !found { + return nil, errorsmod.Wrapf( + observertypes.ErrSupportedChains, + "chain with chainID %d not supported", + foreignCoin.ForeignChainId, + ) + } + + senderChain, err := chains.ZetaChainFromCosmosChainID(ctx.ChainID()) + if err != nil { + return nil, errors.Wrapf(err, "ProcessZEVMInboundV2: failed to convert chainID %s", ctx.ChainID()) + } + + toAddr, err := receiverChain.EncodeAddress(event.Receiver) + if err != nil { + return nil, errors.Wrapf(err, "cannot encode address %v", event.Receiver) + } + + gasLimit := event.GasLimit.Uint64() + if gasLimit == 0 { + gasLimitQueried, err := k.fungibleKeeper.QueryGasLimit( + ctx, + ethcommon.HexToAddress(foreignCoin.Zrc20ContractAddress), + ) + if err != nil { + return nil, errors.Wrap(err, "cannot query gas limit") + } + gasLimit = gasLimitQueried.Uint64() + } + + return types.NewMsgVoteInbound( + "", + from.Hex(), + senderChain.ChainId, + txOrigin, + toAddr, + foreignCoin.ForeignChainId, + math.ZeroUint(), + hex.EncodeToString(event.Message), + event.Raw.TxHash.String(), + event.Raw.BlockNumber, + gasLimit, + coin.CoinType_NoAssetCall, + "", + event.Raw.Index, + types.ProtocolContractVersion_V2, + types.WithZEVMRevertOptions(event.RevertOptions), + ), nil +} diff --git a/x/crosschain/types/cctx.go b/x/crosschain/types/cctx.go index 6952a5092f..d7a77e259c 100644 --- a/x/crosschain/types/cctx.go +++ b/x/crosschain/types/cctx.go @@ -8,10 +8,33 @@ import ( sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + ethcommon "github.com/ethereum/go-ethereum/common" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +// GetEVMRevertAddress returns the EVM revert address +// If a revert address is specified in the revert options, it returns the address +// Otherwise returns sender address +func (m CrossChainTx) GetEVMRevertAddress() ethcommon.Address { + addr, valid := m.RevertOptions.GetEVMRevertAddress() + if valid { + return addr + } + return ethcommon.HexToAddress(m.InboundParams.Sender) +} + +// GetEVMAbortAddress returns the EVM abort address +// If an abort address is specified in the revert options, it returns the address +// Otherwise returns sender address +func (m CrossChainTx) GetEVMAbortAddress() ethcommon.Address { + addr, valid := m.RevertOptions.GetEVMAbortAddress() + if valid { + return addr + } + return ethcommon.HexToAddress(m.InboundParams.Sender) +} + // GetCurrentOutboundParam returns the current outbound params. // There can only be one active outbound. // OutboundParams[0] is the original outbound, if it reverts, then @@ -82,8 +105,19 @@ func (m *CrossChainTx) AddRevertOutbound(gasLimit uint64) error { return fmt.Errorf("cannot revert before trying to process an outbound tx") } + // in protocol contract V2, developers can specify a specific address to receive the revert + // if not specified, the sender address is used + // note: this option is current only support for EVM type chains + revertReceiver := m.InboundParams.Sender + if m.ProtocolContractVersion == ProtocolContractVersion_V2 { + revertAddress, valid := m.RevertOptions.GetEVMRevertAddress() + if valid { + revertReceiver = revertAddress.Hex() + } + } + revertTxParams := &OutboundParams{ - Receiver: m.InboundParams.Sender, + Receiver: revertReceiver, ReceiverChainId: m.InboundParams.SenderChainId, Amount: m.GetCurrentOutboundParam().Amount, GasLimit: gasLimit, @@ -122,7 +156,6 @@ func (m *CrossChainTx) AddOutbound( m.GetCurrentOutboundParam().EffectiveGasPrice = msg.ObservedOutboundEffectiveGasPrice m.GetCurrentOutboundParam().EffectiveGasLimit = msg.ObservedOutboundEffectiveGasLimit m.GetCurrentOutboundParam().ObservedExternalHeight = msg.ObservedOutboundBlockHeight - m.CctxStatus.LastUpdateTimestamp = ctx.BlockHeader().Time.Unix() return nil } @@ -169,6 +202,7 @@ func (m CrossChainTx) SetOutboundBallotIndex(index string) { m.GetCurrentOutboundParam().BallotIndex = index } +// GetCctxIndexFromBytes returns the CCTX index from a byte array. func GetCctxIndexFromBytes(sendHash [32]byte) string { return fmt.Sprintf("0x%s", hex.EncodeToString(sendHash[:])) } @@ -211,17 +245,20 @@ func NewCCTX(ctx sdk.Context, msg MsgVoteInbound, tssPubkey string) (CrossChainT status := &Status{ Status: CctxStatus_PendingInbound, StatusMessage: "", + CreatedTimestamp: ctx.BlockHeader().Time.Unix(), LastUpdateTimestamp: ctx.BlockHeader().Time.Unix(), IsAbortRefunded: false, } cctx := CrossChainTx{ - Creator: msg.Creator, - Index: index, - ZetaFees: sdkmath.ZeroUint(), - RelayedMessage: msg.Message, - CctxStatus: status, - InboundParams: inboundParams, - OutboundParams: []*OutboundParams{outboundParams}, + Creator: msg.Creator, + Index: index, + ZetaFees: sdkmath.ZeroUint(), + RelayedMessage: msg.Message, + CctxStatus: status, + InboundParams: inboundParams, + OutboundParams: []*OutboundParams{outboundParams}, + ProtocolContractVersion: msg.ProtocolContractVersion, + RevertOptions: msg.RevertOptions, } // TODO: remove this validate call diff --git a/x/crosschain/types/cctx_test.go b/x/crosschain/types/cctx_test.go index 57d09ed188..1b706d0feb 100644 --- a/x/crosschain/types/cctx_test.go +++ b/x/crosschain/types/cctx_test.go @@ -16,12 +16,46 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) +func TestCrossChainTx_GetEVMRevertAddress(t *testing.T) { + t.Run("use revert address if revert options", func(t *testing.T) { + cctx := sample.CrossChainTx(t, "sample") + addr := sample.EthAddress() + cctx.RevertOptions.RevertAddress = addr.Hex() + require.EqualValues(t, addr, cctx.GetEVMRevertAddress()) + }) + + t.Run("use sender address if no revert options", func(t *testing.T) { + cctx := sample.CrossChainTx(t, "sample") + addr := sample.EthAddress() + cctx.InboundParams.Sender = addr.Hex() + require.EqualValues(t, addr, cctx.GetEVMRevertAddress()) + }) + +} + +func TestCrossChainTx_GetEVMAbortAddress(t *testing.T) { + t.Run("use revert address if abort options", func(t *testing.T) { + cctx := sample.CrossChainTx(t, "sample") + addr := sample.EthAddress() + cctx.RevertOptions.AbortAddress = addr.Hex() + require.EqualValues(t, addr, cctx.GetEVMAbortAddress()) + }) + + t.Run("use sender address if no abort options", func(t *testing.T) { + cctx := sample.CrossChainTx(t, "sample") + addr := sample.EthAddress() + cctx.InboundParams.Sender = addr.Hex() + require.EqualValues(t, addr, cctx.GetEVMAbortAddress()) + }) +} + func TestCrossChainTx_SetOutboundBallot(t *testing.T) { cctx := sample.CrossChainTx(t, "test") ballotIndex := sample.ZetaIndex(t) cctx.SetOutboundBallotIndex(ballotIndex) require.Equal(t, ballotIndex, cctx.GetCurrentOutboundParam().BallotIndex) } + func TestCrossChainTx_GetCCTXIndexBytes(t *testing.T) { cctx := sample.CrossChainTx(t, "sample") indexBytes, err := cctx.GetCCTXIndexBytes() @@ -29,7 +63,7 @@ func TestCrossChainTx_GetCCTXIndexBytes(t *testing.T) { require.Equal(t, cctx.Index, types.GetCctxIndexFromBytes(indexBytes)) } -func Test_InitializeCCTX(t *testing.T) { +func Test_NewCCTX(t *testing.T) { t.Run("should return a cctx with correct values", func(t *testing.T) { _, ctx, _, _ := keepertest.CrosschainKeeper(t) senderChain := chains.Goerli @@ -47,20 +81,21 @@ func Test_InitializeCCTX(t *testing.T) { cointType := coin.CoinType_ERC20 tss := sample.Tss() msg := types.MsgVoteInbound{ - Creator: creator, - Sender: sender.String(), - SenderChainId: senderChain.ChainId, - Receiver: receiver.String(), - ReceiverChain: receiverChain.ChainId, - Amount: amount, - Message: message, - InboundHash: inboundHash.String(), - InboundBlockHeight: inboundBlockHeight, - GasLimit: gasLimit, - CoinType: cointType, - TxOrigin: sender.String(), - Asset: asset, - EventIndex: eventIndex, + Creator: creator, + Sender: sender.String(), + SenderChainId: senderChain.ChainId, + Receiver: receiver.String(), + ReceiverChain: receiverChain.ChainId, + Amount: amount, + Message: message, + InboundHash: inboundHash.String(), + InboundBlockHeight: inboundBlockHeight, + GasLimit: gasLimit, + CoinType: cointType, + TxOrigin: sender.String(), + Asset: asset, + EventIndex: eventIndex, + ProtocolContractVersion: types.ProtocolContractVersion_V2, } cctx, err := types.NewCCTX(ctx, msg, tss.TssPubkey) require.NoError(t, err) @@ -79,7 +114,9 @@ func Test_InitializeCCTX(t *testing.T) { require.Equal(t, sdkmath.ZeroUint(), cctx.GetCurrentOutboundParam().Amount) require.Equal(t, types.CctxStatus_PendingInbound, cctx.CctxStatus.Status) require.Equal(t, false, cctx.CctxStatus.IsAbortRefunded) + require.Equal(t, types.ProtocolContractVersion_V2, cctx.ProtocolContractVersion) }) + t.Run("should return an error if the cctx is invalid", func(t *testing.T) { _, ctx, _, _ := keepertest.CrosschainKeeper(t) senderChain := chains.Goerli @@ -115,6 +152,11 @@ func Test_InitializeCCTX(t *testing.T) { _, err := types.NewCCTX(ctx, msg, tss.TssPubkey) require.ErrorContains(t, err, "sender cannot be empty") }) + + t.Run("zero value for protocol contract version gives V1", func(t *testing.T) { + cctx := types.CrossChainTx{} + require.Equal(t, types.ProtocolContractVersion_V1, cctx.ProtocolContractVersion) + }) } func TestCrossChainTx_Validate(t *testing.T) { @@ -199,7 +241,6 @@ func TestCrossChainTx_AddOutbound(t *testing.T) { require.Equal(t, cctx.GetCurrentOutboundParam().EffectiveGasPrice, sdkmath.NewInt(100)) require.Equal(t, cctx.GetCurrentOutboundParam().EffectiveGasLimit, uint64(20)) require.Equal(t, cctx.GetCurrentOutboundParam().ObservedExternalHeight, uint64(10)) - require.Equal(t, cctx.CctxStatus.LastUpdateTimestamp, ctx.BlockHeader().Time.Unix()) }) t.Run("successfully get outbound tx for failed ballot without amount check", func(t *testing.T) { @@ -220,7 +261,6 @@ func TestCrossChainTx_AddOutbound(t *testing.T) { require.Equal(t, cctx.GetCurrentOutboundParam().EffectiveGasPrice, sdkmath.NewInt(100)) require.Equal(t, cctx.GetCurrentOutboundParam().EffectiveGasLimit, uint64(20)) require.Equal(t, cctx.GetCurrentOutboundParam().ObservedExternalHeight, uint64(10)) - require.Equal(t, cctx.CctxStatus.LastUpdateTimestamp, ctx.BlockHeader().Time.Unix()) }) t.Run("failed to get outbound tx if amount does not match value received", func(t *testing.T) { diff --git a/x/crosschain/types/cmd_cctxs.go b/x/crosschain/types/cmd_cctxs.go new file mode 100644 index 0000000000..3f416c6d7d --- /dev/null +++ b/x/crosschain/types/cmd_cctxs.go @@ -0,0 +1,308 @@ +package types + +import ( + "fmt" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/constant" + zetacrypto "github.com/zeta-chain/zetacore/pkg/crypto" + "github.com/zeta-chain/zetacore/pkg/gas" +) + +const ( + // TssMigrationGasMultiplierEVM is multiplied to the median gas price to get the gas price for the tss migration . + // This is done to avoid the tss migration tx getting stuck in the mempool + TssMigrationGasMultiplierEVM = "2.5" + + // TSSMigrationBufferAmountEVM is the buffer amount added to the gas price for the tss migration transaction + TSSMigrationBufferAmountEVM = "2100000000" + + // ERC20CustodyMigrationGasMultiplierEVM is multiplied to the median gas price to get the gas price for the erc20 custody migration + // NOTE: this is a integer type unlike type above because the message logic is slightly different and an integer is needed + ERC20CustodyMigrationGasMultiplierEVM = 2 + + // ERC20CustodyWhitelistGasMultiplierEVM is multiplied to the median gas price to get the gas price for the erc20 custody whitelist + ERC20CustodyWhitelistGasMultiplierEVM = 2 + + // ERC20CustodyPausingGasMultiplierEVM is multiplied to the median gas price to get the gas price for the erc20 custody pausing + ERC20CustodyPausingGasMultiplierEVM = 2 +) + +// MigrateERC20CustodyFundsCmdCCTX returns a CCTX allowing to migrate ERC20 custody funds +func MigrateERC20CustodyFundsCmdCCTX( + creator string, + erc20Address string, + custodyContractAddress string, + newCustodyContractAddress string, + chainID int64, + amount sdkmath.Uint, + gasPrice string, + priorityFee string, + tssPubKey string, + currentNonce uint64, +) CrossChainTx { + indexString := GetERC20CustodyMigrationCCTXIndexString(tssPubKey, currentNonce, chainID, erc20Address) + hash := crypto.Keccak256Hash([]byte(indexString)) + + return newCmdCCTX( + creator, + hash.Hex(), + fmt.Sprintf( + "%s:%s,%s,%s", + constant.CmdMigrateERC20CustodyFunds, + newCustodyContractAddress, + erc20Address, + amount.String(), + ), + creator, + hash.Hex(), + custodyContractAddress, + chainID, + sdkmath.NewUint(0), + 100_000, + gasPrice, + priorityFee, + tssPubKey, + ) +} + +// GetERC20CustodyMigrationCCTXIndexString returns the index string of the CCTX for migrating ERC20 custody funds +func GetERC20CustodyMigrationCCTXIndexString( + tssPubKey string, + nonce uint64, + chainID int64, + erc20Address string, +) string { + return fmt.Sprintf("%s-%s-%d-%d-%s", constant.CmdMigrateERC20CustodyFunds, tssPubKey, nonce, chainID, erc20Address) +} + +// UpdateERC20CustodyPauseStatusCmdCCTX returns a CCTX allowing to update the pause status of the ERC20 custody contract +func UpdateERC20CustodyPauseStatusCmdCCTX( + creator string, + custodyContractAddress string, + chainID int64, + pause bool, + gasPrice string, + priorityFee string, + tssPubKey string, + currentNonce uint64, +) CrossChainTx { + indexString := GetERC20CustodyPausingCmdCCTXIndexString(tssPubKey, currentNonce, chainID) + hash := crypto.Keccak256Hash([]byte(indexString)) + + params := constant.OptionUnpause + if pause { + params = constant.OptionPause + } + + return newCmdCCTX( + creator, + hash.Hex(), + fmt.Sprintf("%s:%s", constant.CmdUpdateERC20CustodyPauseStatus, params), + creator, + hash.Hex(), + custodyContractAddress, + chainID, + sdkmath.NewUint(0), + 100_000, + gasPrice, + priorityFee, + tssPubKey, + ) +} + +// GetERC20CustodyPausingCmdCCTXIndexString returns the index string of the CCTX for updating the pause status of the ERC20 custody contract +func GetERC20CustodyPausingCmdCCTXIndexString( + tssPubKey string, + nonce uint64, + chainID int64, +) string { + return fmt.Sprintf("%s-%s-%d-%d", constant.CmdUpdateERC20CustodyPauseStatus, tssPubKey, nonce, chainID) +} + +// WhitelistERC20CmdCCTX returns a CCTX allowing to whitelist an ERC20 token on an external chain +func WhitelistERC20CmdCCTX( + creator string, + zrc20Address ethcommon.Address, + erc20Address string, + custodyContractAddress string, + chainID int64, + gasPrice string, + priorityFee string, + tssPubKey string, +) CrossChainTx { + // calculate the cctx index + // we use the deployed zrc20 contract address to generate a unique index + // since other parts of the system may use the zrc20 for the index, we add a message specific suffix + hash := crypto.Keccak256Hash(zrc20Address.Bytes(), []byte("WhitelistERC20")) + + return newCmdCCTX( + creator, + hash.Hex(), + fmt.Sprintf("%s:%s", constant.CmdWhitelistERC20, erc20Address), + creator, + hash.Hex(), + custodyContractAddress, + chainID, + sdkmath.NewUint(0), + 100_000, + gasPrice, + priorityFee, + tssPubKey, + ) +} + +// MigrateFundCmdCCTX returns a CCTX allowing to migrate funds from the current TSS to the new TSS +func MigrateFundCmdCCTX( + blockHeight int64, + creator string, + inboundHash string, + chainID int64, + amount sdkmath.Uint, + medianGasPrice sdkmath.Uint, + priorityFee sdkmath.Uint, + currentTSSPubKey string, + newTSSPubKey string, + additionalStaticChainInfo []chains.Chain, +) (CrossChainTx, error) { + var ( + sender string + receiver string + gasLimit uint64 + gasPrice string + finalAmount sdkmath.Uint + ) + + // set sender, receiver, gas limit, gas price and final amount based on the chain + switch { + case chains.IsEVMChain(chainID, additionalStaticChainInfo): + ethAddressOld, err := zetacrypto.GetTssAddrEVM(currentTSSPubKey) + if err != nil { + return CrossChainTx{}, err + } + ethAddressNew, err := zetacrypto.GetTssAddrEVM(newTSSPubKey) + if err != nil { + return CrossChainTx{}, err + } + sender = ethAddressOld.String() + receiver = ethAddressNew.String() + gasLimit = gas.EVMSend + gasPriceUint, err := gas.MultiplyGasPrice(medianGasPrice, TssMigrationGasMultiplierEVM) + if err != nil { + return CrossChainTx{}, err + } + evmFee := sdkmath.NewUint(gasLimit). + Mul(gasPriceUint). + Add(sdkmath.NewUintFromString(TSSMigrationBufferAmountEVM)) + if evmFee.GT(amount) { + return CrossChainTx{}, errorsmod.Wrap( + ErrInsufficientFundsTssMigration, + fmt.Sprintf( + "insufficient funds to pay for gas fee, amount: %s, gas fee: %s, chainid: %d", + amount.String(), + evmFee.String(), + chainID, + ), + ) + } + gasPrice = gasPriceUint.String() + finalAmount = amount.Sub(evmFee) + case chains.IsBitcoinChain(chainID, additionalStaticChainInfo): + bitcoinNetParams, err := chains.BitcoinNetParamsFromChainID(chainID) + if err != nil { + return CrossChainTx{}, err + } + btcAddressOld, err := zetacrypto.GetTssAddrBTC(currentTSSPubKey, bitcoinNetParams) + if err != nil { + return CrossChainTx{}, err + } + btcAddressNew, err := zetacrypto.GetTssAddrBTC(newTSSPubKey, bitcoinNetParams) + if err != nil { + return CrossChainTx{}, err + } + sender = btcAddressOld + receiver = btcAddressNew + gasLimit = 1_000_000 + gasPrice = medianGasPrice.MulUint64(2).String() + finalAmount = amount + default: + return CrossChainTx{}, errorsmod.Wrap(ErrUnsupportedChain, fmt.Sprintf("chain %d is not supported", chainID)) + } + + indexString := GetTssMigrationCCTXIndexString(currentTSSPubKey, newTSSPubKey, chainID, amount, blockHeight) + hash := crypto.Keccak256Hash([]byte(indexString)) + + return newCmdCCTX( + creator, + hash.Hex(), + fmt.Sprintf("%s:%s", constant.CmdMigrateTssFunds, "Funds Migrator Admin Cmd"), + sender, + inboundHash, + receiver, + chainID, + finalAmount, + gasLimit, + gasPrice, + priorityFee.MulUint64(2).String(), + newTSSPubKey, + ), nil +} + +// GetTssMigrationCCTXIndexString returns the index string of the CCTX for migrating funds from the current TSS to the new TSS +func GetTssMigrationCCTXIndexString( + currentTssPubkey, + newTssPubkey string, + chainID int64, + amount sdkmath.Uint, + height int64, +) string { + return fmt.Sprintf("%s-%s-%d-%s-%d", currentTssPubkey, newTssPubkey, chainID, amount.String(), height) +} + +// newCmdCCTX returns a new CCTX for admin cmd with the given parameters +func newCmdCCTX( + creator string, + index string, + relayedMessage, + sender string, + inboundHash string, + receiver string, + chainID int64, + amount sdkmath.Uint, + gasLimit uint64, + medianGasPrice string, + priorityFee string, + tssPubKey string, +) CrossChainTx { + return CrossChainTx{ + Creator: creator, + Index: index, + RelayedMessage: relayedMessage, + CctxStatus: &Status{ + Status: CctxStatus_PendingOutbound, + }, + InboundParams: &InboundParams{ + Sender: sender, + CoinType: coin.CoinType_Cmd, + ObservedHash: inboundHash, + }, + OutboundParams: []*OutboundParams{ + { + Receiver: receiver, + ReceiverChainId: chainID, + CoinType: coin.CoinType_Cmd, + Amount: amount, + GasLimit: gasLimit, + GasPrice: medianGasPrice, + GasPriorityFee: priorityFee, + TssPubkey: tssPubKey, + }, + }, + } +} diff --git a/x/crosschain/types/cmd_cctxs_test.go b/x/crosschain/types/cmd_cctxs_test.go new file mode 100644 index 0000000000..56fc205048 --- /dev/null +++ b/x/crosschain/types/cmd_cctxs_test.go @@ -0,0 +1,669 @@ +package types_test + +import ( + sdkmath "cosmossdk.io/math" + "fmt" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/constant" + "github.com/zeta-chain/zetacore/pkg/gas" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" + "testing" +) + +func TestMigrateERC20CustodyFundsCmdCCTX(t *testing.T) { + t.Run("returns a new CCTX for migrating ERC20 custody funds with unique index", func(t *testing.T) { + // ARRANGE + creator := sample.AccAddress() + erc20Address := sample.EthAddress().String() + custodyContractAddress := sample.EthAddress().String() + newCustodyContractAddress := sample.EthAddress().String() + chainID := int64(42) + amount := sdkmath.NewUint(1000) + gasPrice := "100000" + priorityFee := "100000" + tssPubKey := sample.PubKeyString() + currentNonce := uint64(1) + + // ACT + cctx := types.MigrateERC20CustodyFundsCmdCCTX( + creator, + erc20Address, + custodyContractAddress, + newCustodyContractAddress, + chainID, + amount, + gasPrice, + priorityFee, + tssPubKey, + currentNonce, + ) + cctxDifferentERC20Address := types.MigrateERC20CustodyFundsCmdCCTX( + creator, + sample.EthAddress().String(), + custodyContractAddress, + newCustodyContractAddress, + chainID, + amount, + gasPrice, + priorityFee, + tssPubKey, + currentNonce, + ) + cctxDifferentNonce := types.MigrateERC20CustodyFundsCmdCCTX( + creator, + erc20Address, + custodyContractAddress, + newCustodyContractAddress, + chainID, + amount, + gasPrice, + priorityFee, + tssPubKey, + currentNonce+1, + ) + cctxDifferentTSSPubkey := types.MigrateERC20CustodyFundsCmdCCTX( + creator, + erc20Address, + custodyContractAddress, + newCustodyContractAddress, + chainID, + amount, + gasPrice, + priorityFee, + sample.PubKeyString(), + currentNonce, + ) + + // ASSERT + require.NotEmpty(t, cctx.Index) + require.EqualValues(t, creator, cctx.Creator) + require.EqualValues(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.EqualValues(t, fmt.Sprintf("%s:%s,%s,1000", + constant.CmdMigrateERC20CustodyFunds, + newCustodyContractAddress, + erc20Address, + ), cctx.RelayedMessage) + require.EqualValues(t, creator, cctx.InboundParams.Sender) + require.EqualValues(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.EqualValues(t, custodyContractAddress, cctx.OutboundParams[0].Receiver) + require.EqualValues(t, chainID, cctx.OutboundParams[0].ReceiverChainId) + require.EqualValues(t, coin.CoinType_Cmd, cctx.OutboundParams[0].CoinType) + require.EqualValues(t, sdkmath.NewUint(0), cctx.OutboundParams[0].Amount) + require.EqualValues(t, 100_000, cctx.OutboundParams[0].GasLimit) + require.EqualValues(t, gasPrice, cctx.OutboundParams[0].GasPrice) + require.EqualValues(t, priorityFee, cctx.OutboundParams[0].GasPriorityFee) + require.EqualValues(t, tssPubKey, cctx.OutboundParams[0].TssPubkey) + + // check erc20, TSS pubkey and nonce produce unique index + require.NotEqual(t, cctx.Index, cctxDifferentERC20Address.Index) + require.NotEqual(t, cctx.Index, cctxDifferentNonce.Index) + require.NotEqual(t, cctx.Index, cctxDifferentTSSPubkey.Index) + }) +} + +func TestGetERC20CustodyMigrationCCTXIndexString(t *testing.T) { + t.Run("returns the unique index string for the CCTX for migrating ERC20 custody funds", func(t *testing.T) { + // ARRANGE + tssPubKey := sample.PubKeyString() + nonce := uint64(1) + chainID := int64(42) + erc20Address := sample.EthAddress().String() + + // ACT + index := types.GetERC20CustodyMigrationCCTXIndexString( + tssPubKey, + nonce, + chainID, + erc20Address, + ) + indexDifferentTSSPubkey := types.GetERC20CustodyMigrationCCTXIndexString( + sample.PubKeyString(), + nonce, + chainID, + erc20Address, + ) + indexDifferentNonce := types.GetERC20CustodyMigrationCCTXIndexString( + tssPubKey, + nonce+1, + chainID, + erc20Address, + ) + indexDifferentERC20Address := types.GetERC20CustodyMigrationCCTXIndexString( + tssPubKey, + nonce, + chainID, + sample.EthAddress().String(), + ) + indexDifferentChainID := types.GetERC20CustodyMigrationCCTXIndexString( + tssPubKey, + nonce, + chainID+1, + erc20Address, + ) + + // ASSERT + require.NotEmpty(t, index) + require.NotEqual(t, index, indexDifferentTSSPubkey) + require.NotEqual(t, index, indexDifferentNonce) + require.NotEqual(t, index, indexDifferentERC20Address) + require.NotEqual(t, index, indexDifferentChainID) + }) +} + +func TestUpdateERC20CustodyPauseStatusCmdCCTX(t *testing.T) { + t.Run("returns a new CCTX to pause ERC20Custody", func(t *testing.T) { + // ARRANGE + creator := sample.AccAddress() + custodyContractAddress := sample.EthAddress().String() + chainID := int64(42) + gasPrice := "100000" + priorityFee := "100000" + tssPubKey := sample.PubKeyString() + currentNonce := uint64(1) + + // ACT + cctx := types.UpdateERC20CustodyPauseStatusCmdCCTX( + creator, + custodyContractAddress, + chainID, + true, + gasPrice, + priorityFee, + tssPubKey, + currentNonce, + ) + cctxDifferentNonce := types.UpdateERC20CustodyPauseStatusCmdCCTX( + creator, + custodyContractAddress, + chainID, + true, + gasPrice, + priorityFee, + tssPubKey, + currentNonce+1, + ) + cctxDifferentTSSPubkey := types.UpdateERC20CustodyPauseStatusCmdCCTX( + creator, + custodyContractAddress, + chainID, + true, + gasPrice, + priorityFee, + sample.PubKeyString(), + currentNonce, + ) + cctxDifferentChainID := types.UpdateERC20CustodyPauseStatusCmdCCTX( + creator, + custodyContractAddress, + chainID+1, + true, + gasPrice, + priorityFee, + tssPubKey, + currentNonce, + ) + + // ASSERT + require.NotEmpty(t, cctx.Index) + require.EqualValues(t, creator, cctx.Creator) + require.EqualValues(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.EqualValues(t, fmt.Sprintf("%s:%s", + constant.CmdUpdateERC20CustodyPauseStatus, + constant.OptionPause, + ), cctx.RelayedMessage) + require.EqualValues(t, creator, cctx.InboundParams.Sender) + require.EqualValues(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.EqualValues(t, custodyContractAddress, cctx.OutboundParams[0].Receiver) + require.EqualValues(t, chainID, cctx.OutboundParams[0].ReceiverChainId) + require.EqualValues(t, coin.CoinType_Cmd, cctx.OutboundParams[0].CoinType) + require.EqualValues(t, sdkmath.NewUint(0), cctx.OutboundParams[0].Amount) + require.EqualValues(t, 100_000, cctx.OutboundParams[0].GasLimit) + require.EqualValues(t, gasPrice, cctx.OutboundParams[0].GasPrice) + require.EqualValues(t, priorityFee, cctx.OutboundParams[0].GasPriorityFee) + require.EqualValues(t, tssPubKey, cctx.OutboundParams[0].TssPubkey) + + // check erc20, TSS pubkey and nonce produce unique index + require.NotEqual(t, cctx.Index, cctxDifferentNonce.Index) + require.NotEqual(t, cctx.Index, cctxDifferentTSSPubkey.Index) + require.NotEqual(t, cctx.Index, cctxDifferentChainID.Index) + }) +} + +func TestGetERC20CustodyPausingCmdCCTXIndecString(t *testing.T) { + t.Run("returns the unique index string for the CCTX for updating ERC20 custody pause status", func(t *testing.T) { + // ARRANGE + tssPubKey := sample.PubKeyString() + nonce := uint64(1) + chainID := int64(42) + + // ACT + index := types.GetERC20CustodyPausingCmdCCTXIndexString( + tssPubKey, + nonce, + chainID, + ) + indexDifferentTSSPubkey := types.GetERC20CustodyPausingCmdCCTXIndexString( + sample.PubKeyString(), + nonce, + chainID, + ) + indexDifferentNonce := types.GetERC20CustodyPausingCmdCCTXIndexString( + tssPubKey, + nonce+1, + chainID, + ) + indexDifferentChainID := types.GetERC20CustodyPausingCmdCCTXIndexString( + tssPubKey, + nonce, + chainID+1, + ) + + // ASSERT + require.NotEmpty(t, index) + require.NotEqual(t, index, indexDifferentTSSPubkey) + require.NotEqual(t, index, indexDifferentNonce) + require.NotEqual(t, index, indexDifferentChainID) + }) +} + +func TestWhitelistERC20CmdCCTX(t *testing.T) { + t.Run("returns a new CCTX for whitelisting ERC20 tokens", func(t *testing.T) { + // ARRANGE + creator := sample.AccAddress() + zrc20Address := sample.EthAddress() + erc20Address := sample.EthAddress().Hex() + custodyAddress := sample.EthAddress().Hex() + chainID := int64(42) + gasPrice := "100000" + priorityFee := "100000" + tssPubKey := sample.PubKeyString() + + // ACT + cctx := types.WhitelistERC20CmdCCTX( + creator, + zrc20Address, + erc20Address, + custodyAddress, + chainID, + gasPrice, + priorityFee, + tssPubKey, + ) + cctxDifferentZRC20Address := types.WhitelistERC20CmdCCTX( + creator, + sample.EthAddress(), + erc20Address, + custodyAddress, + chainID, + gasPrice, + priorityFee, + tssPubKey, + ) + + // ASSERT + require.NotEmpty(t, cctx.Index) + require.EqualValues(t, creator, cctx.Creator) + require.EqualValues(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.EqualValues(t, fmt.Sprintf("%s:%s", constant.CmdWhitelistERC20, erc20Address), cctx.RelayedMessage) + require.EqualValues(t, creator, cctx.InboundParams.Sender) + require.EqualValues(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.EqualValues(t, custodyAddress, cctx.OutboundParams[0].Receiver) + require.EqualValues(t, chainID, cctx.OutboundParams[0].ReceiverChainId) + require.EqualValues(t, coin.CoinType_Cmd, cctx.OutboundParams[0].CoinType) + require.EqualValues(t, sdkmath.NewUint(0), cctx.OutboundParams[0].Amount) + require.EqualValues(t, 100_000, cctx.OutboundParams[0].GasLimit) + require.EqualValues(t, gasPrice, cctx.OutboundParams[0].GasPrice) + require.EqualValues(t, priorityFee, cctx.OutboundParams[0].GasPriorityFee) + require.EqualValues(t, tssPubKey, cctx.OutboundParams[0].TssPubkey) + + // check zrc20 address produces unique index + require.NotEqual(t, cctx.Index, cctxDifferentZRC20Address.Index) + }) +} + +func TestMigrateFundCmdCCTX(t *testing.T) { + t.Run("returns a new CCTX for migrating funds on EVM", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.Ethereum.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := sample.Tss() + + // ACT + cctx, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.NoError(t, err) + require.NotEmpty(t, cctx.Index) + require.EqualValues(t, creator, cctx.Creator) + require.EqualValues(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.EqualValues( + t, + fmt.Sprintf("%s:%s", constant.CmdMigrateTssFunds, "Funds Migrator Admin Cmd"), + cctx.RelayedMessage, + ) + require.NotEmpty(t, cctx.InboundParams.Sender) + require.EqualValues(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.NotEmpty(t, cctx.OutboundParams[0].Receiver) + require.EqualValues(t, chains.Ethereum.ChainId, cctx.OutboundParams[0].ReceiverChainId) + require.EqualValues(t, coin.CoinType_Cmd, cctx.OutboundParams[0].CoinType) + require.False(t, cctx.OutboundParams[0].Amount.IsZero()) + require.EqualValues(t, gas.EVMSend, cctx.OutboundParams[0].GasLimit) + require.NotEmpty(t, cctx.OutboundParams[0].GasPrice) + require.NotEmpty(t, cctx.OutboundParams[0].GasPriorityFee) + }) + + t.Run("returns a new CCTX for migrating funds on Bitcoin", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.BitcoinMainnet.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := sample.Tss() + + // ACT + cctx, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.NoError(t, err) + require.NotEmpty(t, cctx.Index) + require.EqualValues(t, creator, cctx.Creator) + require.EqualValues(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) + require.EqualValues( + t, + fmt.Sprintf("%s:%s", constant.CmdMigrateTssFunds, "Funds Migrator Admin Cmd"), + cctx.RelayedMessage, + ) + require.NotEmpty(t, cctx.InboundParams.Sender) + require.EqualValues(t, coin.CoinType_Cmd, cctx.InboundParams.CoinType) + require.Len(t, cctx.OutboundParams, 1) + require.NotEmpty(t, cctx.OutboundParams[0].Receiver) + require.EqualValues(t, chains.BitcoinMainnet.ChainId, cctx.OutboundParams[0].ReceiverChainId) + require.EqualValues(t, coin.CoinType_Cmd, cctx.OutboundParams[0].CoinType) + require.False(t, cctx.OutboundParams[0].Amount.IsZero()) + require.EqualValues(t, uint64(1_000_000), cctx.OutboundParams[0].GasLimit) + require.NotEmpty(t, cctx.OutboundParams[0].GasPrice) + require.NotEmpty(t, cctx.OutboundParams[0].GasPriorityFee) + }) + + t.Run("prevent migration with invalid ETH address for current TSS", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.Ethereum.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := "invalid" + newTSSPubkey := sample.Tss() + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration with invalid ETH address for new TSS", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.Ethereum.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := "invalid" + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration on EVM if fees higher than amount", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.Ethereum.ChainId + amount := sdkmath.NewUint(100_000_000) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := sample.Tss() + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration with invalid Bitcoin address for current TSS", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.BitcoinMainnet.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := "invalid" + newTSSPubkey := sample.Tss() + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration with invalid Bitcoin address for new TSS", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := chains.BitcoinMainnet.ChainId + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := "invalid" + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) + + t.Run("prevent migration if invalid chain ID", func(t *testing.T) { + // ARRANGE + blockHeight := int64(1000) + creator := sample.AccAddress() + inboundHash := sample.Hash().Hex() + chainID := int64(1000) + amount := sdkmath.NewUint(1e18) + medianGasPrice := sdkmath.NewUint(100000) + priorityFee := sdkmath.NewUint(100000) + currentTSSPubkey := sample.Tss() + newTSSPubkey := sample.Tss() + + // ACT + _, err := types.MigrateFundCmdCCTX( + blockHeight, + creator, + inboundHash, + chainID, + amount, + medianGasPrice, + priorityFee, + currentTSSPubkey.TssPubkey, + newTSSPubkey.TssPubkey, + []chains.Chain{}, + ) + + // ASSERT + require.Error(t, err) + }) +} + +func TestGetTssMigrationCCTXIndexString(t *testing.T) { + t.Run("returns unique index string for the CCTX for migrating funds", func(t *testing.T) { + // ARRANGE + currentTSSPubkey := sample.PubKeyString() + newTSSPubkey := sample.PubKeyString() + chainID := int64(42) + amount := sdkmath.NewUint(1000) + height := int64(1000) + + // ACT + index := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + newTSSPubkey, + chainID, + amount, + height, + ) + indexDifferentCurrentTSSPubkey := types.GetTssMigrationCCTXIndexString( + sample.PubKeyString(), + newTSSPubkey, + chainID, + amount, + height, + ) + indexDifferentNewTSSPubkey := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + sample.PubKeyString(), + chainID, + amount, + height, + ) + indexDifferentChainID := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + newTSSPubkey, + chainID+1, + amount, + height, + ) + indexDifferentAmount := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + newTSSPubkey, + chainID, + sdkmath.NewUint(1001), + height, + ) + indexDifferentHeight := types.GetTssMigrationCCTXIndexString( + currentTSSPubkey, + newTSSPubkey, + chainID, + amount, + height+1, + ) + + // ASSERT + require.NotEmpty(t, index) + require.NotEqual(t, index, indexDifferentCurrentTSSPubkey) + require.NotEqual(t, index, indexDifferentNewTSSPubkey) + require.NotEqual(t, index, indexDifferentChainID) + require.NotEqual(t, index, indexDifferentAmount) + require.NotEqual(t, index, indexDifferentHeight) + }) +} diff --git a/x/crosschain/types/cross_chain_tx.pb.go b/x/crosschain/types/cross_chain_tx.pb.go index 5817c7b709..526efa053e 100644 --- a/x/crosschain/types/cross_chain_tx.pb.go +++ b/x/crosschain/types/cross_chain_tx.pb.go @@ -90,6 +90,33 @@ func (TxFinalizationStatus) EnumDescriptor() ([]byte, []int) { return fileDescriptor_d4c1966807fb5cb2, []int{1} } +// ProtocolContractVersion represents the version of the protocol contract used +// for cctx workflow +type ProtocolContractVersion int32 + +const ( + ProtocolContractVersion_V1 ProtocolContractVersion = 0 + ProtocolContractVersion_V2 ProtocolContractVersion = 1 +) + +var ProtocolContractVersion_name = map[int32]string{ + 0: "V1", + 1: "V2", +} + +var ProtocolContractVersion_value = map[string]int32{ + "V1": 0, + "V2": 1, +} + +func (x ProtocolContractVersion) String() string { + return proto.EnumName(ProtocolContractVersion_name, int32(x)) +} + +func (ProtocolContractVersion) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_d4c1966807fb5cb2, []int{2} +} + type InboundParams struct { Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` // the Connector.send() @@ -404,6 +431,8 @@ type Status struct { StatusMessage string `protobuf:"bytes,2,opt,name=status_message,json=statusMessage,proto3" json:"status_message,omitempty"` LastUpdateTimestamp int64 `protobuf:"varint,3,opt,name=lastUpdate_timestamp,json=lastUpdateTimestamp,proto3" json:"lastUpdate_timestamp,omitempty"` IsAbortRefunded bool `protobuf:"varint,4,opt,name=isAbortRefunded,proto3" json:"isAbortRefunded,omitempty"` + // when the CCTX was created. only populated on new transactions. + CreatedTimestamp int64 `protobuf:"varint,5,opt,name=created_timestamp,json=createdTimestamp,proto3" json:"created_timestamp,omitempty"` } func (m *Status) Reset() { *m = Status{} } @@ -467,21 +496,100 @@ func (m *Status) GetIsAbortRefunded() bool { return false } +func (m *Status) GetCreatedTimestamp() int64 { + if m != nil { + return m.CreatedTimestamp + } + return 0 +} + +// RevertOptions represents the options for reverting a cctx +type RevertOptions struct { + RevertAddress string `protobuf:"bytes,1,opt,name=revert_address,json=revertAddress,proto3" json:"revert_address,omitempty"` + CallOnRevert bool `protobuf:"varint,2,opt,name=call_on_revert,json=callOnRevert,proto3" json:"call_on_revert,omitempty"` + AbortAddress string `protobuf:"bytes,3,opt,name=abort_address,json=abortAddress,proto3" json:"abort_address,omitempty"` + RevertMessage []byte `protobuf:"bytes,4,opt,name=revert_message,json=revertMessage,proto3" json:"revert_message,omitempty"` + RevertGasLimit github_com_cosmos_cosmos_sdk_types.Uint `protobuf:"bytes,5,opt,name=revert_gas_limit,json=revertGasLimit,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Uint" json:"revert_gas_limit"` +} + +func (m *RevertOptions) Reset() { *m = RevertOptions{} } +func (m *RevertOptions) String() string { return proto.CompactTextString(m) } +func (*RevertOptions) ProtoMessage() {} +func (*RevertOptions) Descriptor() ([]byte, []int) { + return fileDescriptor_d4c1966807fb5cb2, []int{4} +} +func (m *RevertOptions) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RevertOptions) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RevertOptions.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RevertOptions) XXX_Merge(src proto.Message) { + xxx_messageInfo_RevertOptions.Merge(m, src) +} +func (m *RevertOptions) XXX_Size() int { + return m.Size() +} +func (m *RevertOptions) XXX_DiscardUnknown() { + xxx_messageInfo_RevertOptions.DiscardUnknown(m) +} + +var xxx_messageInfo_RevertOptions proto.InternalMessageInfo + +func (m *RevertOptions) GetRevertAddress() string { + if m != nil { + return m.RevertAddress + } + return "" +} + +func (m *RevertOptions) GetCallOnRevert() bool { + if m != nil { + return m.CallOnRevert + } + return false +} + +func (m *RevertOptions) GetAbortAddress() string { + if m != nil { + return m.AbortAddress + } + return "" +} + +func (m *RevertOptions) GetRevertMessage() []byte { + if m != nil { + return m.RevertMessage + } + return nil +} + type CrossChainTx struct { - Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` - Index string `protobuf:"bytes,2,opt,name=index,proto3" json:"index,omitempty"` - ZetaFees github_com_cosmos_cosmos_sdk_types.Uint `protobuf:"bytes,5,opt,name=zeta_fees,json=zetaFees,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Uint" json:"zeta_fees" yaml:"zeta_fees"` - RelayedMessage string `protobuf:"bytes,6,opt,name=relayed_message,json=relayedMessage,proto3" json:"relayed_message,omitempty"` - CctxStatus *Status `protobuf:"bytes,8,opt,name=cctx_status,json=cctxStatus,proto3" json:"cctx_status,omitempty"` - InboundParams *InboundParams `protobuf:"bytes,9,opt,name=inbound_params,json=inboundParams,proto3" json:"inbound_params,omitempty"` - OutboundParams []*OutboundParams `protobuf:"bytes,10,rep,name=outbound_params,json=outboundParams,proto3" json:"outbound_params,omitempty"` + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` + Index string `protobuf:"bytes,2,opt,name=index,proto3" json:"index,omitempty"` + ZetaFees github_com_cosmos_cosmos_sdk_types.Uint `protobuf:"bytes,5,opt,name=zeta_fees,json=zetaFees,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Uint" json:"zeta_fees" yaml:"zeta_fees"` + RelayedMessage string `protobuf:"bytes,6,opt,name=relayed_message,json=relayedMessage,proto3" json:"relayed_message,omitempty"` + CctxStatus *Status `protobuf:"bytes,8,opt,name=cctx_status,json=cctxStatus,proto3" json:"cctx_status,omitempty"` + InboundParams *InboundParams `protobuf:"bytes,9,opt,name=inbound_params,json=inboundParams,proto3" json:"inbound_params,omitempty"` + OutboundParams []*OutboundParams `protobuf:"bytes,10,rep,name=outbound_params,json=outboundParams,proto3" json:"outbound_params,omitempty"` + ProtocolContractVersion ProtocolContractVersion `protobuf:"varint,11,opt,name=protocol_contract_version,json=protocolContractVersion,proto3,enum=zetachain.zetacore.crosschain.ProtocolContractVersion" json:"protocol_contract_version,omitempty"` + RevertOptions RevertOptions `protobuf:"bytes,12,opt,name=revert_options,json=revertOptions,proto3" json:"revert_options"` } func (m *CrossChainTx) Reset() { *m = CrossChainTx{} } func (m *CrossChainTx) String() string { return proto.CompactTextString(m) } func (*CrossChainTx) ProtoMessage() {} func (*CrossChainTx) Descriptor() ([]byte, []int) { - return fileDescriptor_d4c1966807fb5cb2, []int{4} + return fileDescriptor_d4c1966807fb5cb2, []int{5} } func (m *CrossChainTx) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -552,13 +660,29 @@ func (m *CrossChainTx) GetOutboundParams() []*OutboundParams { return nil } +func (m *CrossChainTx) GetProtocolContractVersion() ProtocolContractVersion { + if m != nil { + return m.ProtocolContractVersion + } + return ProtocolContractVersion_V1 +} + +func (m *CrossChainTx) GetRevertOptions() RevertOptions { + if m != nil { + return m.RevertOptions + } + return RevertOptions{} +} + func init() { proto.RegisterEnum("zetachain.zetacore.crosschain.CctxStatus", CctxStatus_name, CctxStatus_value) proto.RegisterEnum("zetachain.zetacore.crosschain.TxFinalizationStatus", TxFinalizationStatus_name, TxFinalizationStatus_value) + proto.RegisterEnum("zetachain.zetacore.crosschain.ProtocolContractVersion", ProtocolContractVersion_name, ProtocolContractVersion_value) proto.RegisterType((*InboundParams)(nil), "zetachain.zetacore.crosschain.InboundParams") proto.RegisterType((*ZetaAccounting)(nil), "zetachain.zetacore.crosschain.ZetaAccounting") proto.RegisterType((*OutboundParams)(nil), "zetachain.zetacore.crosschain.OutboundParams") proto.RegisterType((*Status)(nil), "zetachain.zetacore.crosschain.Status") + proto.RegisterType((*RevertOptions)(nil), "zetachain.zetacore.crosschain.RevertOptions") proto.RegisterType((*CrossChainTx)(nil), "zetachain.zetacore.crosschain.CrossChainTx") } @@ -567,76 +691,88 @@ func init() { } var fileDescriptor_d4c1966807fb5cb2 = []byte{ - // 1100 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0xcd, 0x72, 0x13, 0x47, - 0x10, 0xd6, 0x22, 0x21, 0x4b, 0xad, 0xbf, 0x65, 0x2c, 0x9c, 0x8d, 0x53, 0x08, 0x47, 0x29, 0x40, - 0x50, 0x41, 0x2a, 0xcc, 0x25, 0x95, 0x9b, 0xed, 0xc2, 0xe0, 0x10, 0xc0, 0xb5, 0x98, 0x1c, 0x38, - 0x64, 0x33, 0xda, 0x6d, 0xad, 0xa6, 0x2c, 0xed, 0x28, 0x3b, 0x23, 0xd7, 0x8a, 0xca, 0x43, 0x24, - 0xef, 0x90, 0x43, 0x8e, 0x79, 0x82, 0x9c, 0xb9, 0x85, 0x63, 0x2a, 0x07, 0x2a, 0x85, 0xdf, 0x20, - 0x4f, 0x90, 0x9a, 0x99, 0x5d, 0xc9, 0x72, 0xb9, 0x6c, 0x42, 0x72, 0x52, 0xf7, 0xd7, 0x33, 0x5f, - 0xb7, 0x7a, 0xbe, 0x9e, 0x59, 0xd8, 0x7c, 0x85, 0x92, 0xfa, 0x43, 0xca, 0xa2, 0x9e, 0xb6, 0x78, - 0x8c, 0x3d, 0x3f, 0xe6, 0x42, 0x18, 0x4c, 0x9b, 0x9e, 0xb6, 0x3d, 0x99, 0x74, 0x27, 0x31, 0x97, - 0x9c, 0x5c, 0x9b, 0xef, 0xe9, 0x66, 0x7b, 0xba, 0x8b, 0x3d, 0xeb, 0xcd, 0x90, 0x87, 0x5c, 0xaf, - 0xec, 0x29, 0xcb, 0x6c, 0x5a, 0xbf, 0x79, 0x46, 0xa2, 0xc9, 0x61, 0xd8, 0xf3, 0xb9, 0x4a, 0xc3, - 0x59, 0x64, 0xd6, 0xb5, 0x7f, 0x2d, 0x40, 0x6d, 0x2f, 0xea, 0xf3, 0x69, 0x14, 0xec, 0xd3, 0x98, - 0x8e, 0x05, 0x59, 0x83, 0xa2, 0xc0, 0x28, 0xc0, 0xd8, 0xb1, 0x36, 0xac, 0x4e, 0xd9, 0x4d, 0x3d, - 0x72, 0x13, 0x1a, 0xc6, 0x4a, 0xeb, 0x63, 0x81, 0x73, 0x69, 0xc3, 0xea, 0xe4, 0xdd, 0x9a, 0x81, - 0x77, 0x14, 0xba, 0x17, 0x90, 0x4f, 0xa0, 0x2c, 0x13, 0x8f, 0xc7, 0x2c, 0x64, 0x91, 0x93, 0xd7, - 0x14, 0x25, 0x99, 0x3c, 0xd3, 0x3e, 0xd9, 0x86, 0xb2, 0x4a, 0xee, 0xc9, 0xd9, 0x04, 0x9d, 0xc2, - 0x86, 0xd5, 0xa9, 0x6f, 0xde, 0xe8, 0x9e, 0xf1, 0xff, 0x26, 0x87, 0x61, 0x57, 0x57, 0xb9, 0xc3, - 0x59, 0x74, 0x30, 0x9b, 0xa0, 0x5b, 0xf2, 0x53, 0x8b, 0x34, 0xe1, 0x32, 0x15, 0x02, 0xa5, 0x73, - 0x59, 0x93, 0x1b, 0x87, 0x3c, 0x84, 0x22, 0x1d, 0xf3, 0x69, 0x24, 0x9d, 0xa2, 0x82, 0xb7, 0x7b, - 0xaf, 0xdf, 0x5e, 0xcf, 0xfd, 0xf9, 0xf6, 0xfa, 0xad, 0x90, 0xc9, 0xe1, 0xb4, 0xdf, 0xf5, 0xf9, - 0xb8, 0xe7, 0x73, 0x31, 0xe6, 0x22, 0xfd, 0xb9, 0x2b, 0x82, 0xc3, 0x9e, 0xaa, 0x43, 0x74, 0x5f, - 0xb0, 0x48, 0xba, 0xe9, 0x76, 0xf2, 0x19, 0xd4, 0x78, 0x5f, 0x60, 0x7c, 0x84, 0x81, 0x37, 0xa4, - 0x62, 0xe8, 0xac, 0xe8, 0x34, 0xd5, 0x0c, 0x7c, 0x44, 0xc5, 0x90, 0x7c, 0x01, 0xce, 0x7c, 0x11, - 0x26, 0x12, 0xe3, 0x88, 0x8e, 0xbc, 0x21, 0xb2, 0x70, 0x28, 0x9d, 0xd2, 0x86, 0xd5, 0x29, 0xb8, - 0x6b, 0x59, 0xfc, 0x41, 0x1a, 0x7e, 0xa4, 0xa3, 0xe4, 0x53, 0xa8, 0xf6, 0xe9, 0x68, 0xc4, 0xa5, - 0xc7, 0xa2, 0x00, 0x13, 0xa7, 0xac, 0xd9, 0x2b, 0x06, 0xdb, 0x53, 0x10, 0xd9, 0x84, 0xab, 0x03, - 0x16, 0xd1, 0x11, 0x7b, 0x85, 0x81, 0xa7, 0x5a, 0x92, 0x31, 0x83, 0x66, 0x5e, 0x9d, 0x07, 0x5f, - 0xa2, 0xa4, 0x29, 0x2d, 0x83, 0x35, 0x99, 0x78, 0x69, 0x84, 0x4a, 0xc6, 0x23, 0x4f, 0x48, 0x2a, - 0xa7, 0xc2, 0xa9, 0xe8, 0x2e, 0xdf, 0xef, 0x9e, 0xab, 0xa2, 0xee, 0x41, 0xb2, 0x7b, 0x62, 0xef, - 0x73, 0xbd, 0xd5, 0x6d, 0xca, 0x33, 0xd0, 0xf6, 0xf7, 0x50, 0x57, 0x89, 0xb7, 0x7c, 0x5f, 0xf5, - 0x8b, 0x45, 0x21, 0xf1, 0x60, 0x95, 0xf6, 0x79, 0x2c, 0xb3, 0x72, 0xd3, 0x83, 0xb0, 0x3e, 0xec, - 0x20, 0xae, 0xa4, 0x5c, 0x3a, 0x89, 0x66, 0x6a, 0xff, 0x54, 0x84, 0xfa, 0xb3, 0xa9, 0x3c, 0x29, - 0xd3, 0x75, 0x28, 0xc5, 0xe8, 0x23, 0x3b, 0x9a, 0x0b, 0x75, 0xee, 0x93, 0xdb, 0x60, 0x67, 0xb6, - 0x11, 0xeb, 0x5e, 0xa6, 0xd5, 0x46, 0x86, 0x67, 0x6a, 0x5d, 0x12, 0x64, 0xfe, 0xc3, 0x04, 0xb9, - 0x90, 0x5e, 0xe1, 0xbf, 0x49, 0x4f, 0x8d, 0x8e, 0x10, 0x5e, 0xc4, 0x23, 0x1f, 0xb5, 0xba, 0x0b, - 0x6e, 0x49, 0x0a, 0xf1, 0x54, 0xf9, 0x2a, 0x18, 0x52, 0xe1, 0x8d, 0xd8, 0x98, 0x19, 0x8d, 0x17, - 0xdc, 0x52, 0x48, 0xc5, 0xd7, 0xca, 0xcf, 0x82, 0x93, 0x98, 0xf9, 0x98, 0x0a, 0x56, 0x05, 0xf7, - 0x95, 0x4f, 0x3a, 0x60, 0xa7, 0x41, 0x1e, 0x33, 0x39, 0xf3, 0x06, 0x88, 0xce, 0x47, 0x7a, 0x4d, - 0xdd, 0xac, 0xd1, 0xf0, 0x2e, 0x22, 0x21, 0x50, 0xd0, 0x92, 0x2f, 0xe9, 0xa8, 0xb6, 0xdf, 0x47, - 0xb0, 0xe7, 0x4d, 0x03, 0x9c, 0x3b, 0x0d, 0x1f, 0x83, 0x2a, 0xd3, 0x9b, 0x0a, 0x0c, 0x9c, 0xa6, - 0x5e, 0xb9, 0x12, 0x52, 0xf1, 0x42, 0x60, 0x40, 0xbe, 0x85, 0x55, 0x1c, 0x0c, 0xd0, 0x97, 0xec, - 0x08, 0xbd, 0xc5, 0x9f, 0xbb, 0xaa, 0x5b, 0xdc, 0x4d, 0x5b, 0x7c, 0xf3, 0x3d, 0x5a, 0xbc, 0xa7, - 0x34, 0x35, 0xa7, 0x7a, 0x98, 0x75, 0xa5, 0x7b, 0x9a, 0xdf, 0x74, 0x76, 0x4d, 0x57, 0xb1, 0xb4, - 0xde, 0xb4, 0xf8, 0x1a, 0x80, 0x3a, 0x9c, 0xc9, 0xb4, 0x7f, 0x88, 0x33, 0x3d, 0x55, 0x65, 0x57, - 0x1d, 0xd7, 0xbe, 0x06, 0xce, 0x19, 0xc0, 0xea, 0xff, 0x3c, 0x80, 0x5f, 0x15, 0x4a, 0x35, 0xbb, - 0xd9, 0xfe, 0xdd, 0x82, 0xa2, 0x01, 0xc8, 0x16, 0x14, 0xd3, 0x5c, 0x96, 0xce, 0x75, 0xfb, 0x82, - 0x5c, 0x3b, 0xbe, 0x4c, 0xd2, 0x0c, 0xe9, 0x46, 0x72, 0x03, 0xea, 0xc6, 0xf2, 0xc6, 0x28, 0x04, - 0x0d, 0x51, 0x0f, 0x4c, 0xd9, 0xad, 0x19, 0xf4, 0x89, 0x01, 0xc9, 0x3d, 0x68, 0x8e, 0xa8, 0x90, - 0x2f, 0x26, 0x01, 0x95, 0xe8, 0x49, 0x36, 0x46, 0x21, 0xe9, 0x78, 0xa2, 0x27, 0x27, 0xef, 0xae, - 0x2e, 0x62, 0x07, 0x59, 0x88, 0x74, 0xa0, 0xc1, 0xc4, 0x96, 0x1a, 0x69, 0x17, 0x07, 0xd3, 0x28, - 0xc0, 0x40, 0x8f, 0x49, 0xc9, 0x3d, 0x0d, 0xb7, 0x7f, 0xcb, 0x43, 0x75, 0x47, 0x55, 0xa9, 0x87, - 0xf3, 0x20, 0x21, 0x0e, 0xac, 0xf8, 0x31, 0x52, 0xc9, 0xb3, 0x11, 0xcf, 0x5c, 0xf5, 0x06, 0x18, - 0x35, 0x9a, 0x2a, 0x8d, 0x43, 0xbe, 0x83, 0xb2, 0xbe, 0x7f, 0x06, 0x88, 0xc2, 0xbc, 0x0e, 0xdb, - 0x3b, 0xff, 0x72, 0x16, 0xff, 0x7e, 0x7b, 0xdd, 0x9e, 0xd1, 0xf1, 0xe8, 0xcb, 0xf6, 0x9c, 0xa9, - 0xed, 0x96, 0x94, 0xbd, 0x8b, 0x28, 0xc8, 0x2d, 0x68, 0xc4, 0x38, 0xa2, 0x33, 0x0c, 0xe6, 0x7d, - 0x2a, 0x9a, 0x49, 0x4a, 0xe1, 0xac, 0x51, 0xbb, 0x50, 0xf1, 0x7d, 0x99, 0x64, 0x1a, 0x50, 0x03, - 0x55, 0x39, 0xfb, 0x66, 0x39, 0x71, 0x2e, 0xe9, 0x99, 0x80, 0x3f, 0x3f, 0x1f, 0xf2, 0x1c, 0xea, - 0xcc, 0x3c, 0xcf, 0xde, 0x44, 0x5f, 0x7c, 0x7a, 0xfe, 0x2a, 0x9b, 0x9f, 0x5f, 0x40, 0xb5, 0xf4, - 0xa6, 0xbb, 0x35, 0xb6, 0xf4, 0xc4, 0x7f, 0x03, 0x0d, 0x9e, 0xde, 0xa6, 0x19, 0x2b, 0x6c, 0xe4, - 0x3b, 0x95, 0xcd, 0xbb, 0x17, 0xb0, 0x2e, 0xdf, 0xc1, 0x6e, 0x9d, 0x2f, 0xf9, 0x77, 0x7e, 0x00, - 0x58, 0x48, 0x8b, 0x10, 0xa8, 0xef, 0x63, 0x14, 0xb0, 0x28, 0x4c, 0x8b, 0xb1, 0x73, 0x64, 0x15, - 0x1a, 0x29, 0x96, 0x51, 0xd9, 0x16, 0xb9, 0x02, 0xb5, 0xcc, 0x7b, 0xc2, 0x22, 0x0c, 0xec, 0xbc, - 0x82, 0xd2, 0x75, 0x2e, 0x1e, 0x61, 0x2c, 0xed, 0x02, 0xa9, 0x42, 0xc9, 0xd8, 0x18, 0xd8, 0x97, - 0x49, 0x05, 0x56, 0xb6, 0xcc, 0x33, 0x61, 0x17, 0xd7, 0x0b, 0xbf, 0xfc, 0xdc, 0xb2, 0xee, 0x3c, - 0x86, 0xe6, 0x59, 0x43, 0x44, 0x6c, 0xa8, 0x3e, 0xe5, 0x72, 0x37, 0x7b, 0x34, 0xed, 0x1c, 0xa9, - 0x41, 0x79, 0xe1, 0x5a, 0x8a, 0xf9, 0x41, 0x82, 0xfe, 0x54, 0x91, 0x5d, 0x32, 0x64, 0xdb, 0x8f, - 0x5f, 0xbf, 0x6b, 0x59, 0x6f, 0xde, 0xb5, 0xac, 0xbf, 0xde, 0xb5, 0xac, 0x1f, 0x8f, 0x5b, 0xb9, - 0x37, 0xc7, 0xad, 0xdc, 0x1f, 0xc7, 0xad, 0xdc, 0xcb, 0x7b, 0x27, 0x94, 0xa4, 0x7a, 0x74, 0xf7, - 0xd4, 0x57, 0x56, 0x72, 0xf2, 0x83, 0x4e, 0x0b, 0xab, 0x5f, 0xd4, 0xdf, 0x5a, 0xf7, 0xff, 0x09, - 0x00, 0x00, 0xff, 0xff, 0xea, 0x6a, 0x48, 0x57, 0xfe, 0x09, 0x00, 0x00, + // 1286 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x4f, 0x6f, 0x13, 0x47, + 0x14, 0xf7, 0x26, 0x8e, 0x63, 0x3f, 0xff, 0xc9, 0x32, 0x31, 0x61, 0x49, 0x85, 0x49, 0xdd, 0x02, + 0x86, 0x16, 0x5b, 0x04, 0xa9, 0xaa, 0x7a, 0x4b, 0x22, 0x02, 0x29, 0x85, 0x44, 0x4b, 0x40, 0x82, + 0x43, 0xb7, 0xe3, 0xdd, 0xc9, 0x7a, 0x14, 0x7b, 0xc7, 0xdd, 0x19, 0x47, 0x0e, 0xea, 0xad, 0xe7, + 0x4a, 0xed, 0x77, 0xe8, 0xa1, 0xc7, 0x7e, 0x0c, 0x8e, 0x1c, 0xab, 0x1e, 0x50, 0x05, 0xdf, 0xa0, + 0xe7, 0x1e, 0xaa, 0xf9, 0xb7, 0x8e, 0x91, 0x9b, 0x50, 0xda, 0x93, 0x67, 0x7e, 0x6f, 0xde, 0xef, + 0xcd, 0xbe, 0xf9, 0xbd, 0x37, 0x63, 0x58, 0x7f, 0x4e, 0x04, 0x0e, 0x7b, 0x98, 0x26, 0x1d, 0x35, + 0x62, 0x29, 0xe9, 0x84, 0x29, 0xe3, 0x5c, 0x63, 0x6a, 0x18, 0xa8, 0x71, 0x20, 0xc6, 0xed, 0x61, + 0xca, 0x04, 0x43, 0x97, 0x32, 0x9f, 0xb6, 0xf5, 0x69, 0x4f, 0x7c, 0x56, 0xeb, 0x31, 0x8b, 0x99, + 0x5a, 0xd9, 0x91, 0x23, 0xed, 0xb4, 0x7a, 0x75, 0x46, 0xa0, 0xe1, 0x61, 0xdc, 0x09, 0x99, 0x0c, + 0xc3, 0x68, 0xa2, 0xd7, 0x35, 0x7f, 0xcd, 0x43, 0x75, 0x27, 0xe9, 0xb2, 0x51, 0x12, 0xed, 0xe1, + 0x14, 0x0f, 0x38, 0x5a, 0x81, 0x02, 0x27, 0x49, 0x44, 0x52, 0xcf, 0x59, 0x73, 0x5a, 0x25, 0xdf, + 0xcc, 0xd0, 0x55, 0x58, 0xd2, 0x23, 0xb3, 0x3f, 0x1a, 0x79, 0x73, 0x6b, 0x4e, 0x6b, 0xde, 0xaf, + 0x6a, 0x78, 0x4b, 0xa2, 0x3b, 0x11, 0xfa, 0x00, 0x4a, 0x62, 0x1c, 0xb0, 0x94, 0xc6, 0x34, 0xf1, + 0xe6, 0x15, 0x45, 0x51, 0x8c, 0x77, 0xd5, 0x1c, 0x6d, 0x42, 0x49, 0x06, 0x0f, 0xc4, 0xf1, 0x90, + 0x78, 0xf9, 0x35, 0xa7, 0x55, 0x5b, 0xbf, 0xd2, 0x9e, 0xf1, 0x7d, 0xc3, 0xc3, 0xb8, 0xad, 0x76, + 0xb9, 0xc5, 0x68, 0xb2, 0x7f, 0x3c, 0x24, 0x7e, 0x31, 0x34, 0x23, 0x54, 0x87, 0x05, 0xcc, 0x39, + 0x11, 0xde, 0x82, 0x22, 0xd7, 0x13, 0x74, 0x17, 0x0a, 0x78, 0xc0, 0x46, 0x89, 0xf0, 0x0a, 0x12, + 0xde, 0xec, 0xbc, 0x78, 0x75, 0x39, 0xf7, 0xfb, 0xab, 0xcb, 0xd7, 0x62, 0x2a, 0x7a, 0xa3, 0x6e, + 0x3b, 0x64, 0x83, 0x4e, 0xc8, 0xf8, 0x80, 0x71, 0xf3, 0x73, 0x93, 0x47, 0x87, 0x1d, 0xb9, 0x0f, + 0xde, 0x7e, 0x4c, 0x13, 0xe1, 0x1b, 0x77, 0xf4, 0x11, 0x54, 0x59, 0x97, 0x93, 0xf4, 0x88, 0x44, + 0x41, 0x0f, 0xf3, 0x9e, 0xb7, 0xa8, 0xc2, 0x54, 0x2c, 0x78, 0x0f, 0xf3, 0x1e, 0xfa, 0x1c, 0xbc, + 0x6c, 0x11, 0x19, 0x0b, 0x92, 0x26, 0xb8, 0x1f, 0xf4, 0x08, 0x8d, 0x7b, 0xc2, 0x2b, 0xae, 0x39, + 0xad, 0xbc, 0xbf, 0x62, 0xed, 0x77, 0x8c, 0xf9, 0x9e, 0xb2, 0xa2, 0x0f, 0xa1, 0xd2, 0xc5, 0xfd, + 0x3e, 0x13, 0x01, 0x4d, 0x22, 0x32, 0xf6, 0x4a, 0x8a, 0xbd, 0xac, 0xb1, 0x1d, 0x09, 0xa1, 0x75, + 0x38, 0x7f, 0x40, 0x13, 0xdc, 0xa7, 0xcf, 0x49, 0x14, 0xc8, 0x94, 0x58, 0x66, 0x50, 0xcc, 0xcb, + 0x99, 0xf1, 0x19, 0x11, 0xd8, 0xd0, 0x52, 0x58, 0x11, 0xe3, 0xc0, 0x58, 0xb0, 0xa0, 0x2c, 0x09, + 0xb8, 0xc0, 0x62, 0xc4, 0xbd, 0xb2, 0xca, 0xf2, 0xed, 0xf6, 0xa9, 0x2a, 0x6a, 0xef, 0x8f, 0xb7, + 0x4f, 0xf8, 0x3e, 0x52, 0xae, 0x7e, 0x5d, 0xcc, 0x40, 0x9b, 0xdf, 0x42, 0x4d, 0x06, 0xde, 0x08, + 0x43, 0x99, 0x2f, 0x9a, 0xc4, 0x28, 0x80, 0x65, 0xdc, 0x65, 0xa9, 0xb0, 0xdb, 0x35, 0x07, 0xe1, + 0xbc, 0xdf, 0x41, 0x9c, 0x33, 0x5c, 0x2a, 0x88, 0x62, 0x6a, 0xfe, 0x54, 0x80, 0xda, 0xee, 0x48, + 0x9c, 0x94, 0xe9, 0x2a, 0x14, 0x53, 0x12, 0x12, 0x7a, 0x94, 0x09, 0x35, 0x9b, 0xa3, 0xeb, 0xe0, + 0xda, 0xb1, 0x16, 0xeb, 0x8e, 0xd5, 0xea, 0x92, 0xc5, 0xad, 0x5a, 0xa7, 0x04, 0x39, 0xff, 0x7e, + 0x82, 0x9c, 0x48, 0x2f, 0xff, 0xdf, 0xa4, 0x27, 0x4b, 0x87, 0xf3, 0x20, 0x61, 0x49, 0x48, 0x94, + 0xba, 0xf3, 0x7e, 0x51, 0x70, 0xfe, 0x50, 0xce, 0xa5, 0x31, 0xc6, 0x3c, 0xe8, 0xd3, 0x01, 0xd5, + 0x1a, 0xcf, 0xfb, 0xc5, 0x18, 0xf3, 0xaf, 0xe4, 0xdc, 0x1a, 0x87, 0x29, 0x0d, 0x89, 0x11, 0xac, + 0x34, 0xee, 0xc9, 0x39, 0x6a, 0x81, 0x6b, 0x8c, 0x2c, 0xa5, 0xe2, 0x38, 0x38, 0x20, 0xc4, 0xbb, + 0xa0, 0xd6, 0xd4, 0xf4, 0x1a, 0x05, 0x6f, 0x13, 0x82, 0x10, 0xe4, 0x95, 0xe4, 0x8b, 0xca, 0xaa, + 0xc6, 0xef, 0x22, 0xd8, 0xd3, 0xaa, 0x01, 0x4e, 0xad, 0x86, 0x8b, 0x20, 0xb7, 0x19, 0x8c, 0x38, + 0x89, 0xbc, 0xba, 0x5a, 0xb9, 0x18, 0x63, 0xfe, 0x98, 0x93, 0x08, 0x7d, 0x0d, 0xcb, 0xe4, 0xe0, + 0x80, 0x84, 0x82, 0x1e, 0x91, 0x60, 0xf2, 0x71, 0xe7, 0x55, 0x8a, 0xdb, 0x26, 0xc5, 0x57, 0xdf, + 0x21, 0xc5, 0x3b, 0x52, 0x53, 0x19, 0xd5, 0x5d, 0x9b, 0x95, 0xf6, 0xdb, 0xfc, 0x3a, 0xb3, 0x2b, + 0x6a, 0x17, 0x53, 0xeb, 0x75, 0x8a, 0x2f, 0x01, 0xc8, 0xc3, 0x19, 0x8e, 0xba, 0x87, 0xe4, 0x58, + 0x55, 0x55, 0xc9, 0x97, 0xc7, 0xb5, 0xa7, 0x80, 0x53, 0x0a, 0xb0, 0xf2, 0x3f, 0x17, 0xe0, 0x97, + 0xf9, 0x62, 0xd5, 0xad, 0x37, 0xff, 0x72, 0xa0, 0xa0, 0x01, 0xb4, 0x01, 0x05, 0x13, 0xcb, 0x51, + 0xb1, 0xae, 0x9f, 0x11, 0x6b, 0x2b, 0x14, 0x63, 0x13, 0xc1, 0x38, 0xa2, 0x2b, 0x50, 0xd3, 0xa3, + 0x60, 0x40, 0x38, 0xc7, 0x31, 0x51, 0x05, 0x53, 0xf2, 0xab, 0x1a, 0x7d, 0xa0, 0x41, 0x74, 0x0b, + 0xea, 0x7d, 0xcc, 0xc5, 0xe3, 0x61, 0x84, 0x05, 0x09, 0x04, 0x1d, 0x10, 0x2e, 0xf0, 0x60, 0xa8, + 0x2a, 0x67, 0xde, 0x5f, 0x9e, 0xd8, 0xf6, 0xad, 0x09, 0xb5, 0x60, 0x89, 0xf2, 0x0d, 0x59, 0xd2, + 0x3e, 0x39, 0x18, 0x25, 0x11, 0x89, 0x54, 0x99, 0x14, 0xfd, 0xb7, 0x61, 0xf4, 0x09, 0x9c, 0x0b, + 0x53, 0x82, 0x65, 0x1b, 0x99, 0x30, 0x2f, 0x28, 0x66, 0xd7, 0x18, 0x32, 0xda, 0xe6, 0xf7, 0x73, + 0x50, 0xf5, 0xc9, 0x11, 0x49, 0xc5, 0xee, 0x50, 0xe6, 0x46, 0x7d, 0x42, 0xaa, 0x80, 0x00, 0x47, + 0x51, 0x4a, 0x38, 0x37, 0x7d, 0xa1, 0xaa, 0xd1, 0x0d, 0x0d, 0xa2, 0x8f, 0xa1, 0x16, 0xe2, 0x7e, + 0x3f, 0x60, 0x49, 0xa0, 0x0d, 0xea, 0x4b, 0x8b, 0x7e, 0x45, 0xa2, 0xbb, 0x89, 0xe6, 0x94, 0xb7, + 0x80, 0x6a, 0x43, 0x19, 0x97, 0xbe, 0xc9, 0x2a, 0x0a, 0xb4, 0x54, 0x93, 0x88, 0x36, 0x69, 0xf2, + 0xcb, 0x2a, 0x36, 0xa2, 0x4d, 0xda, 0x53, 0xd9, 0x8e, 0xd4, 0xb2, 0x89, 0xcc, 0x16, 0xde, 0xaf, + 0x53, 0x98, 0x78, 0x56, 0x94, 0xcd, 0x1f, 0x16, 0xa0, 0xb2, 0x25, 0x0f, 0x56, 0xf5, 0xb3, 0xfd, + 0x31, 0xf2, 0x60, 0x51, 0xa5, 0x8a, 0xd9, 0xae, 0x68, 0xa7, 0xf2, 0xda, 0xd4, 0x05, 0xac, 0x0f, + 0x56, 0x4f, 0xd0, 0x37, 0x50, 0x52, 0x2d, 0xfb, 0x80, 0x10, 0x6e, 0x36, 0xb5, 0xf5, 0x2f, 0x37, + 0xf5, 0xe7, 0xab, 0xcb, 0xee, 0x31, 0x1e, 0xf4, 0xbf, 0x68, 0x66, 0x4c, 0x4d, 0xbf, 0x28, 0xc7, + 0xdb, 0x84, 0x70, 0x74, 0x0d, 0x96, 0x52, 0xd2, 0xc7, 0xc7, 0x24, 0xca, 0xb2, 0x54, 0xd0, 0xcd, + 0xc7, 0xc0, 0x36, 0x4d, 0xdb, 0x50, 0x0e, 0x43, 0x31, 0xb6, 0x65, 0x23, 0x7b, 0x50, 0x79, 0x76, + 0x33, 0x3e, 0x21, 0x65, 0x23, 0x63, 0x08, 0x33, 0x49, 0xa3, 0x47, 0x50, 0xa3, 0xfa, 0x45, 0x13, + 0x0c, 0xd5, 0x5d, 0xa1, 0x5a, 0x56, 0x79, 0xfd, 0xd3, 0x33, 0xa8, 0xa6, 0x9e, 0x41, 0x7e, 0x95, + 0x4e, 0xbd, 0x8a, 0x9e, 0xc0, 0x12, 0x33, 0x17, 0x90, 0x65, 0x85, 0xb5, 0xf9, 0x56, 0x79, 0xfd, + 0xe6, 0x19, 0xac, 0xd3, 0xd7, 0x96, 0x5f, 0x63, 0xd3, 0xd7, 0x58, 0x0a, 0x17, 0xd5, 0x43, 0x2c, + 0x64, 0xfd, 0x20, 0x64, 0x89, 0x48, 0x71, 0x28, 0x82, 0x23, 0x92, 0x72, 0xca, 0x12, 0x73, 0x75, + 0x7f, 0x76, 0x46, 0x84, 0x3d, 0xe3, 0xbf, 0x65, 0xdc, 0x9f, 0x68, 0x6f, 0xff, 0xc2, 0x70, 0xb6, + 0x01, 0x3d, 0xcd, 0x64, 0xcb, 0x74, 0xe9, 0xa8, 0x16, 0x75, 0x76, 0x82, 0xa6, 0xca, 0x6d, 0x33, + 0x2f, 0x65, 0x62, 0xa5, 0x6e, 0xc0, 0x1b, 0xdf, 0x01, 0x4c, 0x9a, 0x0b, 0x42, 0x50, 0xdb, 0x23, + 0x49, 0x44, 0x93, 0xd8, 0xe4, 0xd6, 0xcd, 0xa1, 0x65, 0x58, 0x32, 0x98, 0xcd, 0x8c, 0xeb, 0xa0, + 0x73, 0x50, 0xb5, 0xb3, 0x07, 0x34, 0x21, 0x91, 0x3b, 0x2f, 0x21, 0xb3, 0x4e, 0x87, 0x75, 0xf3, + 0xa8, 0x02, 0x45, 0x3d, 0x26, 0x91, 0xbb, 0x80, 0xca, 0xb0, 0xb8, 0xa1, 0x1f, 0x0a, 0x6e, 0x61, + 0x35, 0xff, 0xcb, 0xcf, 0x0d, 0xe7, 0xc6, 0x7d, 0xa8, 0xcf, 0x6a, 0xa3, 0xc8, 0x85, 0xca, 0x43, + 0x26, 0xb6, 0xed, 0xb3, 0xc9, 0xcd, 0xa1, 0x2a, 0x94, 0x26, 0x53, 0x47, 0x32, 0xdf, 0x19, 0x93, + 0x70, 0x24, 0xc9, 0xe6, 0x0c, 0x59, 0x07, 0x2e, 0xfc, 0x43, 0x66, 0x51, 0x01, 0xe6, 0x9e, 0xdc, + 0x72, 0x73, 0xea, 0x77, 0xdd, 0x75, 0xb4, 0xc3, 0xe6, 0xfd, 0x17, 0xaf, 0x1b, 0xce, 0xcb, 0xd7, + 0x0d, 0xe7, 0x8f, 0xd7, 0x0d, 0xe7, 0xc7, 0x37, 0x8d, 0xdc, 0xcb, 0x37, 0x8d, 0xdc, 0x6f, 0x6f, + 0x1a, 0xb9, 0x67, 0xb7, 0x4e, 0x54, 0x92, 0x4c, 0xec, 0xcd, 0xb7, 0x1e, 0xe6, 0xe3, 0x93, 0xff, + 0x01, 0x54, 0x61, 0x75, 0x0b, 0xea, 0xf0, 0x6e, 0xff, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xe9, 0x16, + 0x38, 0x3b, 0x31, 0x0c, 0x00, 0x00, } func (m *InboundParams) Marshal() (dAtA []byte, err error) { @@ -918,6 +1054,11 @@ func (m *Status) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.CreatedTimestamp != 0 { + i = encodeVarintCrossChainTx(dAtA, i, uint64(m.CreatedTimestamp)) + i-- + dAtA[i] = 0x28 + } if m.IsAbortRefunded { i-- if m.IsAbortRefunded { @@ -948,6 +1089,70 @@ func (m *Status) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *RevertOptions) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RevertOptions) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RevertOptions) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.RevertGasLimit.Size() + i -= size + if _, err := m.RevertGasLimit.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintCrossChainTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if len(m.RevertMessage) > 0 { + i -= len(m.RevertMessage) + copy(dAtA[i:], m.RevertMessage) + i = encodeVarintCrossChainTx(dAtA, i, uint64(len(m.RevertMessage))) + i-- + dAtA[i] = 0x22 + } + if len(m.AbortAddress) > 0 { + i -= len(m.AbortAddress) + copy(dAtA[i:], m.AbortAddress) + i = encodeVarintCrossChainTx(dAtA, i, uint64(len(m.AbortAddress))) + i-- + dAtA[i] = 0x1a + } + if m.CallOnRevert { + i-- + if m.CallOnRevert { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if len(m.RevertAddress) > 0 { + i -= len(m.RevertAddress) + copy(dAtA[i:], m.RevertAddress) + i = encodeVarintCrossChainTx(dAtA, i, uint64(len(m.RevertAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *CrossChainTx) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -968,6 +1173,21 @@ func (m *CrossChainTx) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size, err := m.RevertOptions.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintCrossChainTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x62 + if m.ProtocolContractVersion != 0 { + i = encodeVarintCrossChainTx(dAtA, i, uint64(m.ProtocolContractVersion)) + i-- + dAtA[i] = 0x58 + } if len(m.OutboundParams) > 0 { for iNdEx := len(m.OutboundParams) - 1; iNdEx >= 0; iNdEx-- { { @@ -1188,6 +1408,35 @@ func (m *Status) Size() (n int) { if m.IsAbortRefunded { n += 2 } + if m.CreatedTimestamp != 0 { + n += 1 + sovCrossChainTx(uint64(m.CreatedTimestamp)) + } + return n +} + +func (m *RevertOptions) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RevertAddress) + if l > 0 { + n += 1 + l + sovCrossChainTx(uint64(l)) + } + if m.CallOnRevert { + n += 2 + } + l = len(m.AbortAddress) + if l > 0 { + n += 1 + l + sovCrossChainTx(uint64(l)) + } + l = len(m.RevertMessage) + if l > 0 { + n += 1 + l + sovCrossChainTx(uint64(l)) + } + l = m.RevertGasLimit.Size() + n += 1 + l + sovCrossChainTx(uint64(l)) return n } @@ -1225,6 +1474,11 @@ func (m *CrossChainTx) Size() (n int) { n += 1 + l + sovCrossChainTx(uint64(l)) } } + if m.ProtocolContractVersion != 0 { + n += 1 + sovCrossChainTx(uint64(m.ProtocolContractVersion)) + } + l = m.RevertOptions.Size() + n += 1 + l + sovCrossChainTx(uint64(l)) return n } @@ -2238,6 +2492,227 @@ func (m *Status) Unmarshal(dAtA []byte) error { } } m.IsAbortRefunded = bool(v != 0) + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CreatedTimestamp", wireType) + } + m.CreatedTimestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.CreatedTimestamp |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipCrossChainTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthCrossChainTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RevertOptions) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RevertOptions: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RevertOptions: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RevertAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCrossChainTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCrossChainTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RevertAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CallOnRevert", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.CallOnRevert = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AbortAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCrossChainTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCrossChainTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AbortAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RevertMessage", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCrossChainTx + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCrossChainTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RevertMessage = append(m.RevertMessage[:0], dAtA[iNdEx:postIndex]...) + if m.RevertMessage == nil { + m.RevertMessage = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RevertGasLimit", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCrossChainTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCrossChainTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.RevertGasLimit.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCrossChainTx(dAtA[iNdEx:]) @@ -2524,6 +2999,58 @@ func (m *CrossChainTx) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolContractVersion", wireType) + } + m.ProtocolContractVersion = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ProtocolContractVersion |= ProtocolContractVersion(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RevertOptions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCrossChainTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthCrossChainTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.RevertOptions.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCrossChainTx(dAtA[iNdEx:]) diff --git a/x/crosschain/types/events.pb.go b/x/crosschain/types/events.pb.go index ff1dd01b62..05751c4336 100644 --- a/x/crosschain/types/events.pb.go +++ b/x/crosschain/types/events.pb.go @@ -619,6 +619,134 @@ func (m *EventERC20Whitelist) GetZrc20Address() string { return "" } +type EventERC20CustodyFundsMigration struct { + NewCustodyAddress string `protobuf:"bytes,1,opt,name=new_custody_address,json=newCustodyAddress,proto3" json:"new_custody_address,omitempty"` + Erc20Address string `protobuf:"bytes,2,opt,name=erc20_address,json=erc20Address,proto3" json:"erc20_address,omitempty"` + Amount string `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + CctxIndex string `protobuf:"bytes,4,opt,name=cctx_index,json=cctxIndex,proto3" json:"cctx_index,omitempty"` +} + +func (m *EventERC20CustodyFundsMigration) Reset() { *m = EventERC20CustodyFundsMigration{} } +func (m *EventERC20CustodyFundsMigration) String() string { return proto.CompactTextString(m) } +func (*EventERC20CustodyFundsMigration) ProtoMessage() {} +func (*EventERC20CustodyFundsMigration) Descriptor() ([]byte, []int) { + return fileDescriptor_dd08b628129fa2e1, []int{7} +} +func (m *EventERC20CustodyFundsMigration) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventERC20CustodyFundsMigration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventERC20CustodyFundsMigration.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventERC20CustodyFundsMigration) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventERC20CustodyFundsMigration.Merge(m, src) +} +func (m *EventERC20CustodyFundsMigration) XXX_Size() int { + return m.Size() +} +func (m *EventERC20CustodyFundsMigration) XXX_DiscardUnknown() { + xxx_messageInfo_EventERC20CustodyFundsMigration.DiscardUnknown(m) +} + +var xxx_messageInfo_EventERC20CustodyFundsMigration proto.InternalMessageInfo + +func (m *EventERC20CustodyFundsMigration) GetNewCustodyAddress() string { + if m != nil { + return m.NewCustodyAddress + } + return "" +} + +func (m *EventERC20CustodyFundsMigration) GetErc20Address() string { + if m != nil { + return m.Erc20Address + } + return "" +} + +func (m *EventERC20CustodyFundsMigration) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *EventERC20CustodyFundsMigration) GetCctxIndex() string { + if m != nil { + return m.CctxIndex + } + return "" +} + +type EventERC20CustodyPausing struct { + ChainId int64 `protobuf:"varint,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + Pause bool `protobuf:"varint,2,opt,name=pause,proto3" json:"pause,omitempty"` + CctxIndex string `protobuf:"bytes,3,opt,name=cctx_index,json=cctxIndex,proto3" json:"cctx_index,omitempty"` +} + +func (m *EventERC20CustodyPausing) Reset() { *m = EventERC20CustodyPausing{} } +func (m *EventERC20CustodyPausing) String() string { return proto.CompactTextString(m) } +func (*EventERC20CustodyPausing) ProtoMessage() {} +func (*EventERC20CustodyPausing) Descriptor() ([]byte, []int) { + return fileDescriptor_dd08b628129fa2e1, []int{8} +} +func (m *EventERC20CustodyPausing) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventERC20CustodyPausing) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventERC20CustodyPausing.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventERC20CustodyPausing) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventERC20CustodyPausing.Merge(m, src) +} +func (m *EventERC20CustodyPausing) XXX_Size() int { + return m.Size() +} +func (m *EventERC20CustodyPausing) XXX_DiscardUnknown() { + xxx_messageInfo_EventERC20CustodyPausing.DiscardUnknown(m) +} + +var xxx_messageInfo_EventERC20CustodyPausing proto.InternalMessageInfo + +func (m *EventERC20CustodyPausing) GetChainId() int64 { + if m != nil { + return m.ChainId + } + return 0 +} + +func (m *EventERC20CustodyPausing) GetPause() bool { + if m != nil { + return m.Pause + } + return false +} + +func (m *EventERC20CustodyPausing) GetCctxIndex() string { + if m != nil { + return m.CctxIndex + } + return "" +} + func init() { proto.RegisterType((*EventInboundFinalized)(nil), "zetachain.zetacore.crosschain.EventInboundFinalized") proto.RegisterType((*EventZrcWithdrawCreated)(nil), "zetachain.zetacore.crosschain.EventZrcWithdrawCreated") @@ -627,6 +755,8 @@ func init() { proto.RegisterType((*EventOutboundSuccess)(nil), "zetachain.zetacore.crosschain.EventOutboundSuccess") proto.RegisterType((*EventCCTXGasPriceIncreased)(nil), "zetachain.zetacore.crosschain.EventCCTXGasPriceIncreased") proto.RegisterType((*EventERC20Whitelist)(nil), "zetachain.zetacore.crosschain.EventERC20Whitelist") + proto.RegisterType((*EventERC20CustodyFundsMigration)(nil), "zetachain.zetacore.crosschain.EventERC20CustodyFundsMigration") + proto.RegisterType((*EventERC20CustodyPausing)(nil), "zetachain.zetacore.crosschain.EventERC20CustodyPausing") } func init() { @@ -634,50 +764,57 @@ func init() { } var fileDescriptor_dd08b628129fa2e1 = []byte{ - // 686 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x95, 0xd1, 0x4e, 0x13, 0x4f, - 0x14, 0xc6, 0x59, 0x68, 0x4b, 0x3b, 0xb4, 0xfd, 0xff, 0x33, 0x56, 0x5d, 0x49, 0x68, 0xa0, 0xc6, - 0x68, 0x8c, 0x16, 0xc4, 0x27, 0x90, 0x06, 0x84, 0x18, 0x83, 0x01, 0x0c, 0x86, 0x9b, 0xc9, 0x74, - 0xe7, 0xb8, 0x3b, 0x71, 0xbb, 0xd3, 0xcc, 0xcc, 0xd2, 0xc2, 0x53, 0x18, 0x1f, 0xc5, 0xc4, 0x3b, - 0x1f, 0xc0, 0x4b, 0x2e, 0xbd, 0x34, 0xf4, 0x45, 0xcc, 0xcc, 0xec, 0x16, 0xda, 0x1a, 0xbd, 0x30, - 0x9a, 0x78, 0xb7, 0xe7, 0x3b, 0x67, 0xa6, 0xbf, 0xf9, 0xbe, 0xed, 0x0e, 0x7a, 0x78, 0x0e, 0x9a, - 0x06, 0x11, 0xe5, 0xc9, 0xba, 0x7d, 0x12, 0x12, 0xd6, 0x03, 0x29, 0x94, 0x72, 0x1a, 0x9c, 0x42, - 0xa2, 0x55, 0xbb, 0x2f, 0x85, 0x16, 0x78, 0x65, 0x3c, 0xdb, 0xce, 0x67, 0xdb, 0x57, 0xb3, 0xcb, - 0x8d, 0x50, 0x84, 0xc2, 0x4e, 0xae, 0x9b, 0x27, 0xb7, 0xa8, 0x35, 0x5a, 0x40, 0x37, 0xb7, 0xcd, - 0x2e, 0x7b, 0x49, 0x57, 0xa4, 0x09, 0xdb, 0xe1, 0x09, 0x8d, 0xf9, 0x39, 0x30, 0xbc, 0x8a, 0xaa, - 0x3d, 0x15, 0x12, 0x7d, 0xd6, 0x07, 0x92, 0xca, 0xd8, 0xf7, 0x56, 0xbd, 0x07, 0x95, 0x03, 0xd4, - 0x53, 0xe1, 0xd1, 0x59, 0x1f, 0x5e, 0xcb, 0x18, 0xaf, 0x20, 0x14, 0x04, 0x7a, 0x48, 0x78, 0xc2, - 0x60, 0xe8, 0xcf, 0xdb, 0x7e, 0xc5, 0x28, 0x7b, 0x46, 0xc0, 0xb7, 0x50, 0x49, 0x41, 0xc2, 0x40, - 0xfa, 0x0b, 0xb6, 0x95, 0x55, 0xf8, 0x0e, 0x2a, 0xeb, 0x21, 0x11, 0x32, 0xe4, 0x89, 0x5f, 0xb0, - 0x9d, 0x45, 0x3d, 0xdc, 0x37, 0x25, 0x6e, 0xa0, 0x22, 0x55, 0x0a, 0xb4, 0x5f, 0xb4, 0xba, 0x2b, - 0xf0, 0x1a, 0xaa, 0x72, 0x47, 0x47, 0x22, 0xaa, 0x22, 0xbf, 0x64, 0x9b, 0x4b, 0x99, 0xb6, 0x4b, - 0x55, 0x84, 0x37, 0x50, 0x23, 0x1f, 0xe9, 0xc6, 0x22, 0x78, 0x47, 0x22, 0xe0, 0x61, 0xa4, 0xfd, - 0x45, 0x3b, 0x8a, 0xb3, 0xde, 0x96, 0x69, 0xed, 0xda, 0x0e, 0x5e, 0x46, 0x65, 0x09, 0x01, 0xf0, - 0x53, 0x90, 0x7e, 0xd9, 0x4e, 0x8d, 0x6b, 0x7c, 0x0f, 0xd5, 0xf3, 0x67, 0x62, 0xcd, 0xf3, 0x2b, - 0x76, 0xa2, 0x96, 0xab, 0x1d, 0x23, 0x9a, 0x03, 0xd2, 0x9e, 0x48, 0x13, 0xed, 0x23, 0x77, 0x40, - 0x57, 0xe1, 0xfb, 0xe8, 0x3f, 0x09, 0x31, 0x3d, 0x03, 0x46, 0x7a, 0xa0, 0x14, 0x0d, 0xc1, 0x5f, - 0xb2, 0x03, 0xf5, 0x4c, 0x7e, 0xe9, 0x54, 0x63, 0x60, 0x02, 0x03, 0xa2, 0x34, 0xd5, 0xa9, 0xf2, - 0xab, 0xce, 0xc0, 0x04, 0x06, 0x87, 0x56, 0x30, 0x18, 0xae, 0x35, 0xde, 0xa6, 0xe6, 0x30, 0x9c, - 0x9a, 0xef, 0xb2, 0x86, 0xaa, 0xce, 0xd9, 0x8c, 0xb5, 0xee, 0xec, 0x71, 0x9a, 0x25, 0x6d, 0x7d, - 0x9c, 0x47, 0xb7, 0x6d, 0xca, 0x27, 0x32, 0x38, 0xe6, 0x3a, 0x62, 0x92, 0x0e, 0x3a, 0x12, 0xa8, - 0xfe, 0x93, 0x39, 0x4f, 0x73, 0x15, 0x66, 0xb8, 0x66, 0x92, 0x2d, 0xce, 0x26, 0x7b, 0x3d, 0xa7, - 0xd2, 0x2f, 0x73, 0x5a, 0xfc, 0x79, 0x4e, 0xe5, 0x89, 0x9c, 0x26, 0xed, 0xaf, 0x4c, 0xd9, 0xdf, - 0xfa, 0xe4, 0x21, 0xdf, 0x99, 0x06, 0x9a, 0xfe, 0x4d, 0xd7, 0x26, 0x2c, 0x29, 0xcc, 0x5a, 0x32, - 0xc9, 0x5d, 0x9c, 0xe6, 0xfe, 0xec, 0xa1, 0x86, 0xe5, 0xde, 0x4f, 0xb5, 0xfb, 0x4f, 0x53, 0x1e, - 0xa7, 0x12, 0x7e, 0x9f, 0x79, 0x05, 0x21, 0x11, 0xb3, 0xfc, 0x87, 0x1d, 0x77, 0x45, 0xc4, 0x2c, - 0x7b, 0x5f, 0x27, 0xb9, 0x0a, 0x3f, 0x78, 0x9d, 0x4f, 0x69, 0x9c, 0x02, 0xc9, 0xd2, 0x61, 0x19, - 0x7a, 0xcd, 0xaa, 0x07, 0x99, 0x38, 0x8b, 0x7f, 0x98, 0x06, 0x01, 0x28, 0xf5, 0x8f, 0xe0, 0x7f, - 0xf0, 0xd0, 0xb2, 0xc5, 0xef, 0x74, 0x8e, 0xde, 0x3c, 0xa7, 0xea, 0x95, 0xe4, 0x01, 0xec, 0x25, - 0x81, 0x04, 0xaa, 0x80, 0x4d, 0x21, 0x7a, 0xd3, 0x88, 0x8f, 0x10, 0x0e, 0xa9, 0x22, 0x7d, 0xb3, - 0x88, 0xf0, 0x6c, 0x55, 0x76, 0x92, 0xff, 0xc3, 0xa9, 0xdd, 0xcc, 0x87, 0x86, 0x32, 0xc6, 0x35, - 0x17, 0x09, 0x8d, 0xc9, 0x5b, 0x80, 0xfc, 0x54, 0xf5, 0x2b, 0x79, 0x07, 0x40, 0xb5, 0x62, 0x74, - 0xc3, 0x32, 0x6d, 0x1f, 0x74, 0x36, 0x37, 0x8e, 0x23, 0xae, 0x21, 0xe6, 0x4a, 0x9b, 0xaf, 0xe6, - 0x20, 0x2f, 0xc8, 0x0c, 0x16, 0x1e, 0xf7, 0x3a, 0x63, 0xbe, 0xbb, 0xa8, 0x76, 0x2e, 0x83, 0xcd, - 0x0d, 0x42, 0x19, 0x93, 0xa0, 0x54, 0x86, 0x56, 0xb5, 0xe2, 0x33, 0xa7, 0x6d, 0xbd, 0xf8, 0x72, - 0xd9, 0xf4, 0x2e, 0x2e, 0x9b, 0xde, 0xb7, 0xcb, 0xa6, 0xf7, 0x7e, 0xd4, 0x9c, 0xbb, 0x18, 0x35, - 0xe7, 0xbe, 0x8e, 0x9a, 0x73, 0x27, 0x4f, 0x42, 0xae, 0xa3, 0xb4, 0xdb, 0x0e, 0x44, 0xcf, 0xde, - 0x67, 0x8f, 0xa7, 0xae, 0xb6, 0xe1, 0xf5, 0xcb, 0xcd, 0x04, 0xad, 0xba, 0x25, 0x7b, 0x4f, 0x3d, - 0xfd, 0x1e, 0x00, 0x00, 0xff, 0xff, 0xc4, 0x44, 0xd3, 0x7e, 0x0a, 0x07, 0x00, 0x00, + // 789 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x95, 0xcd, 0x6e, 0xeb, 0x44, + 0x14, 0xc7, 0xeb, 0x9b, 0xef, 0xb9, 0x49, 0x00, 0xdf, 0x00, 0xa6, 0x52, 0xc3, 0xbd, 0x41, 0x08, + 0x84, 0x20, 0x2d, 0xe5, 0x09, 0x68, 0xd4, 0xd2, 0x08, 0x55, 0xad, 0xd2, 0xa2, 0xa2, 0x6e, 0x46, + 0x13, 0xcf, 0xc1, 0x1e, 0x70, 0xc6, 0xd1, 0xcc, 0xb8, 0x49, 0xfa, 0x14, 0x88, 0xf7, 0x60, 0x83, + 0xc4, 0x8e, 0x07, 0x60, 0xd9, 0x25, 0x4b, 0xd4, 0xbc, 0x08, 0x9a, 0x0f, 0xa7, 0x89, 0x53, 0xc1, + 0x02, 0x81, 0x74, 0x77, 0x3e, 0xff, 0x73, 0x3c, 0xe7, 0x37, 0xff, 0xe3, 0xf1, 0xa0, 0x4f, 0xee, + 0x40, 0x91, 0x30, 0x26, 0x8c, 0xef, 0x9b, 0xa7, 0x54, 0xc0, 0x7e, 0x28, 0x52, 0x29, 0xad, 0x06, + 0xb7, 0xc0, 0x95, 0xec, 0x4f, 0x45, 0xaa, 0x52, 0x7f, 0x6f, 0x55, 0xdb, 0xcf, 0x6b, 0xfb, 0x8f, + 0xb5, 0xbb, 0x9d, 0x28, 0x8d, 0x52, 0x53, 0xb9, 0xaf, 0x9f, 0xec, 0x4b, 0xbd, 0x65, 0x09, 0xbd, + 0x7d, 0xac, 0x57, 0x19, 0xf2, 0x71, 0x9a, 0x71, 0x7a, 0xc2, 0x38, 0x49, 0xd8, 0x1d, 0x50, 0xff, + 0x25, 0x6a, 0x4e, 0x64, 0x84, 0xd5, 0x62, 0x0a, 0x38, 0x13, 0x49, 0xe0, 0xbd, 0xf4, 0x3e, 0x6e, + 0x8c, 0xd0, 0x44, 0x46, 0x57, 0x8b, 0x29, 0x7c, 0x23, 0x12, 0x7f, 0x0f, 0xa1, 0x30, 0x54, 0x73, + 0xcc, 0x38, 0x85, 0x79, 0xf0, 0xcc, 0xe4, 0x1b, 0x5a, 0x19, 0x6a, 0xc1, 0x7f, 0x07, 0x55, 0x25, + 0x70, 0x0a, 0x22, 0x28, 0x99, 0x94, 0x8b, 0xfc, 0xf7, 0x50, 0x5d, 0xcd, 0x71, 0x2a, 0x22, 0xc6, + 0x83, 0xb2, 0xc9, 0xd4, 0xd4, 0xfc, 0x5c, 0x87, 0x7e, 0x07, 0x55, 0x88, 0x94, 0xa0, 0x82, 0x8a, + 0xd1, 0x6d, 0xe0, 0xbf, 0x42, 0x4d, 0x66, 0xe9, 0x70, 0x4c, 0x64, 0x1c, 0x54, 0x4d, 0xf2, 0xb9, + 0xd3, 0x4e, 0x89, 0x8c, 0xfd, 0x03, 0xd4, 0xc9, 0x4b, 0xc6, 0x49, 0x1a, 0xfe, 0x80, 0x63, 0x60, + 0x51, 0xac, 0x82, 0x9a, 0x29, 0xf5, 0x5d, 0xee, 0x48, 0xa7, 0x4e, 0x4d, 0xc6, 0xdf, 0x45, 0x75, + 0x01, 0x21, 0xb0, 0x5b, 0x10, 0x41, 0xdd, 0x54, 0xad, 0x62, 0xff, 0x43, 0xd4, 0xce, 0x9f, 0xb1, + 0x31, 0x2f, 0x68, 0x98, 0x8a, 0x56, 0xae, 0x0e, 0xb4, 0xa8, 0x37, 0x48, 0x26, 0x69, 0xc6, 0x55, + 0x80, 0xec, 0x06, 0x6d, 0xe4, 0x7f, 0x84, 0xde, 0x10, 0x90, 0x90, 0x05, 0x50, 0x3c, 0x01, 0x29, + 0x49, 0x04, 0xc1, 0x73, 0x53, 0xd0, 0x76, 0xf2, 0x99, 0x55, 0xb5, 0x81, 0x1c, 0x66, 0x58, 0x2a, + 0xa2, 0x32, 0x19, 0x34, 0xad, 0x81, 0x1c, 0x66, 0x97, 0x46, 0xd0, 0x18, 0x36, 0xb5, 0x5a, 0xa6, + 0x65, 0x31, 0xac, 0x9a, 0xaf, 0xf2, 0x0a, 0x35, 0xad, 0xb3, 0x8e, 0xb5, 0x6d, 0xed, 0xb1, 0x9a, + 0x21, 0xed, 0xfd, 0xf2, 0x0c, 0xbd, 0x6b, 0xa6, 0x7c, 0x23, 0xc2, 0x6b, 0xa6, 0x62, 0x2a, 0xc8, + 0x6c, 0x20, 0x80, 0xa8, 0xff, 0x72, 0xce, 0x45, 0xae, 0xf2, 0x16, 0xd7, 0xd6, 0x64, 0x2b, 0xdb, + 0x93, 0x5d, 0x9f, 0x53, 0xf5, 0x1f, 0xe7, 0x54, 0xfb, 0xfb, 0x39, 0xd5, 0x37, 0xe6, 0xb4, 0x69, + 0x7f, 0xa3, 0x60, 0x7f, 0xef, 0x57, 0x0f, 0x05, 0xd6, 0x34, 0x50, 0xe4, 0xff, 0x74, 0x6d, 0xc3, + 0x92, 0xf2, 0xb6, 0x25, 0x9b, 0xdc, 0x95, 0x22, 0xf7, 0x6f, 0x1e, 0xea, 0x18, 0xee, 0xf3, 0x4c, + 0xd9, 0x33, 0x4d, 0x58, 0x92, 0x09, 0xf8, 0xf7, 0xcc, 0x7b, 0x08, 0xa5, 0x09, 0xcd, 0x1b, 0x5b, + 0xee, 0x46, 0x9a, 0x50, 0xf7, 0xbd, 0x6e, 0x72, 0x95, 0x9f, 0xf8, 0x9c, 0x6f, 0x49, 0x92, 0x01, + 0x76, 0xd3, 0xa1, 0x0e, 0xbd, 0x65, 0xd4, 0x91, 0x13, 0xb7, 0xf1, 0x2f, 0xb3, 0x30, 0x04, 0x29, + 0x5f, 0x13, 0xfc, 0x9f, 0x3c, 0xb4, 0x6b, 0xf0, 0x07, 0x83, 0xab, 0x6f, 0xbf, 0x22, 0xf2, 0x42, + 0xb0, 0x10, 0x86, 0x3c, 0x14, 0x40, 0x24, 0xd0, 0x02, 0xa2, 0x57, 0x44, 0xfc, 0x14, 0xf9, 0x11, + 0x91, 0x78, 0xaa, 0x5f, 0xc2, 0xcc, 0xbd, 0xe5, 0x76, 0xf2, 0x66, 0x54, 0x58, 0x4d, 0xff, 0x68, + 0x08, 0xa5, 0x4c, 0xb1, 0x94, 0x93, 0x04, 0x7f, 0x07, 0x90, 0xef, 0xaa, 0xfd, 0x28, 0x9f, 0x00, + 0xc8, 0x5e, 0x82, 0x5e, 0x18, 0xa6, 0xe3, 0xd1, 0xe0, 0xf0, 0xe0, 0x3a, 0x66, 0x0a, 0x12, 0x26, + 0x95, 0xfe, 0x6b, 0xce, 0xf2, 0x00, 0x6f, 0x61, 0xf9, 0xab, 0xdc, 0x60, 0xc5, 0xf7, 0x01, 0x6a, + 0xdd, 0x89, 0xf0, 0xf0, 0x00, 0x13, 0x4a, 0x05, 0x48, 0xe9, 0xd0, 0x9a, 0x46, 0xfc, 0xd2, 0x6a, + 0xbd, 0x9f, 0x3d, 0xf4, 0xfe, 0x63, 0xbb, 0x41, 0x26, 0x55, 0x4a, 0x17, 0x27, 0x19, 0xa7, 0xf2, + 0x8c, 0x45, 0x82, 0x68, 0x2e, 0xbf, 0x8f, 0x5e, 0x68, 0xb3, 0x43, 0x9b, 0x5c, 0x2d, 0x67, 0x3b, + 0xbf, 0xc5, 0x61, 0xe6, 0x5e, 0x73, 0x6b, 0xea, 0xc6, 0xf0, 0x54, 0x63, 0x58, 0x6b, 0xbc, 0x76, + 0xd0, 0x4b, 0xc5, 0x83, 0xbe, 0xb6, 0xbb, 0x72, 0xc1, 0xf4, 0xde, 0xf7, 0xee, 0x9c, 0xaf, 0xe3, + 0x5e, 0x90, 0x4c, 0x32, 0x1e, 0xe9, 0xcb, 0xca, 0xfc, 0x59, 0x30, 0xa3, 0x06, 0xae, 0x34, 0xaa, + 0x99, 0x78, 0x48, 0xf5, 0x65, 0x35, 0x25, 0x99, 0x1b, 0x4f, 0x7d, 0x64, 0x83, 0x42, 0xaf, 0x52, + 0xa1, 0xd7, 0xd1, 0xd7, 0xbf, 0x3f, 0x74, 0xbd, 0xfb, 0x87, 0xae, 0xf7, 0xe7, 0x43, 0xd7, 0xfb, + 0x71, 0xd9, 0xdd, 0xb9, 0x5f, 0x76, 0x77, 0xfe, 0x58, 0x76, 0x77, 0x6e, 0x3e, 0x8f, 0x98, 0x8a, + 0xb3, 0x71, 0x3f, 0x4c, 0x27, 0xe6, 0xae, 0xff, 0xac, 0x70, 0xed, 0xcf, 0xd7, 0x2f, 0x7e, 0x7d, + 0x08, 0xe4, 0xb8, 0x6a, 0xee, 0xf0, 0x2f, 0xfe, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x0c, 0xc2, 0xaf, + 0xdb, 0x26, 0x08, 0x00, 0x00, } func (m *EventInboundFinalized) Marshal() (dAtA []byte, err error) { @@ -1142,6 +1279,102 @@ func (m *EventERC20Whitelist) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *EventERC20CustodyFundsMigration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventERC20CustodyFundsMigration) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventERC20CustodyFundsMigration) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CctxIndex) > 0 { + i -= len(m.CctxIndex) + copy(dAtA[i:], m.CctxIndex) + i = encodeVarintEvents(dAtA, i, uint64(len(m.CctxIndex))) + i-- + dAtA[i] = 0x22 + } + if len(m.Amount) > 0 { + i -= len(m.Amount) + copy(dAtA[i:], m.Amount) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Amount))) + i-- + dAtA[i] = 0x1a + } + if len(m.Erc20Address) > 0 { + i -= len(m.Erc20Address) + copy(dAtA[i:], m.Erc20Address) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Erc20Address))) + i-- + dAtA[i] = 0x12 + } + if len(m.NewCustodyAddress) > 0 { + i -= len(m.NewCustodyAddress) + copy(dAtA[i:], m.NewCustodyAddress) + i = encodeVarintEvents(dAtA, i, uint64(len(m.NewCustodyAddress))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventERC20CustodyPausing) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventERC20CustodyPausing) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventERC20CustodyPausing) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CctxIndex) > 0 { + i -= len(m.CctxIndex) + copy(dAtA[i:], m.CctxIndex) + i = encodeVarintEvents(dAtA, i, uint64(len(m.CctxIndex))) + i-- + dAtA[i] = 0x1a + } + if m.Pause { + i-- + if m.Pause { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.ChainId != 0 { + i = encodeVarintEvents(dAtA, i, uint64(m.ChainId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { offset -= sovEvents(v) base := offset @@ -1388,6 +1621,50 @@ func (m *EventERC20Whitelist) Size() (n int) { return n } +func (m *EventERC20CustodyFundsMigration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.NewCustodyAddress) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Erc20Address) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Amount) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.CctxIndex) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + +func (m *EventERC20CustodyPausing) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ChainId != 0 { + n += 1 + sovEvents(uint64(m.ChainId)) + } + if m.Pause { + n += 2 + } + l = len(m.CctxIndex) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + func sovEvents(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3120,6 +3397,305 @@ func (m *EventERC20Whitelist) Unmarshal(dAtA []byte) error { } return nil } +func (m *EventERC20CustodyFundsMigration) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventERC20CustodyFundsMigration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventERC20CustodyFundsMigration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewCustodyAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewCustodyAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Erc20Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Erc20Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Amount = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CctxIndex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CctxIndex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventERC20CustodyPausing) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventERC20CustodyPausing: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventERC20CustodyPausing: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + m.ChainId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ChainId |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Pause", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Pause = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CctxIndex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CctxIndex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipEvents(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 369093e8e6..9d053f9b05 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -145,7 +145,18 @@ type FungibleKeeper interface { data []byte, coinType coin.CoinType, asset string, + protocolContractVersion ProtocolContractVersion, ) (*evmtypes.MsgEthereumTxResponse, bool, error) + ProcessV2RevertDeposit( + ctx sdk.Context, + amount *big.Int, + chainID int64, + coinType coin.CoinType, + asset string, + revertAddress ethcommon.Address, + callOnRevert bool, + revertMessage []byte, + ) error CallUniswapV2RouterSwapExactTokensForTokens( ctx sdk.Context, sender ethcommon.Address, diff --git a/x/crosschain/types/keys.go b/x/crosschain/types/keys.go index 8496ccf608..d9fea4a77d 100644 --- a/x/crosschain/types/keys.go +++ b/x/crosschain/types/keys.go @@ -25,12 +25,6 @@ const ( ProtocolFee = 2000000000000000000 - // TssMigrationGasMultiplierEVM is multiplied to the median gas price to get the gas price for the tss migration . - // This is done to avoid the tss migration tx getting stuck in the mempool - TssMigrationGasMultiplierEVM = "2.5" - // TSSMigrationBufferAmountEVM is the buffer amount added to the gas price for the tss migration transaction - TSSMigrationBufferAmountEVM = "2100000000" - // CCTXIndexLength is the length of a crosschain transaction index CCTXIndexLength = 66 ) diff --git a/x/crosschain/types/message_migrate_erc20_custody_funds.go b/x/crosschain/types/message_migrate_erc20_custody_funds.go new file mode 100644 index 0000000000..da69db1fd8 --- /dev/null +++ b/x/crosschain/types/message_migrate_erc20_custody_funds.go @@ -0,0 +1,68 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + ethcommon "github.com/ethereum/go-ethereum/common" +) + +const TypeMsgMigrateERC20CustodyFunds = "MigrateERC20CustodyFunds" + +var _ sdk.Msg = &MsgMigrateERC20CustodyFunds{} + +func NewMsgMigrateERC20CustodyFunds( + creator string, + chainID int64, + newCustodyAddress string, + erc20Address string, + amount sdkmath.Uint, +) *MsgMigrateERC20CustodyFunds { + return &MsgMigrateERC20CustodyFunds{ + Creator: creator, + ChainId: chainID, + NewCustodyAddress: newCustodyAddress, + Erc20Address: erc20Address, + Amount: amount, + } +} + +func (msg *MsgMigrateERC20CustodyFunds) Route() string { + return RouterKey +} + +func (msg *MsgMigrateERC20CustodyFunds) Type() string { + return TypeMsgMigrateERC20CustodyFunds +} + +func (msg *MsgMigrateERC20CustodyFunds) GetSigners() []sdk.AccAddress { + creator, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + panic(err) + } + return []sdk.AccAddress{creator} +} + +func (msg *MsgMigrateERC20CustodyFunds) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) +} + +func (msg *MsgMigrateERC20CustodyFunds) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + } + + switch { + case !ethcommon.IsHexAddress(msg.NewCustodyAddress): + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid new custody address") + case !ethcommon.IsHexAddress(msg.Erc20Address): + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid erc20 address") + case msg.Amount.IsZero(): + return errorsmod.Wrapf(sdkerrors.ErrInvalidRequest, "amount cannot be zero") + } + + return nil +} diff --git a/x/crosschain/types/message_migrate_erc20_custody_funds_test.go b/x/crosschain/types/message_migrate_erc20_custody_funds_test.go new file mode 100644 index 0000000000..9a86b6d141 --- /dev/null +++ b/x/crosschain/types/message_migrate_erc20_custody_funds_test.go @@ -0,0 +1,169 @@ +package types_test + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestNewMsgMigrateERC20CustodyFunds_ValidateBasic(t *testing.T) { + keeper.SetConfig(false) + tests := []struct { + name string + msg *types.MsgMigrateERC20CustodyFunds + error bool + }{ + { + name: "invalid creator", + msg: types.NewMsgMigrateERC20CustodyFunds( + "invalid address", + chains.DefaultChainsList()[0].ChainId, + sample.EthAddress().String(), + sample.EthAddress().String(), + sdkmath.NewUintFromString("100000"), + ), + error: true, + }, + { + name: "invalid amount", + msg: types.NewMsgMigrateERC20CustodyFunds( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + sample.EthAddress().String(), + sample.EthAddress().String(), + sdkmath.NewUintFromString("0"), + ), + error: true, + }, + { + name: "valid msg", + msg: types.NewMsgMigrateERC20CustodyFunds( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + sample.EthAddress().String(), + sample.EthAddress().String(), + sdkmath.NewUintFromString("100000"), + ), + }, + { + name: "invalid erc20 address", + msg: types.NewMsgMigrateERC20CustodyFunds( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + sample.EthAddress().String(), + "invalid address", + sdkmath.NewUintFromString("100000"), + ), + error: true, + }, + { + name: "invalid new custody address", + msg: types.NewMsgMigrateERC20CustodyFunds( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + "invalid address", + sample.EthAddress().String(), + sdkmath.NewUintFromString("100000"), + ), + error: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.error { + require.Error(t, err) + return + } else { + require.NoError(t, err) + } + }) + } +} + +func TestNewMsgMigrateERC20CustodyFunds_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgMigrateERC20CustodyFunds + panics bool + }{ + { + name: "valid signer", + msg: types.MsgMigrateERC20CustodyFunds{ + Creator: signer, + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgMigrateERC20CustodyFunds{ + Creator: "invalid_address", + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestNewMsgMigrateERC20CustodyFunds_Type(t *testing.T) { + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + } + require.Equal(t, types.TypeMsgMigrateERC20CustodyFunds, msg.Type()) +} + +func TestNewMsgMigrateERC20CustodyFunds_Route(t *testing.T) { + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestNewMsgMigrateERC20CustodyFunds_GetSignBytes(t *testing.T) { + msg := types.MsgMigrateERC20CustodyFunds{ + Creator: sample.AccAddress(), + ChainId: chains.DefaultChainsList()[0].ChainId, + NewCustodyAddress: sample.EthAddress().String(), + Erc20Address: sample.EthAddress().String(), + Amount: sdkmath.NewUintFromString("100000"), + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_update_erc20_custody_pause_status.go b/x/crosschain/types/message_update_erc20_custody_pause_status.go new file mode 100644 index 0000000000..fd45fd30db --- /dev/null +++ b/x/crosschain/types/message_update_erc20_custody_pause_status.go @@ -0,0 +1,53 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const TypeUpdateERC20CustodyPauseStatus = "UpdateERC20CustodyPauseStatus" + +var _ sdk.Msg = &MsgUpdateERC20CustodyPauseStatus{} + +func NewMsgUpdateERC20CustodyPauseStatus( + creator string, + chainID int64, + pause bool, +) *MsgUpdateERC20CustodyPauseStatus { + return &MsgUpdateERC20CustodyPauseStatus{ + Creator: creator, + ChainId: chainID, + Pause: pause, + } +} + +func (msg *MsgUpdateERC20CustodyPauseStatus) Route() string { + return RouterKey +} + +func (msg *MsgUpdateERC20CustodyPauseStatus) Type() string { + return TypeUpdateERC20CustodyPauseStatus +} + +func (msg *MsgUpdateERC20CustodyPauseStatus) GetSigners() []sdk.AccAddress { + creator, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + panic(err) + } + return []sdk.AccAddress{creator} +} + +func (msg *MsgUpdateERC20CustodyPauseStatus) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) +} + +func (msg *MsgUpdateERC20CustodyPauseStatus) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Creator) + if err != nil { + return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + } + + return nil +} diff --git a/x/crosschain/types/message_update_erc20_custody_pause_status_test.go b/x/crosschain/types/message_update_erc20_custody_pause_status_test.go new file mode 100644 index 0000000000..26842b4e0b --- /dev/null +++ b/x/crosschain/types/message_update_erc20_custody_pause_status_test.go @@ -0,0 +1,129 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +func TestNewMsgUpdateERC20CustodyPauseStatus_ValidateBasic(t *testing.T) { + keeper.SetConfig(false) + tests := []struct { + name string + msg *types.MsgUpdateERC20CustodyPauseStatus + error bool + }{ + { + name: "invalid creator", + msg: types.NewMsgUpdateERC20CustodyPauseStatus( + "invalid address", + chains.DefaultChainsList()[0].ChainId, + true, + ), + error: true, + }, + { + name: "valid msg", + msg: types.NewMsgUpdateERC20CustodyPauseStatus( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + true, + ), + }, + { + name: "valid msg with pause false", + msg: types.NewMsgUpdateERC20CustodyPauseStatus( + sample.AccAddress(), + chains.DefaultChainsList()[0].ChainId, + false, + ), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.msg.ValidateBasic() + if tt.error { + require.Error(t, err) + return + } else { + require.NoError(t, err) + } + }) + } +} + +func TestMsgUpdateERC20CustodyPauseStatus_GetSigners(t *testing.T) { + signer := sample.AccAddress() + tests := []struct { + name string + msg types.MsgUpdateERC20CustodyPauseStatus + panics bool + }{ + { + name: "valid signer", + msg: types.MsgUpdateERC20CustodyPauseStatus{ + Creator: signer, + ChainId: chains.DefaultChainsList()[0].ChainId, + Pause: true, + }, + panics: false, + }, + { + name: "invalid signer", + msg: types.MsgUpdateERC20CustodyPauseStatus{ + Creator: "invalid_address", + ChainId: chains.DefaultChainsList()[0].ChainId, + Pause: true, + }, + panics: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.panics { + signers := tt.msg.GetSigners() + require.Equal(t, []sdk.AccAddress{sdk.MustAccAddressFromBech32(signer)}, signers) + } else { + require.Panics(t, func() { + tt.msg.GetSigners() + }) + } + }) + } +} + +func TestMsgUpdateERC20CustodyPauseStatus_Type(t *testing.T) { + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chains.DefaultChainsList()[0].ChainId, + Pause: true, + } + require.Equal(t, types.TypeUpdateERC20CustodyPauseStatus, msg.Type()) +} + +func TestMsgUpdateERC20CustodyPauseStatus_Route(t *testing.T) { + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chains.DefaultChainsList()[0].ChainId, + Pause: true, + } + require.Equal(t, types.RouterKey, msg.Route()) +} + +func TestMsgUpdateERC20CustodyPauseStatus_GetSignBytes(t *testing.T) { + msg := types.MsgUpdateERC20CustodyPauseStatus{ + Creator: sample.AccAddress(), + ChainId: chains.DefaultChainsList()[0].ChainId, + Pause: true, + } + require.NotPanics(t, func() { + msg.GetSignBytes() + }) +} diff --git a/x/crosschain/types/message_update_rate_limiter_flags.go b/x/crosschain/types/message_update_rate_limiter_flags.go index 85d3ccf7c4..f082ad4f58 100644 --- a/x/crosschain/types/message_update_rate_limiter_flags.go +++ b/x/crosschain/types/message_update_rate_limiter_flags.go @@ -43,7 +43,7 @@ func (msg *MsgUpdateRateLimiterFlags) ValidateBasic() error { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if err := msg.RateLimiterFlags.Validate(); err != nil { - return errorsmod.Wrapf(ErrInvalidRateLimiterFlags, err.Error()) + return errorsmod.Wrap(ErrInvalidRateLimiterFlags, err.Error()) } return nil } diff --git a/x/crosschain/types/message_vote_inbound.go b/x/crosschain/types/message_vote_inbound.go index b2ee24ce2d..b5720e5e6f 100644 --- a/x/crosschain/types/message_vote_inbound.go +++ b/x/crosschain/types/message_vote_inbound.go @@ -6,6 +6,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/crypto" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" "github.com/zeta-chain/zetacore/pkg/authz" "github.com/zeta-chain/zetacore/pkg/coin" @@ -17,6 +19,25 @@ import ( // https://github.com/zeta-chain/node/issues/862 const MaxMessageLength = 10240 +// InboundVoteOption is a function that sets some option on the inbound vote message +type InboundVoteOption func(*MsgVoteInbound) + +// WithZEVMRevertOptions sets the revert options for the inbound vote message (ZEVM format) +// the function convert the type from abigen to type defined in proto +func WithZEVMRevertOptions(revertOptions gatewayzevm.RevertOptions) InboundVoteOption { + return func(msg *MsgVoteInbound) { + msg.RevertOptions = NewRevertOptionsFromZEVM(revertOptions) + } +} + +// WithEVMRevertOptions sets the revert options for the inbound vote message (EVM format) +// the function convert the type from abigen to type defined in proto +func WithEVMRevertOptions(revertOptions gatewayevm.RevertOptions) InboundVoteOption { + return func(msg *MsgVoteInbound) { + msg.RevertOptions = NewRevertOptionsFromEVM(revertOptions) + } +} + var _ sdk.Msg = &MsgVoteInbound{} func NewMsgVoteInbound( @@ -34,23 +55,33 @@ func NewMsgVoteInbound( coinType coin.CoinType, asset string, eventIndex uint, + protocolContractVersion ProtocolContractVersion, + options ...InboundVoteOption, ) *MsgVoteInbound { - return &MsgVoteInbound{ - Creator: creator, - Sender: sender, - SenderChainId: senderChain, - TxOrigin: txOrigin, - Receiver: receiver, - ReceiverChain: receiverChain, - Amount: amount, - Message: message, - InboundHash: inboundHash, - InboundBlockHeight: inboundBlockHeight, - GasLimit: gasLimit, - CoinType: coinType, - Asset: asset, - EventIndex: uint64(eventIndex), + msg := &MsgVoteInbound{ + Creator: creator, + Sender: sender, + SenderChainId: senderChain, + TxOrigin: txOrigin, + Receiver: receiver, + ReceiverChain: receiverChain, + Amount: amount, + Message: message, + InboundHash: inboundHash, + InboundBlockHeight: inboundBlockHeight, + GasLimit: gasLimit, + CoinType: coinType, + Asset: asset, + EventIndex: uint64(eventIndex), + ProtocolContractVersion: protocolContractVersion, + RevertOptions: NewEmptyRevertOptions(), } + + for _, option := range options { + option(msg) + } + + return msg } func (msg *MsgVoteInbound) Route() string { diff --git a/x/crosschain/types/message_vote_inbound_test.go b/x/crosschain/types/message_vote_inbound_test.go index f96f640b9e..29cf49ba40 100644 --- a/x/crosschain/types/message_vote_inbound_test.go +++ b/x/crosschain/types/message_vote_inbound_test.go @@ -1,6 +1,9 @@ package types_test import ( + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + "math/big" "math/rand" "testing" @@ -15,6 +18,168 @@ import ( "github.com/zeta-chain/zetacore/x/crosschain/types" ) +func TestNewMsgVoteInbound(t *testing.T) { + t.Run("empty revert options by default", func(t *testing.T) { + msg := types.NewMsgVoteInbound( + sample.AccAddress(), + sample.AccAddress(), + 42, + sample.String(), + sample.String(), + 42, + math.NewUint(42), + sample.String(), + sample.String(), + 42, + 42, + coin.CoinType_Zeta, + sample.String(), + 42, + types.ProtocolContractVersion_V1, + ) + require.EqualValues(t, types.NewEmptyRevertOptions(), msg.RevertOptions) + }) + + t.Run("can set ZEVM revert options", func(t *testing.T) { + revertAddress := sample.EthAddress() + abortAddress := sample.EthAddress() + revertMessage := sample.Bytes() + + msg := types.NewMsgVoteInbound( + sample.AccAddress(), + sample.AccAddress(), + 42, + sample.String(), + sample.String(), + 42, + math.NewUint(42), + sample.String(), + sample.String(), + 42, + 42, + coin.CoinType_Zeta, + sample.String(), + 42, + types.ProtocolContractVersion_V1, + types.WithZEVMRevertOptions(gatewayzevm.RevertOptions{ + RevertAddress: revertAddress, + CallOnRevert: true, + AbortAddress: abortAddress, + RevertMessage: revertMessage, + OnRevertGasLimit: big.NewInt(1000), + }), + ) + require.EqualValues(t, types.RevertOptions{ + RevertAddress: revertAddress.Hex(), + CallOnRevert: true, + AbortAddress: abortAddress.Hex(), + RevertMessage: revertMessage, + RevertGasLimit: math.NewUint(1000), + }, msg.RevertOptions) + + // if revertGasLimit not specified, it should be zero + msg = types.NewMsgVoteInbound( + sample.AccAddress(), + sample.AccAddress(), + 42, + sample.String(), + sample.String(), + 42, + math.NewUint(42), + sample.String(), + sample.String(), + 42, + 42, + coin.CoinType_Zeta, + sample.String(), + 42, + types.ProtocolContractVersion_V1, + types.WithZEVMRevertOptions(gatewayzevm.RevertOptions{ + RevertAddress: revertAddress, + CallOnRevert: true, + AbortAddress: abortAddress, + RevertMessage: revertMessage, + }), + ) + require.EqualValues(t, types.RevertOptions{ + RevertAddress: revertAddress.Hex(), + CallOnRevert: true, + AbortAddress: abortAddress.Hex(), + RevertMessage: revertMessage, + RevertGasLimit: math.ZeroUint(), + }, msg.RevertOptions) + }) + + t.Run("can set EVM revert options", func(t *testing.T) { + revertAddress := sample.EthAddress() + abortAddress := sample.EthAddress() + revertMessage := sample.Bytes() + + msg := types.NewMsgVoteInbound( + sample.AccAddress(), + sample.AccAddress(), + 42, + sample.String(), + sample.String(), + 42, + math.NewUint(42), + sample.String(), + sample.String(), + 42, + 42, + coin.CoinType_Zeta, + sample.String(), + 42, + types.ProtocolContractVersion_V1, + types.WithEVMRevertOptions(gatewayevm.RevertOptions{ + RevertAddress: revertAddress, + CallOnRevert: true, + AbortAddress: abortAddress, + RevertMessage: revertMessage, + OnRevertGasLimit: big.NewInt(1000), + }), + ) + require.EqualValues(t, types.RevertOptions{ + RevertAddress: revertAddress.Hex(), + CallOnRevert: true, + AbortAddress: abortAddress.Hex(), + RevertMessage: revertMessage, + RevertGasLimit: math.NewUint(1000), + }, msg.RevertOptions) + + msg = types.NewMsgVoteInbound( + sample.AccAddress(), + sample.AccAddress(), + 42, + sample.String(), + sample.String(), + 42, + math.NewUint(42), + sample.String(), + sample.String(), + 42, + 42, + coin.CoinType_Zeta, + sample.String(), + 42, + types.ProtocolContractVersion_V1, + types.WithEVMRevertOptions(gatewayevm.RevertOptions{ + RevertAddress: revertAddress, + CallOnRevert: true, + AbortAddress: abortAddress, + RevertMessage: revertMessage, + }), + ) + require.EqualValues(t, types.RevertOptions{ + RevertAddress: revertAddress.Hex(), + CallOnRevert: true, + AbortAddress: abortAddress.Hex(), + RevertMessage: revertMessage, + RevertGasLimit: math.ZeroUint(), + }, msg.RevertOptions) + }) +} + func TestMsgVoteInbound_ValidateBasic(t *testing.T) { r := rand.New(rand.NewSource(42)) @@ -40,6 +205,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { coin.CoinType_Zeta, sample.String(), 42, + types.ProtocolContractVersion_V1, ), }, { @@ -59,6 +225,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { coin.CoinType_Zeta, sample.String(), 42, + types.ProtocolContractVersion_V1, ), err: sdkerrors.ErrInvalidAddress, }, @@ -79,6 +246,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { coin.CoinType_Zeta, sample.String(), 42, + types.ProtocolContractVersion_V1, ), err: types.ErrInvalidChainID, }, @@ -99,6 +267,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { coin.CoinType_Zeta, sample.String(), 42, + types.ProtocolContractVersion_V1, ), err: types.ErrInvalidChainID, }, @@ -119,6 +288,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { coin.CoinType_Zeta, sample.String(), 42, + types.ProtocolContractVersion_V1, ), err: sdkerrors.ErrInvalidRequest, }, @@ -139,20 +309,21 @@ func TestMsgVoteInbound_Digest(t *testing.T) { r := rand.New(rand.NewSource(42)) msg := types.MsgVoteInbound{ - Creator: sample.AccAddress(), - Sender: sample.AccAddress(), - SenderChainId: 42, - TxOrigin: sample.String(), - Receiver: sample.String(), - ReceiverChain: 42, - Amount: math.NewUint(42), - Message: sample.String(), - InboundHash: sample.String(), - InboundBlockHeight: 42, - GasLimit: 42, - CoinType: coin.CoinType_Zeta, - Asset: sample.String(), - EventIndex: 42, + Creator: sample.AccAddress(), + Sender: sample.AccAddress(), + SenderChainId: 42, + TxOrigin: sample.String(), + Receiver: sample.String(), + ReceiverChain: 42, + Amount: math.NewUint(42), + Message: sample.String(), + InboundHash: sample.String(), + InboundBlockHeight: 42, + GasLimit: 42, + CoinType: coin.CoinType_Zeta, + Asset: sample.String(), + EventIndex: 42, + ProtocolContractVersion: types.ProtocolContractVersion_V1, } hash := msg.Digest() require.NotEmpty(t, hash, "hash should not be empty") @@ -240,6 +411,12 @@ func TestMsgVoteInbound_Digest(t *testing.T) { msg.EventIndex = 43 hash2 = msg.Digest() require.NotEqual(t, hash, hash2, "event index should change hash") + + // protocol contract version used + msg = msg + msg.ProtocolContractVersion = types.ProtocolContractVersion_V2 + hash2 = msg.Digest() + require.NotEqual(t, hash, hash2, "protocol contract version should change hash") } func TestMsgVoteInbound_GetSigners(t *testing.T) { diff --git a/x/crosschain/types/revert_options.go b/x/crosschain/types/revert_options.go new file mode 100644 index 0000000000..1f07e96239 --- /dev/null +++ b/x/crosschain/types/revert_options.go @@ -0,0 +1,83 @@ +package types + +import ( + sdkmath "cosmossdk.io/math" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + + "github.com/zeta-chain/zetacore/pkg/crypto" +) + +// NewEmptyRevertOptions initializes a new empty RevertOptions +func NewEmptyRevertOptions() RevertOptions { + return RevertOptions{ + RevertGasLimit: sdkmath.ZeroUint(), // default to 0 instead of nil + } +} + +// NewRevertOptionsFromZEVM initializes a new RevertOptions from a gatewayzevm.RevertOptions +func NewRevertOptionsFromZEVM(revertOptions gatewayzevm.RevertOptions) RevertOptions { + revertGasLimit := sdkmath.ZeroUint() + if revertOptions.OnRevertGasLimit != nil { + revertGasLimit = sdkmath.NewUintFromBigInt(revertOptions.OnRevertGasLimit) + } + + return RevertOptions{ + RevertAddress: revertOptions.RevertAddress.Hex(), + CallOnRevert: revertOptions.CallOnRevert, + AbortAddress: revertOptions.AbortAddress.Hex(), + RevertMessage: revertOptions.RevertMessage, + RevertGasLimit: revertGasLimit, + } +} + +// NewRevertOptionsFromEVM initializes a new RevertOptions from a gatewayevm.RevertOptions +func NewRevertOptionsFromEVM(revertOptions gatewayevm.RevertOptions) RevertOptions { + revertGasLimit := sdkmath.ZeroUint() + if revertOptions.OnRevertGasLimit != nil { + revertGasLimit = sdkmath.NewUintFromBigInt(revertOptions.OnRevertGasLimit) + } + + return RevertOptions{ + RevertAddress: revertOptions.RevertAddress.Hex(), + CallOnRevert: revertOptions.CallOnRevert, + AbortAddress: revertOptions.AbortAddress.Hex(), + RevertMessage: revertOptions.RevertMessage, + RevertGasLimit: revertGasLimit, + } +} + +// ToZEVMRevertOptions converts the RevertOptions to a gatewayzevm.RevertOptions +func (r RevertOptions) ToZEVMRevertOptions() gatewayzevm.RevertOptions { + return gatewayzevm.RevertOptions{ + RevertAddress: ethcommon.HexToAddress(r.RevertAddress), + CallOnRevert: r.CallOnRevert, + AbortAddress: ethcommon.HexToAddress(r.AbortAddress), + RevertMessage: r.RevertMessage, + } +} + +// ToEVMRevertOptions converts the RevertOptions to a gatewayevm.RevertOptions +func (r RevertOptions) ToEVMRevertOptions() gatewayevm.RevertOptions { + return gatewayevm.RevertOptions{ + RevertAddress: ethcommon.HexToAddress(r.RevertAddress), + CallOnRevert: r.CallOnRevert, + AbortAddress: ethcommon.HexToAddress(r.AbortAddress), + RevertMessage: r.RevertMessage, + } +} + +// GetEVMRevertAddress returns the EVM revert address +// if the revert address is not a valid address, it returns false +func (r RevertOptions) GetEVMRevertAddress() (ethcommon.Address, bool) { + addr := ethcommon.HexToAddress(r.RevertAddress) + return addr, !crypto.IsEmptyAddress(addr) +} + +// GetEVMAbortAddress returns the EVM abort address +// if the abort address is not a valid address, it returns false +func (r RevertOptions) GetEVMAbortAddress() (ethcommon.Address, bool) { + addr := ethcommon.HexToAddress(r.AbortAddress) + return addr, !crypto.IsEmptyAddress(addr) +} diff --git a/x/crosschain/types/revert_options_test.go b/x/crosschain/types/revert_options_test.go new file mode 100644 index 0000000000..2b178e7c3f --- /dev/null +++ b/x/crosschain/types/revert_options_test.go @@ -0,0 +1,81 @@ +package types_test + +import ( + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/constant" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/crosschain/types" + "testing" +) + +func TestRevertOptions_GetEVMRevertAddress(t *testing.T) { + t.Run("valid revert address", func(t *testing.T) { + addr := sample.EthAddress() + actualAddr, valid := types.RevertOptions{ + RevertAddress: addr.Hex(), + }.GetEVMRevertAddress() + + require.True(t, valid) + require.Equal(t, addr.Hex(), actualAddr.Hex()) + }) + + t.Run("invalid revert address", func(t *testing.T) { + _, valid := types.RevertOptions{ + RevertAddress: "invalid", + }.GetEVMRevertAddress() + + require.False(t, valid) + }) + + t.Run("empty revert address", func(t *testing.T) { + _, valid := types.RevertOptions{ + RevertAddress: "", + }.GetEVMRevertAddress() + + require.False(t, valid) + }) + + t.Run("zero revert address", func(t *testing.T) { + _, valid := types.RevertOptions{ + RevertAddress: constant.EVMZeroAddress, + }.GetEVMRevertAddress() + + require.False(t, valid) + }) +} + +func TestRevertOptions_GetEVMAbortAddress(t *testing.T) { + t.Run("valid abort address", func(t *testing.T) { + addr := sample.EthAddress() + actualAddr, valid := types.RevertOptions{ + AbortAddress: addr.Hex(), + }.GetEVMAbortAddress() + + require.True(t, valid) + require.Equal(t, addr.Hex(), actualAddr.Hex()) + }) + + t.Run("invalid abort address", func(t *testing.T) { + _, valid := types.RevertOptions{ + AbortAddress: "invalid", + }.GetEVMAbortAddress() + + require.False(t, valid) + }) + + t.Run("empty abort address", func(t *testing.T) { + _, valid := types.RevertOptions{ + AbortAddress: "", + }.GetEVMAbortAddress() + + require.False(t, valid) + }) + + t.Run("zero abort address", func(t *testing.T) { + _, valid := types.RevertOptions{ + AbortAddress: constant.EVMZeroAddress, + }.GetEVMAbortAddress() + + require.False(t, valid) + }) +} diff --git a/x/crosschain/types/status.go b/x/crosschain/types/status.go index 320209ae78..cdaf5b0aae 100644 --- a/x/crosschain/types/status.go +++ b/x/crosschain/types/status.go @@ -4,10 +4,9 @@ import ( "fmt" ) -func (m *Status) AbortRefunded(timeStamp int64) { +func (m *Status) AbortRefunded() { m.IsAbortRefunded = true m.StatusMessage = "CCTX aborted and Refunded" - m.LastUpdateTimestamp = timeStamp } // ChangeStatus changes the status of the cross chain transaction diff --git a/x/crosschain/types/status_test.go b/x/crosschain/types/status_test.go index 247cc19f50..49ed1fc74f 100644 --- a/x/crosschain/types/status_test.go +++ b/x/crosschain/types/status_test.go @@ -3,7 +3,6 @@ package types_test import ( "fmt" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -19,11 +18,9 @@ func TestStatus_AbortRefunded(t *testing.T) { LastUpdateTimestamp: 0, IsAbortRefunded: false, } - timestamp := time.Now().Unix() - status.AbortRefunded(timestamp) + status.AbortRefunded() require.Equal(t, status.IsAbortRefunded, true) require.Equal(t, status.StatusMessage, "CCTX aborted and Refunded") - require.Equal(t, status.LastUpdateTimestamp, timestamp) }) } diff --git a/x/crosschain/types/tx.pb.go b/x/crosschain/types/tx.pb.go index 4bd154f595..bbf88f017b 100644 --- a/x/crosschain/types/tx.pb.go +++ b/x/crosschain/types/tx.pb.go @@ -997,6 +997,10 @@ type MsgVoteInbound struct { Asset string `protobuf:"bytes,14,opt,name=asset,proto3" json:"asset,omitempty"` // event index of the sent asset in the observed tx EventIndex uint64 `protobuf:"varint,15,opt,name=event_index,json=eventIndex,proto3" json:"event_index,omitempty"` + // protocol contract version to use for the cctx workflow + ProtocolContractVersion ProtocolContractVersion `protobuf:"varint,16,opt,name=protocol_contract_version,json=protocolContractVersion,proto3,enum=zetachain.zetacore.crosschain.ProtocolContractVersion" json:"protocol_contract_version,omitempty"` + // revert options provided by the sender + RevertOptions RevertOptions `protobuf:"bytes,17,opt,name=revert_options,json=revertOptions,proto3" json:"revert_options"` } func (m *MsgVoteInbound) Reset() { *m = MsgVoteInbound{} } @@ -1123,6 +1127,20 @@ func (m *MsgVoteInbound) GetEventIndex() uint64 { return 0 } +func (m *MsgVoteInbound) GetProtocolContractVersion() ProtocolContractVersion { + if m != nil { + return m.ProtocolContractVersion + } + return ProtocolContractVersion_V1 +} + +func (m *MsgVoteInbound) GetRevertOptions() RevertOptions { + if m != nil { + return m.RevertOptions + } + return RevertOptions{} +} + type MsgVoteInboundResponse struct { } @@ -1431,6 +1449,227 @@ func (m *MsgUpdateRateLimiterFlagsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateRateLimiterFlagsResponse proto.InternalMessageInfo +type MsgMigrateERC20CustodyFunds struct { + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` + ChainId int64 `protobuf:"varint,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + NewCustodyAddress string `protobuf:"bytes,3,opt,name=new_custody_address,json=newCustodyAddress,proto3" json:"new_custody_address,omitempty"` + Erc20Address string `protobuf:"bytes,4,opt,name=erc20_address,json=erc20Address,proto3" json:"erc20_address,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Uint `protobuf:"bytes,5,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Uint" json:"amount"` +} + +func (m *MsgMigrateERC20CustodyFunds) Reset() { *m = MsgMigrateERC20CustodyFunds{} } +func (m *MsgMigrateERC20CustodyFunds) String() string { return proto.CompactTextString(m) } +func (*MsgMigrateERC20CustodyFunds) ProtoMessage() {} +func (*MsgMigrateERC20CustodyFunds) Descriptor() ([]byte, []int) { + return fileDescriptor_15f0860550897740, []int{24} +} +func (m *MsgMigrateERC20CustodyFunds) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMigrateERC20CustodyFunds) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMigrateERC20CustodyFunds.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMigrateERC20CustodyFunds) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMigrateERC20CustodyFunds.Merge(m, src) +} +func (m *MsgMigrateERC20CustodyFunds) XXX_Size() int { + return m.Size() +} +func (m *MsgMigrateERC20CustodyFunds) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMigrateERC20CustodyFunds.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMigrateERC20CustodyFunds proto.InternalMessageInfo + +func (m *MsgMigrateERC20CustodyFunds) GetCreator() string { + if m != nil { + return m.Creator + } + return "" +} + +func (m *MsgMigrateERC20CustodyFunds) GetChainId() int64 { + if m != nil { + return m.ChainId + } + return 0 +} + +func (m *MsgMigrateERC20CustodyFunds) GetNewCustodyAddress() string { + if m != nil { + return m.NewCustodyAddress + } + return "" +} + +func (m *MsgMigrateERC20CustodyFunds) GetErc20Address() string { + if m != nil { + return m.Erc20Address + } + return "" +} + +type MsgMigrateERC20CustodyFundsResponse struct { + CctxIndex string `protobuf:"bytes,1,opt,name=cctx_index,json=cctxIndex,proto3" json:"cctx_index,omitempty"` +} + +func (m *MsgMigrateERC20CustodyFundsResponse) Reset() { *m = MsgMigrateERC20CustodyFundsResponse{} } +func (m *MsgMigrateERC20CustodyFundsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgMigrateERC20CustodyFundsResponse) ProtoMessage() {} +func (*MsgMigrateERC20CustodyFundsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_15f0860550897740, []int{25} +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgMigrateERC20CustodyFundsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgMigrateERC20CustodyFundsResponse.Merge(m, src) +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgMigrateERC20CustodyFundsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgMigrateERC20CustodyFundsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgMigrateERC20CustodyFundsResponse proto.InternalMessageInfo + +func (m *MsgMigrateERC20CustodyFundsResponse) GetCctxIndex() string { + if m != nil { + return m.CctxIndex + } + return "" +} + +type MsgUpdateERC20CustodyPauseStatus struct { + Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` + ChainId int64 `protobuf:"varint,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + // pause or unpause + // true = pause, false = unpause + Pause bool `protobuf:"varint,3,opt,name=pause,proto3" json:"pause,omitempty"` +} + +func (m *MsgUpdateERC20CustodyPauseStatus) Reset() { *m = MsgUpdateERC20CustodyPauseStatus{} } +func (m *MsgUpdateERC20CustodyPauseStatus) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateERC20CustodyPauseStatus) ProtoMessage() {} +func (*MsgUpdateERC20CustodyPauseStatus) Descriptor() ([]byte, []int) { + return fileDescriptor_15f0860550897740, []int{26} +} +func (m *MsgUpdateERC20CustodyPauseStatus) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateERC20CustodyPauseStatus) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateERC20CustodyPauseStatus.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateERC20CustodyPauseStatus) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateERC20CustodyPauseStatus.Merge(m, src) +} +func (m *MsgUpdateERC20CustodyPauseStatus) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateERC20CustodyPauseStatus) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateERC20CustodyPauseStatus.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateERC20CustodyPauseStatus proto.InternalMessageInfo + +func (m *MsgUpdateERC20CustodyPauseStatus) GetCreator() string { + if m != nil { + return m.Creator + } + return "" +} + +func (m *MsgUpdateERC20CustodyPauseStatus) GetChainId() int64 { + if m != nil { + return m.ChainId + } + return 0 +} + +func (m *MsgUpdateERC20CustodyPauseStatus) GetPause() bool { + if m != nil { + return m.Pause + } + return false +} + +type MsgUpdateERC20CustodyPauseStatusResponse struct { + CctxIndex string `protobuf:"bytes,1,opt,name=cctx_index,json=cctxIndex,proto3" json:"cctx_index,omitempty"` +} + +func (m *MsgUpdateERC20CustodyPauseStatusResponse) Reset() { + *m = MsgUpdateERC20CustodyPauseStatusResponse{} +} +func (m *MsgUpdateERC20CustodyPauseStatusResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateERC20CustodyPauseStatusResponse) ProtoMessage() {} +func (*MsgUpdateERC20CustodyPauseStatusResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_15f0860550897740, []int{27} +} +func (m *MsgUpdateERC20CustodyPauseStatusResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateERC20CustodyPauseStatusResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateERC20CustodyPauseStatusResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateERC20CustodyPauseStatusResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateERC20CustodyPauseStatusResponse.Merge(m, src) +} +func (m *MsgUpdateERC20CustodyPauseStatusResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateERC20CustodyPauseStatusResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateERC20CustodyPauseStatusResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateERC20CustodyPauseStatusResponse proto.InternalMessageInfo + +func (m *MsgUpdateERC20CustodyPauseStatusResponse) GetCctxIndex() string { + if m != nil { + return m.CctxIndex + } + return "" +} + func init() { proto.RegisterType((*MsgMigrateTssFunds)(nil), "zetachain.zetacore.crosschain.MsgMigrateTssFunds") proto.RegisterType((*MsgMigrateTssFundsResponse)(nil), "zetachain.zetacore.crosschain.MsgMigrateTssFundsResponse") @@ -1456,6 +1695,10 @@ func init() { proto.RegisterType((*MsgRefundAbortedCCTXResponse)(nil), "zetachain.zetacore.crosschain.MsgRefundAbortedCCTXResponse") proto.RegisterType((*MsgUpdateRateLimiterFlags)(nil), "zetachain.zetacore.crosschain.MsgUpdateRateLimiterFlags") proto.RegisterType((*MsgUpdateRateLimiterFlagsResponse)(nil), "zetachain.zetacore.crosschain.MsgUpdateRateLimiterFlagsResponse") + proto.RegisterType((*MsgMigrateERC20CustodyFunds)(nil), "zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFunds") + proto.RegisterType((*MsgMigrateERC20CustodyFundsResponse)(nil), "zetachain.zetacore.crosschain.MsgMigrateERC20CustodyFundsResponse") + proto.RegisterType((*MsgUpdateERC20CustodyPauseStatus)(nil), "zetachain.zetacore.crosschain.MsgUpdateERC20CustodyPauseStatus") + proto.RegisterType((*MsgUpdateERC20CustodyPauseStatusResponse)(nil), "zetachain.zetacore.crosschain.MsgUpdateERC20CustodyPauseStatusResponse") } func init() { @@ -1463,105 +1706,119 @@ func init() { } var fileDescriptor_15f0860550897740 = []byte{ - // 1557 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x58, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xcf, 0x36, 0x8e, 0x63, 0x3f, 0xc7, 0x4e, 0xba, 0xa4, 0xa9, 0xb3, 0x69, 0x9c, 0xd4, 0xa5, - 0x21, 0x42, 0xad, 0x9d, 0xba, 0xa5, 0x94, 0x16, 0x01, 0x8d, 0xe9, 0x9f, 0x40, 0xdd, 0x46, 0xdb, - 0x14, 0x10, 0x97, 0xd5, 0x7a, 0x77, 0xb2, 0x5e, 0xd9, 0xde, 0xb1, 0x76, 0xc6, 0x96, 0x53, 0x21, - 0x81, 0x90, 0x90, 0x38, 0x02, 0xe2, 0xc4, 0x81, 0x1b, 0x12, 0x67, 0x3e, 0x45, 0x8f, 0x15, 0x27, - 0xc4, 0xa1, 0x42, 0xed, 0x27, 0x80, 0x4f, 0x80, 0x76, 0x66, 0x76, 0xeb, 0x5d, 0xff, 0x8d, 0x2b, - 0xc4, 0xc5, 0xde, 0x79, 0xf3, 0x7e, 0x6f, 0xde, 0xbf, 0x79, 0xef, 0xed, 0xc2, 0xd6, 0x63, 0x44, - 0x75, 0xa3, 0xa6, 0xdb, 0x4e, 0x91, 0x3d, 0x61, 0x17, 0x15, 0x0d, 0x17, 0x13, 0xc2, 0x69, 0xb4, - 0x5b, 0x68, 0xb9, 0x98, 0x62, 0x79, 0x3d, 0xe0, 0x2b, 0xf8, 0x7c, 0x85, 0x97, 0x7c, 0xca, 0xb2, - 0x85, 0x2d, 0xcc, 0x38, 0x8b, 0xde, 0x13, 0x07, 0x29, 0x6f, 0x0e, 0x10, 0xde, 0xaa, 0x5b, 0x45, - 0x46, 0x22, 0xe2, 0x4f, 0xf0, 0x6e, 0x0d, 0xe3, 0xc5, 0xb6, 0xc3, 0x7e, 0xc6, 0xc8, 0x6c, 0xb9, - 0x18, 0x1f, 0x12, 0xf1, 0x27, 0x78, 0xaf, 0x8e, 0x36, 0xce, 0xd5, 0x29, 0xd2, 0x1a, 0x76, 0xd3, - 0xa6, 0xc8, 0xd5, 0x0e, 0x1b, 0xba, 0x25, 0x70, 0xf9, 0x1f, 0x24, 0x90, 0x2b, 0xc4, 0xaa, 0xd8, - 0x96, 0xc7, 0x72, 0x40, 0xc8, 0xed, 0xb6, 0x63, 0x12, 0x39, 0x0b, 0xf3, 0x86, 0x8b, 0x74, 0x8a, - 0xdd, 0xac, 0xb4, 0x29, 0x6d, 0x27, 0x55, 0x7f, 0x29, 0xaf, 0x42, 0x82, 0x89, 0xd4, 0x6c, 0x33, - 0x7b, 0x62, 0x53, 0xda, 0x9e, 0x55, 0xe7, 0xd9, 0x7a, 0xcf, 0x94, 0xef, 0x40, 0x5c, 0x6f, 0xe2, - 0xb6, 0x43, 0xb3, 0xb3, 0x1e, 0x66, 0xb7, 0xf8, 0xe4, 0xd9, 0xc6, 0xcc, 0x9f, 0xcf, 0x36, 0xde, - 0xb0, 0x6c, 0x5a, 0x6b, 0x57, 0x0b, 0x06, 0x6e, 0x16, 0x0d, 0x4c, 0x9a, 0x98, 0x88, 0xbf, 0x8b, - 0xc4, 0xac, 0x17, 0xe9, 0x51, 0x0b, 0x91, 0xc2, 0x23, 0xdb, 0xa1, 0xaa, 0x80, 0xe7, 0xcf, 0x80, - 0xd2, 0xaf, 0x93, 0x8a, 0x48, 0x0b, 0x3b, 0x04, 0xe5, 0xef, 0xc3, 0x6b, 0x15, 0x62, 0x3d, 0x6a, - 0x99, 0x7c, 0xf3, 0xa6, 0x69, 0xba, 0x88, 0x8c, 0x52, 0x79, 0x1d, 0x80, 0x12, 0xa2, 0xb5, 0xda, - 0xd5, 0x3a, 0x3a, 0x62, 0x4a, 0x27, 0xd5, 0x24, 0x25, 0x64, 0x9f, 0x11, 0xf2, 0xeb, 0xb0, 0x36, - 0x40, 0x5e, 0x70, 0xdc, 0xcf, 0x27, 0x60, 0xb9, 0x42, 0xac, 0x9b, 0xa6, 0xb9, 0xe7, 0x54, 0x71, - 0xdb, 0x31, 0x0f, 0x5c, 0xdd, 0xa8, 0x23, 0x77, 0x3a, 0x1f, 0x9d, 0x86, 0x79, 0xda, 0xd5, 0x6a, - 0x3a, 0xa9, 0x71, 0x27, 0xa9, 0x71, 0xda, 0xbd, 0xab, 0x93, 0x9a, 0xbc, 0x0b, 0x49, 0x2f, 0xf4, - 0x9a, 0xe7, 0x8e, 0x6c, 0x6c, 0x53, 0xda, 0xce, 0x94, 0xce, 0x17, 0x06, 0x64, 0x62, 0xab, 0x6e, - 0x15, 0x58, 0x8e, 0x94, 0xb1, 0xed, 0x1c, 0x1c, 0xb5, 0x90, 0x9a, 0x30, 0xc4, 0x93, 0x7c, 0x1d, - 0xe6, 0x58, 0x52, 0x64, 0xe7, 0x36, 0xa5, 0xed, 0x54, 0xe9, 0xf5, 0x61, 0x78, 0x91, 0x39, 0xfb, - 0xde, 0x9f, 0xca, 0x21, 0x9e, 0x93, 0xaa, 0x0d, 0x6c, 0xd4, 0xb9, 0x6e, 0x71, 0xee, 0x24, 0x46, - 0x61, 0xea, 0xad, 0x42, 0x82, 0x76, 0x35, 0xdb, 0x31, 0x51, 0x37, 0x3b, 0xcf, 0x4d, 0xa2, 0xdd, - 0x3d, 0x6f, 0x99, 0xcf, 0xc1, 0x99, 0x41, 0xfe, 0x09, 0x1c, 0xf8, 0xbb, 0x04, 0x27, 0x2b, 0xc4, - 0xfa, 0xb4, 0x66, 0x53, 0xd4, 0xb0, 0x09, 0xbd, 0xa5, 0x96, 0x4b, 0x3b, 0x23, 0xbc, 0x77, 0x0e, - 0xd2, 0xc8, 0x35, 0x4a, 0x3b, 0x9a, 0xce, 0x23, 0x21, 0x22, 0xb6, 0xc0, 0x88, 0x7e, 0xb4, 0x7b, - 0x5d, 0x3c, 0x1b, 0x76, 0xb1, 0x0c, 0x31, 0x47, 0x6f, 0x72, 0x27, 0x26, 0x55, 0xf6, 0x2c, 0xaf, - 0x40, 0x9c, 0x1c, 0x35, 0xab, 0xb8, 0xc1, 0x5c, 0x93, 0x54, 0xc5, 0x4a, 0x56, 0x20, 0x61, 0x22, - 0xc3, 0x6e, 0xea, 0x0d, 0xc2, 0x6c, 0x4e, 0xab, 0xc1, 0x5a, 0x5e, 0x83, 0xa4, 0xa5, 0x13, 0x7e, - 0x6b, 0x84, 0xcd, 0x09, 0x4b, 0x27, 0xf7, 0xbc, 0x75, 0x5e, 0x83, 0xd5, 0x3e, 0x9b, 0x7c, 0x8b, - 0x3d, 0x0b, 0x1e, 0x87, 0x2c, 0xe0, 0x16, 0x2e, 0x3c, 0xee, 0xb5, 0x60, 0x1d, 0xc0, 0x30, 0x02, - 0x9f, 0x8a, 0xac, 0xf4, 0x28, 0xdc, 0xab, 0x7f, 0x4b, 0x70, 0x8a, 0xbb, 0xf5, 0x41, 0x9b, 0xbe, - 0x7a, 0xde, 0x2d, 0xc3, 0x9c, 0x83, 0x1d, 0x03, 0x31, 0x67, 0xc5, 0x54, 0xbe, 0xe8, 0xcd, 0xc6, - 0x58, 0x28, 0x1b, 0xff, 0x9f, 0x4c, 0x7a, 0x0f, 0xd6, 0x07, 0x9a, 0x1c, 0x38, 0x76, 0x1d, 0xc0, - 0x26, 0x9a, 0x8b, 0x9a, 0xb8, 0x83, 0x4c, 0x66, 0x7d, 0x42, 0x4d, 0xda, 0x44, 0xe5, 0x84, 0x3c, - 0x82, 0x6c, 0x85, 0x58, 0x7c, 0xf5, 0xdf, 0x79, 0x2d, 0x9f, 0x87, 0xcd, 0x61, 0xc7, 0x04, 0x49, - 0xff, 0xab, 0x04, 0x8b, 0x15, 0x62, 0x7d, 0x82, 0x29, 0xba, 0xa3, 0x93, 0x7d, 0xd7, 0x36, 0xd0, - 0xd4, 0x2a, 0xb4, 0x3c, 0xb4, 0xaf, 0x02, 0x5b, 0xc8, 0x67, 0x61, 0xa1, 0xe5, 0xda, 0xd8, 0xb5, - 0xe9, 0x91, 0x76, 0x88, 0x10, 0xf3, 0x72, 0x4c, 0x4d, 0xf9, 0xb4, 0xdb, 0x88, 0xb1, 0xf0, 0x30, - 0x38, 0xed, 0x66, 0x15, 0xb9, 0x2c, 0xc0, 0x31, 0x35, 0xc5, 0x68, 0xf7, 0x19, 0xe9, 0xa3, 0x58, - 0x62, 0x6e, 0x29, 0x9e, 0x5f, 0x85, 0xd3, 0x11, 0x4d, 0x03, 0x2b, 0x7e, 0x89, 0x07, 0x56, 0xf8, - 0x86, 0x8e, 0xb0, 0x62, 0x0d, 0x58, 0xfe, 0xf2, 0xb8, 0xf3, 0x84, 0x4e, 0x78, 0x04, 0x16, 0xf6, - 0x2b, 0xb0, 0x82, 0xab, 0x04, 0xb9, 0x1d, 0x64, 0x6a, 0x58, 0xc8, 0xea, 0xad, 0x83, 0xcb, 0xfe, - 0xae, 0x7f, 0x10, 0x43, 0x95, 0x21, 0xd7, 0x8f, 0x12, 0xd9, 0x85, 0x6c, 0xab, 0x46, 0x85, 0x59, - 0x6b, 0x51, 0xf4, 0x2e, 0xcb, 0x37, 0xc6, 0x22, 0xdf, 0x00, 0xa5, 0x5f, 0x88, 0x77, 0xb5, 0xdb, - 0x04, 0x99, 0x59, 0x60, 0x02, 0x4e, 0x47, 0x05, 0xdc, 0xd1, 0xc9, 0x23, 0x82, 0x4c, 0xf9, 0x2b, - 0x09, 0xce, 0xf7, 0xa3, 0xd1, 0xe1, 0x21, 0x32, 0xa8, 0xdd, 0x41, 0x4c, 0x0e, 0x0f, 0x50, 0x8a, - 0x35, 0xbd, 0x82, 0x68, 0x7a, 0x5b, 0x13, 0x34, 0xbd, 0x3d, 0x87, 0xaa, 0x67, 0xa3, 0x07, 0xdf, - 0xf2, 0x45, 0x07, 0x79, 0xb3, 0x3f, 0x5e, 0x03, 0x5e, 0xa4, 0x16, 0x98, 0x29, 0x23, 0x25, 0xb2, - 0xea, 0x25, 0x63, 0xc8, 0x74, 0xf4, 0x46, 0x1b, 0x69, 0x2e, 0x32, 0x90, 0xed, 0xdd, 0x25, 0x56, - 0x16, 0x77, 0xef, 0x1e, 0xb3, 0x63, 0xff, 0xf3, 0x6c, 0xe3, 0xd4, 0x91, 0xde, 0x6c, 0x5c, 0xcf, - 0x87, 0xc5, 0xe5, 0xd5, 0x34, 0x23, 0xa8, 0x62, 0x2d, 0x7f, 0x08, 0x71, 0x42, 0x75, 0xda, 0xe6, - 0x55, 0x36, 0x53, 0xba, 0x30, 0xb4, 0xb5, 0xf1, 0x41, 0x49, 0x00, 0x1f, 0x32, 0x8c, 0x2a, 0xb0, - 0xf2, 0x79, 0xc8, 0x04, 0xf6, 0x33, 0x46, 0x51, 0x40, 0xd2, 0x3e, 0xb5, 0xec, 0x11, 0xe5, 0x0b, - 0x20, 0x07, 0x6c, 0x5e, 0xe3, 0xe7, 0x57, 0x38, 0xc1, 0x9c, 0xb3, 0xe4, 0xef, 0x1c, 0x10, 0x72, - 0x9f, 0xd5, 0xc0, 0x50, 0xe3, 0x4d, 0x4e, 0xd5, 0x78, 0x7b, 0xae, 0x90, 0xef, 0xf3, 0xe0, 0x0a, - 0xfd, 0x14, 0x83, 0x8c, 0xd8, 0x13, 0xfd, 0x71, 0xc4, 0x0d, 0xf2, 0xda, 0x14, 0x72, 0x4c, 0xe4, - 0x8a, 0xeb, 0x23, 0x56, 0xf2, 0x16, 0x2c, 0xf2, 0x27, 0x2d, 0xd2, 0xf4, 0xd2, 0x9c, 0x5c, 0x16, - 0xc5, 0x42, 0x81, 0x84, 0x08, 0x81, 0x2b, 0x0a, 0x7a, 0xb0, 0xf6, 0x9c, 0xe7, 0x3f, 0x0b, 0xe7, - 0xcd, 0x71, 0x11, 0x3e, 0x95, 0x3b, 0xef, 0xe5, 0x10, 0x17, 0x7f, 0xa5, 0x21, 0xce, 0xb3, 0xb2, - 0x89, 0x08, 0xd1, 0x2d, 0xee, 0xfa, 0xa4, 0xea, 0x2f, 0xbd, 0xca, 0x64, 0x3b, 0x3d, 0x05, 0x20, - 0xc9, 0xb6, 0x53, 0x82, 0xc6, 0xee, 0xfd, 0x0e, 0x2c, 0xfb, 0x2c, 0xa1, 0xdb, 0xce, 0x2f, 0xab, - 0x2c, 0xf6, 0x7a, 0x2f, 0x79, 0xa8, 0x5b, 0xa7, 0x18, 0x5b, 0xd0, 0xad, 0xc3, 0x31, 0x5e, 0x98, - 0x6e, 0xb8, 0x5a, 0x83, 0x24, 0xed, 0x6a, 0xd8, 0xb5, 0x2d, 0xdb, 0xc9, 0xa6, 0xb9, 0x73, 0x69, - 0xf7, 0x01, 0x5b, 0x7b, 0x55, 0x5a, 0x27, 0x04, 0xd1, 0x6c, 0x86, 0x6d, 0xf0, 0x85, 0xbc, 0x01, - 0x29, 0xd4, 0x41, 0x0e, 0x15, 0xdd, 0x6e, 0x91, 0x69, 0x05, 0x8c, 0xc4, 0x1b, 0x5e, 0x16, 0x56, - 0xc2, 0xb9, 0x11, 0xa4, 0xcd, 0x3d, 0x36, 0x33, 0xdd, 0xac, 0x62, 0x97, 0x3e, 0xa4, 0x6d, 0xa3, - 0x5e, 0x2e, 0x1f, 0x7c, 0x36, 0x7a, 0xc4, 0x1d, 0x35, 0x4c, 0xac, 0xb1, 0x69, 0x25, 0x2c, 0x2d, - 0x38, 0xaa, 0xc3, 0xe6, 0x5b, 0x15, 0x1d, 0xb6, 0x1d, 0x93, 0xb1, 0x20, 0xf3, 0x95, 0x4e, 0xe3, - 0x99, 0xe6, 0x49, 0x0b, 0xe6, 0x1f, 0x5e, 0xe2, 0xd3, 0x9c, 0x2a, 0x06, 0x20, 0x31, 0x37, 0xf6, - 0x9d, 0xfb, 0xf2, 0xe6, 0x48, 0x4c, 0x6b, 0x3e, 0x98, 0xab, 0x3a, 0x45, 0xf7, 0xf8, 0xfb, 0xcb, - 0x6d, 0xef, 0xf5, 0x65, 0x84, 0x76, 0x06, 0xc8, 0xfd, 0xaf, 0x3b, 0x4c, 0xcb, 0x54, 0xa9, 0x58, - 0x18, 0xf9, 0x72, 0x57, 0x88, 0x1e, 0xb3, 0x1b, 0xf3, 0xd2, 0x5f, 0x5d, 0x72, 0x23, 0xf4, 0xfc, - 0x39, 0x38, 0x3b, 0x54, 0x37, 0xdf, 0x82, 0xd2, 0x6f, 0x0b, 0x30, 0x5b, 0x21, 0x96, 0xfc, 0xad, - 0x04, 0xf2, 0x80, 0x41, 0xee, 0xca, 0x18, 0x65, 0x06, 0xce, 0x42, 0xca, 0xbb, 0xd3, 0xa0, 0x82, - 0x09, 0xea, 0x1b, 0x09, 0x4e, 0xf6, 0xbf, 0xca, 0x5c, 0x9e, 0x48, 0x66, 0x18, 0xa4, 0xdc, 0x98, - 0x02, 0x14, 0xe8, 0xf1, 0xbd, 0x04, 0xa7, 0x06, 0x0f, 0x6a, 0x6f, 0x8f, 0x17, 0x3b, 0x10, 0xa8, - 0xbc, 0x3f, 0x25, 0x30, 0xd0, 0xa9, 0x03, 0x0b, 0xa1, 0x79, 0xad, 0x30, 0x5e, 0x60, 0x2f, 0xbf, - 0x72, 0xf5, 0x78, 0xfc, 0xd1, 0x73, 0x83, 0x09, 0x6b, 0xc2, 0x73, 0x7d, 0xfe, 0x49, 0xcf, 0x8d, - 0xb6, 0x26, 0x99, 0x40, 0xaa, 0xb7, 0x2d, 0x5d, 0x9c, 0x4c, 0x8c, 0x60, 0x57, 0xde, 0x3a, 0x16, - 0x7b, 0x70, 0xe8, 0x17, 0x90, 0x89, 0xbc, 0x09, 0xee, 0x8c, 0x17, 0x14, 0x46, 0x28, 0xd7, 0x8e, - 0x8b, 0x08, 0x4e, 0xff, 0x5a, 0x82, 0xa5, 0xbe, 0x2f, 0x07, 0xa5, 0xf1, 0xe2, 0xa2, 0x18, 0xe5, - 0xfa, 0xf1, 0x31, 0x81, 0x12, 0x5f, 0xc2, 0x62, 0xf4, 0x7b, 0xcb, 0xa5, 0xf1, 0xe2, 0x22, 0x10, - 0xe5, 0x9d, 0x63, 0x43, 0x7a, 0x63, 0x10, 0xe9, 0x2c, 0x13, 0xc4, 0x20, 0x8c, 0x98, 0x24, 0x06, - 0x83, 0xfb, 0x0d, 0x2b, 0x41, 0xfd, 0xdd, 0xe6, 0xf2, 0x24, 0xb7, 0x37, 0x02, 0x9a, 0xa4, 0x04, - 0x0d, 0xed, 0x2f, 0xf2, 0x8f, 0x12, 0xac, 0x0c, 0x69, 0x2e, 0xd7, 0x26, 0x8d, 0x6e, 0x14, 0xa9, - 0x7c, 0x30, 0x2d, 0xd2, 0x57, 0x6b, 0xf7, 0xe3, 0x27, 0xcf, 0x73, 0xd2, 0xd3, 0xe7, 0x39, 0xe9, - 0xaf, 0xe7, 0x39, 0xe9, 0xbb, 0x17, 0xb9, 0x99, 0xa7, 0x2f, 0x72, 0x33, 0x7f, 0xbc, 0xc8, 0xcd, - 0x7c, 0x7e, 0xa9, 0x67, 0x04, 0xf3, 0x64, 0x5f, 0x8c, 0x7c, 0xef, 0xeb, 0x86, 0x3e, 0x67, 0x7a, - 0x13, 0x59, 0x35, 0xce, 0xbe, 0xf2, 0x5d, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0xda, 0xb5, 0x92, - 0x0a, 0xfc, 0x14, 0x00, 0x00, + // 1779 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x59, 0xcd, 0x6f, 0xdb, 0xc8, + 0x15, 0x37, 0xd7, 0xb2, 0x2c, 0x3d, 0xd9, 0xb2, 0xcd, 0x75, 0x6c, 0x99, 0x5e, 0xcb, 0x8e, 0xd2, + 0xb8, 0x46, 0x91, 0x48, 0x5e, 0x65, 0x9b, 0x6e, 0xbd, 0x45, 0xb7, 0xb1, 0x76, 0xe3, 0x75, 0x1b, + 0x25, 0x06, 0xd7, 0xd9, 0x7e, 0x5c, 0x08, 0x8a, 0x1c, 0xd3, 0x84, 0x25, 0x8e, 0xc0, 0x19, 0x29, + 0x72, 0x50, 0xa0, 0x45, 0x81, 0x02, 0x3d, 0xb6, 0x45, 0x4f, 0x39, 0xf4, 0x56, 0xa0, 0xfd, 0x4f, + 0x72, 0x0c, 0x7a, 0x2a, 0x7a, 0x08, 0x8a, 0xe4, 0xd4, 0x5b, 0xdb, 0x6b, 0x2f, 0x05, 0xdf, 0x0c, + 0x19, 0x89, 0xfa, 0xb4, 0x8c, 0x62, 0x2f, 0x16, 0xe7, 0xf1, 0xfd, 0xde, 0xbc, 0xaf, 0x99, 0xf7, + 0x1e, 0x0d, 0xbb, 0xcf, 0x09, 0x37, 0xad, 0x73, 0xd3, 0xf5, 0x4a, 0xf8, 0x44, 0x7d, 0x52, 0xb2, + 0x7c, 0xca, 0x98, 0xa0, 0xf1, 0x4e, 0xb1, 0xe9, 0x53, 0x4e, 0xd5, 0xad, 0x88, 0xaf, 0x18, 0xf2, + 0x15, 0xdf, 0xf1, 0x69, 0xab, 0x0e, 0x75, 0x28, 0x72, 0x96, 0x82, 0x27, 0x01, 0xd2, 0xbe, 0x35, + 0x40, 0x78, 0xf3, 0xc2, 0x29, 0x21, 0x89, 0xc9, 0x1f, 0xc9, 0xbb, 0x3b, 0x8c, 0x97, 0xba, 0x1e, + 0xfe, 0x19, 0x23, 0xb3, 0xe9, 0x53, 0x7a, 0xc6, 0xe4, 0x8f, 0xe4, 0xbd, 0x3f, 0xda, 0x38, 0xdf, + 0xe4, 0xc4, 0xa8, 0xbb, 0x0d, 0x97, 0x13, 0xdf, 0x38, 0xab, 0x9b, 0x4e, 0x88, 0x2b, 0x8f, 0xc6, + 0xe1, 0xa3, 0x81, 0xcf, 0x46, 0xe8, 0xa0, 0xc2, 0xef, 0x15, 0x50, 0xab, 0xcc, 0xa9, 0xba, 0x4e, + 0x20, 0xf6, 0x94, 0xb1, 0x87, 0x2d, 0xcf, 0x66, 0x6a, 0x0e, 0xe6, 0x2d, 0x9f, 0x98, 0x9c, 0xfa, + 0x39, 0x65, 0x47, 0xd9, 0x4b, 0xeb, 0xe1, 0x52, 0xdd, 0x80, 0x94, 0x10, 0xe1, 0xda, 0xb9, 0xf7, + 0x76, 0x94, 0xbd, 0x59, 0x7d, 0x1e, 0xd7, 0xc7, 0xb6, 0x7a, 0x04, 0x49, 0xb3, 0x41, 0x5b, 0x1e, + 0xcf, 0xcd, 0x06, 0x98, 0xc3, 0xd2, 0xcb, 0xd7, 0xdb, 0x33, 0x7f, 0x7f, 0xbd, 0xfd, 0x4d, 0xc7, + 0xe5, 0xe7, 0xad, 0x5a, 0xd1, 0xa2, 0x8d, 0x92, 0x45, 0x59, 0x83, 0x32, 0xf9, 0x73, 0x97, 0xd9, + 0x17, 0x25, 0x7e, 0xd9, 0x24, 0xac, 0xf8, 0xd4, 0xf5, 0xb8, 0x2e, 0xe1, 0x85, 0x0f, 0x40, 0xeb, + 0xd7, 0x49, 0x27, 0xac, 0x49, 0x3d, 0x46, 0x0a, 0x8f, 0xe1, 0xfd, 0x2a, 0x73, 0x9e, 0x36, 0x6d, + 0xf1, 0xf2, 0x81, 0x6d, 0xfb, 0x84, 0x8d, 0x52, 0x79, 0x0b, 0x80, 0x33, 0x66, 0x34, 0x5b, 0xb5, + 0x0b, 0x72, 0x89, 0x4a, 0xa7, 0xf5, 0x34, 0x67, 0xec, 0x04, 0x09, 0x85, 0x2d, 0xd8, 0x1c, 0x20, + 0x2f, 0xda, 0xee, 0x8f, 0xef, 0xc1, 0x6a, 0x95, 0x39, 0x0f, 0x6c, 0xfb, 0xd8, 0xab, 0xd1, 0x96, + 0x67, 0x9f, 0xfa, 0xa6, 0x75, 0x41, 0xfc, 0xe9, 0x7c, 0xb4, 0x0e, 0xf3, 0xbc, 0x63, 0x9c, 0x9b, + 0xec, 0x5c, 0x38, 0x49, 0x4f, 0xf2, 0xce, 0x17, 0x26, 0x3b, 0x57, 0x0f, 0x21, 0x1d, 0xa4, 0x8b, + 0x11, 0xb8, 0x23, 0x97, 0xd8, 0x51, 0xf6, 0xb2, 0xe5, 0xdb, 0xc5, 0x01, 0xd9, 0xdb, 0xbc, 0x70, + 0x8a, 0x98, 0x57, 0x15, 0xea, 0x7a, 0xa7, 0x97, 0x4d, 0xa2, 0xa7, 0x2c, 0xf9, 0xa4, 0x1e, 0xc0, + 0x1c, 0x26, 0x52, 0x6e, 0x6e, 0x47, 0xd9, 0xcb, 0x94, 0xbf, 0x31, 0x0c, 0x2f, 0xb3, 0xed, 0x24, + 0xf8, 0xd1, 0x05, 0x24, 0x70, 0x52, 0xad, 0x4e, 0xad, 0x0b, 0xa1, 0x5b, 0x52, 0x38, 0x09, 0x29, + 0xa8, 0xde, 0x06, 0xa4, 0x78, 0xc7, 0x70, 0x3d, 0x9b, 0x74, 0x72, 0xf3, 0xc2, 0x24, 0xde, 0x39, + 0x0e, 0x96, 0x85, 0x3c, 0x7c, 0x30, 0xc8, 0x3f, 0x91, 0x03, 0xff, 0xaa, 0xc0, 0x4a, 0x95, 0x39, + 0x3f, 0x3e, 0x77, 0x39, 0xa9, 0xbb, 0x8c, 0x7f, 0xae, 0x57, 0xca, 0xfb, 0x23, 0xbc, 0x77, 0x0b, + 0x16, 0x89, 0x6f, 0x95, 0xf7, 0x0d, 0x53, 0x44, 0x42, 0x46, 0x6c, 0x01, 0x89, 0x61, 0xb4, 0xbb, + 0x5d, 0x3c, 0xdb, 0xeb, 0x62, 0x15, 0x12, 0x9e, 0xd9, 0x10, 0x4e, 0x4c, 0xeb, 0xf8, 0xac, 0xae, + 0x41, 0x92, 0x5d, 0x36, 0x6a, 0xb4, 0x8e, 0xae, 0x49, 0xeb, 0x72, 0xa5, 0x6a, 0x90, 0xb2, 0x89, + 0xe5, 0x36, 0xcc, 0x3a, 0x43, 0x9b, 0x17, 0xf5, 0x68, 0xad, 0x6e, 0x42, 0xda, 0x31, 0x99, 0x38, + 0x69, 0xd2, 0xe6, 0x94, 0x63, 0xb2, 0x47, 0xc1, 0xba, 0x60, 0xc0, 0x46, 0x9f, 0x4d, 0xa1, 0xc5, + 0x81, 0x05, 0xcf, 0x7b, 0x2c, 0x10, 0x16, 0x2e, 0x3c, 0xef, 0xb6, 0x60, 0x0b, 0xc0, 0xb2, 0x22, + 0x9f, 0xca, 0xac, 0x0c, 0x28, 0xc2, 0xab, 0xff, 0x56, 0xe0, 0x86, 0x70, 0xeb, 0x93, 0x16, 0xbf, + 0x7e, 0xde, 0xad, 0xc2, 0x9c, 0x47, 0x3d, 0x8b, 0xa0, 0xb3, 0x12, 0xba, 0x58, 0x74, 0x67, 0x63, + 0xa2, 0x27, 0x1b, 0xbf, 0x9e, 0x4c, 0xfa, 0x3e, 0x6c, 0x0d, 0x34, 0x39, 0x72, 0xec, 0x16, 0x80, + 0xcb, 0x0c, 0x9f, 0x34, 0x68, 0x9b, 0xd8, 0x68, 0x7d, 0x4a, 0x4f, 0xbb, 0x4c, 0x17, 0x84, 0x02, + 0x81, 0x5c, 0x95, 0x39, 0x62, 0xf5, 0xff, 0xf3, 0x5a, 0xa1, 0x00, 0x3b, 0xc3, 0xb6, 0x89, 0x92, + 0xfe, 0xcf, 0x0a, 0x2c, 0x55, 0x99, 0xf3, 0x15, 0xe5, 0xe4, 0xc8, 0x64, 0x27, 0xbe, 0x6b, 0x91, + 0xa9, 0x55, 0x68, 0x06, 0xe8, 0x50, 0x05, 0x5c, 0xa8, 0x37, 0x61, 0xa1, 0xe9, 0xbb, 0xd4, 0x77, + 0xf9, 0xa5, 0x71, 0x46, 0x08, 0x7a, 0x39, 0xa1, 0x67, 0x42, 0xda, 0x43, 0x82, 0x2c, 0x22, 0x0c, + 0x5e, 0xab, 0x51, 0x23, 0x3e, 0x06, 0x38, 0xa1, 0x67, 0x90, 0xf6, 0x18, 0x49, 0x3f, 0x4c, 0xa4, + 0xe6, 0x96, 0x93, 0x85, 0x0d, 0x58, 0x8f, 0x69, 0x1a, 0x59, 0xf1, 0xa7, 0x64, 0x64, 0x45, 0x68, + 0xe8, 0x08, 0x2b, 0x36, 0x01, 0xf3, 0x57, 0xc4, 0x5d, 0x24, 0x74, 0x2a, 0x20, 0x60, 0xd8, 0x3f, + 0x82, 0x35, 0x5a, 0x63, 0xc4, 0x6f, 0x13, 0xdb, 0xa0, 0x52, 0x56, 0xf7, 0x3d, 0xb8, 0x1a, 0xbe, + 0x0d, 0x37, 0x42, 0x54, 0x05, 0xf2, 0xfd, 0x28, 0x99, 0x5d, 0xc4, 0x75, 0xce, 0xb9, 0x34, 0x6b, + 0x33, 0x8e, 0x3e, 0xc4, 0x7c, 0x43, 0x16, 0xf5, 0x13, 0xd0, 0xfa, 0x85, 0x04, 0x47, 0xbb, 0xc5, + 0x88, 0x9d, 0x03, 0x14, 0xb0, 0x1e, 0x17, 0x70, 0x64, 0xb2, 0xa7, 0x8c, 0xd8, 0xea, 0x2f, 0x15, + 0xb8, 0xdd, 0x8f, 0x26, 0x67, 0x67, 0xc4, 0xe2, 0x6e, 0x9b, 0xa0, 0x1c, 0x11, 0xa0, 0x0c, 0x16, + 0xbd, 0xa2, 0x2c, 0x7a, 0xbb, 0x13, 0x14, 0xbd, 0x63, 0x8f, 0xeb, 0x37, 0xe3, 0x1b, 0x7f, 0x1e, + 0x8a, 0x8e, 0xf2, 0xe6, 0x64, 0xbc, 0x06, 0xe2, 0x92, 0x5a, 0x40, 0x53, 0x46, 0x4a, 0xc4, 0xdb, + 0x4b, 0xa5, 0x90, 0x6d, 0x9b, 0xf5, 0x16, 0x31, 0x7c, 0x62, 0x11, 0x37, 0x38, 0x4b, 0x78, 0x2d, + 0x1e, 0x7e, 0x71, 0xc5, 0x8a, 0xfd, 0x9f, 0xd7, 0xdb, 0x37, 0x2e, 0xcd, 0x46, 0xfd, 0xa0, 0xd0, + 0x2b, 0xae, 0xa0, 0x2f, 0x22, 0x41, 0x97, 0x6b, 0xf5, 0x33, 0x48, 0x32, 0x6e, 0xf2, 0x96, 0xb8, + 0x65, 0xb3, 0xe5, 0x3b, 0x43, 0x4b, 0x9b, 0x68, 0xae, 0x24, 0xf0, 0x4b, 0xc4, 0xe8, 0x12, 0xab, + 0xde, 0x86, 0x6c, 0x64, 0x3f, 0x32, 0xca, 0x0b, 0x64, 0x31, 0xa4, 0x56, 0x02, 0xa2, 0x7a, 0x07, + 0xd4, 0x88, 0x2d, 0x28, 0xfc, 0xe2, 0x08, 0xa7, 0xd0, 0x39, 0xcb, 0xe1, 0x9b, 0x53, 0xc6, 0x1e, + 0xe3, 0x1d, 0xd8, 0x53, 0x78, 0xd3, 0x53, 0x15, 0xde, 0xae, 0x23, 0x14, 0xfa, 0x3c, 0x3a, 0x42, + 0xff, 0x9c, 0x83, 0xac, 0x7c, 0x27, 0xeb, 0xe3, 0x88, 0x13, 0x14, 0x94, 0x29, 0xe2, 0xd9, 0xc4, + 0x97, 0xc7, 0x47, 0xae, 0xd4, 0x5d, 0x58, 0x12, 0x4f, 0x46, 0xac, 0xe8, 0x2d, 0x0a, 0x72, 0x45, + 0x5e, 0x16, 0x1a, 0xa4, 0x64, 0x08, 0x7c, 0x79, 0xa1, 0x47, 0xeb, 0xc0, 0x79, 0xe1, 0xb3, 0x74, + 0xde, 0x9c, 0x10, 0x11, 0x52, 0x85, 0xf3, 0xde, 0x35, 0x71, 0xc9, 0x6b, 0x35, 0x71, 0x81, 0x95, + 0x0d, 0xc2, 0x98, 0xe9, 0x08, 0xd7, 0xa7, 0xf5, 0x70, 0x19, 0xdc, 0x4c, 0xae, 0xd7, 0x75, 0x01, + 0xa4, 0xf1, 0x75, 0x46, 0xd2, 0xf0, 0xdc, 0xef, 0xc3, 0x6a, 0xc8, 0xd2, 0x73, 0xda, 0xc5, 0x61, + 0x55, 0xe5, 0xbb, 0xee, 0x43, 0xde, 0x53, 0xad, 0x33, 0xc8, 0x16, 0x55, 0xeb, 0xde, 0x18, 0x2f, + 0x4c, 0xd7, 0x5c, 0x6d, 0x42, 0x9a, 0x77, 0x0c, 0xea, 0xbb, 0x8e, 0xeb, 0xe5, 0x16, 0x85, 0x73, + 0x79, 0xe7, 0x09, 0xae, 0x83, 0x5b, 0xda, 0x64, 0x8c, 0xf0, 0x5c, 0x16, 0x5f, 0x88, 0x85, 0xba, + 0x0d, 0x19, 0xd2, 0x26, 0x1e, 0x97, 0xd5, 0x6e, 0x09, 0xb5, 0x02, 0x24, 0x61, 0xc1, 0x53, 0x7d, + 0xd8, 0xc0, 0x36, 0xdc, 0xa2, 0x75, 0xc3, 0xa2, 0x1e, 0xf7, 0x4d, 0x8b, 0x1b, 0x6d, 0xe2, 0x33, + 0x97, 0x7a, 0xb9, 0x65, 0xd4, 0xf3, 0x7e, 0x71, 0xe4, 0x08, 0x13, 0x94, 0x5e, 0xc4, 0x57, 0x24, + 0xfc, 0x2b, 0x81, 0xd6, 0xd7, 0x9b, 0x83, 0x5f, 0xa8, 0x3f, 0x0d, 0xf2, 0xa0, 0x4d, 0x7c, 0x6e, + 0xd0, 0x26, 0x77, 0xa9, 0xc7, 0x72, 0x2b, 0x58, 0xe3, 0xef, 0x8c, 0xd9, 0x48, 0x47, 0xd0, 0x13, + 0x81, 0x39, 0x4c, 0x04, 0x69, 0x11, 0xe4, 0x4e, 0x17, 0xb1, 0x90, 0x83, 0xb5, 0xde, 0x54, 0x8f, + 0x4e, 0xc1, 0x23, 0x6c, 0x01, 0x1f, 0xd4, 0xa8, 0xcf, 0xbf, 0xe4, 0x2d, 0xeb, 0xa2, 0x52, 0x39, + 0xfd, 0xc9, 0xe8, 0x8e, 0x7d, 0x54, 0x6f, 0xb4, 0x89, 0xcd, 0x57, 0xaf, 0xb4, 0x68, 0xab, 0x36, + 0xb6, 0xeb, 0x3a, 0x39, 0x6b, 0x79, 0x36, 0xb2, 0x10, 0xfb, 0x5a, 0xbb, 0x89, 0x83, 0x13, 0x48, + 0x8b, 0xda, 0x39, 0x51, 0xb1, 0x16, 0x05, 0x55, 0xf6, 0x73, 0xb2, 0x0d, 0xee, 0xdb, 0x37, 0xd2, + 0xeb, 0x85, 0x82, 0x5a, 0x8b, 0x39, 0x43, 0x37, 0x39, 0x79, 0x24, 0x46, 0xb8, 0x87, 0xc1, 0x04, + 0x37, 0x42, 0x3b, 0x0b, 0xd4, 0xfe, 0x89, 0x0f, 0xb5, 0xcc, 0x94, 0x4b, 0xe3, 0x62, 0x16, 0xdb, + 0x46, 0x86, 0x6d, 0xd9, 0x8f, 0xd1, 0x0b, 0xb7, 0xe0, 0xe6, 0x50, 0xdd, 0x22, 0x0b, 0xfe, 0xa5, + 0xe0, 0xa4, 0x24, 0xe7, 0x32, 0x6c, 0x79, 0x2b, 0x2d, 0xc6, 0xa9, 0x7d, 0x79, 0x8d, 0xa1, 0xb1, + 0x08, 0xef, 0x7b, 0xe4, 0x99, 0x61, 0x09, 0x41, 0x31, 0x17, 0xaf, 0x78, 0xe4, 0x99, 0xdc, 0x22, + 0x6c, 0x9b, 0xfb, 0xa6, 0x83, 0xc4, 0x80, 0xe9, 0xe0, 0xdd, 0x25, 0x36, 0x77, 0xbd, 0x49, 0xf4, + 0x33, 0xb8, 0x35, 0xc2, 0xe2, 0xee, 0xbe, 0xb4, 0x2b, 0x83, 0x94, 0x78, 0xbe, 0x36, 0xb0, 0x61, + 0x14, 0xde, 0xed, 0x16, 0x72, 0x62, 0xb6, 0x98, 0xac, 0x71, 0xd3, 0x37, 0x87, 0x81, 0x0c, 0x74, + 0x57, 0x4a, 0x17, 0x8b, 0xc2, 0x31, 0xec, 0x8d, 0xdb, 0x6e, 0x42, 0xcd, 0xcb, 0xff, 0xcd, 0xc2, + 0x6c, 0x95, 0x39, 0xea, 0x6f, 0x14, 0x50, 0x07, 0x8c, 0x22, 0x1f, 0x8d, 0xc9, 0xbf, 0x81, 0xdd, + 0xbc, 0xf6, 0xbd, 0x69, 0x50, 0x91, 0xc6, 0xbf, 0x56, 0x60, 0xa5, 0x7f, 0x18, 0xbf, 0x37, 0x91, + 0xcc, 0x5e, 0x90, 0xf6, 0xc9, 0x14, 0xa0, 0x48, 0x8f, 0xdf, 0x29, 0x70, 0x63, 0xf0, 0xa8, 0xf1, + 0x9d, 0xf1, 0x62, 0x07, 0x02, 0xb5, 0x4f, 0xa7, 0x04, 0x46, 0x3a, 0xb5, 0x61, 0xa1, 0x67, 0xe2, + 0x28, 0x8e, 0x17, 0xd8, 0xcd, 0xaf, 0xdd, 0xbf, 0x1a, 0x7f, 0x7c, 0xdf, 0x68, 0x46, 0x98, 0x70, + 0xdf, 0x90, 0x7f, 0xd2, 0x7d, 0xe3, 0xcd, 0x95, 0xca, 0x20, 0xd3, 0xdd, 0x58, 0xdd, 0x9d, 0x4c, + 0x8c, 0x64, 0xd7, 0xbe, 0x7d, 0x25, 0xf6, 0x68, 0xd3, 0x9f, 0x43, 0x36, 0xf6, 0x2d, 0x63, 0x7f, + 0xbc, 0xa0, 0x5e, 0x84, 0xf6, 0xf1, 0x55, 0x11, 0xd1, 0xee, 0xbf, 0x52, 0x60, 0xb9, 0xef, 0xdb, + 0x57, 0x79, 0xbc, 0xb8, 0x38, 0x46, 0x3b, 0xb8, 0x3a, 0x26, 0x52, 0xe2, 0x17, 0xb0, 0x14, 0xff, + 0x62, 0xf8, 0xe1, 0x78, 0x71, 0x31, 0x88, 0xf6, 0xdd, 0x2b, 0x43, 0xba, 0x63, 0x10, 0x6b, 0x26, + 0x26, 0x88, 0x41, 0x2f, 0x62, 0x92, 0x18, 0x0c, 0x6e, 0x31, 0xf0, 0x0a, 0xea, 0x6f, 0x30, 0xee, + 0x4d, 0x72, 0x7a, 0x63, 0xa0, 0x49, 0xae, 0xa0, 0xa1, 0x2d, 0x85, 0xfa, 0x07, 0x05, 0xd6, 0x86, + 0xf4, 0x13, 0x1f, 0x4f, 0x1a, 0xdd, 0x38, 0x52, 0xfb, 0xc1, 0xb4, 0xc8, 0x48, 0xad, 0x17, 0x0a, + 0xe4, 0x86, 0x36, 0x09, 0x07, 0x13, 0x07, 0xbd, 0x0f, 0xab, 0x1d, 0x4e, 0x8f, 0x8d, 0x94, 0xfb, + 0x8b, 0x02, 0x5b, 0xa3, 0x2b, 0xf1, 0xa7, 0x93, 0x3a, 0x60, 0x88, 0x00, 0xed, 0xe8, 0x9a, 0x02, + 0x42, 0x5d, 0x0f, 0x7f, 0xf4, 0xf2, 0x4d, 0x5e, 0x79, 0xf5, 0x26, 0xaf, 0xfc, 0xe3, 0x4d, 0x5e, + 0xf9, 0xed, 0xdb, 0xfc, 0xcc, 0xab, 0xb7, 0xf9, 0x99, 0xbf, 0xbd, 0xcd, 0xcf, 0xfc, 0xec, 0xc3, + 0xae, 0x46, 0x26, 0xd8, 0xe2, 0x6e, 0xec, 0xb3, 0x7f, 0xa7, 0xe7, 0xbf, 0x21, 0x41, 0x5f, 0x53, + 0x4b, 0xe2, 0x40, 0x70, 0xef, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x41, 0xaf, 0x76, 0x5a, 0x3b, + 0x19, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1588,6 +1845,8 @@ type MsgClient interface { AbortStuckCCTX(ctx context.Context, in *MsgAbortStuckCCTX, opts ...grpc.CallOption) (*MsgAbortStuckCCTXResponse, error) RefundAbortedCCTX(ctx context.Context, in *MsgRefundAbortedCCTX, opts ...grpc.CallOption) (*MsgRefundAbortedCCTXResponse, error) UpdateRateLimiterFlags(ctx context.Context, in *MsgUpdateRateLimiterFlags, opts ...grpc.CallOption) (*MsgUpdateRateLimiterFlagsResponse, error) + MigrateERC20CustodyFunds(ctx context.Context, in *MsgMigrateERC20CustodyFunds, opts ...grpc.CallOption) (*MsgMigrateERC20CustodyFundsResponse, error) + UpdateERC20CustodyPauseStatus(ctx context.Context, in *MsgUpdateERC20CustodyPauseStatus, opts ...grpc.CallOption) (*MsgUpdateERC20CustodyPauseStatusResponse, error) } type msgClient struct { @@ -1706,6 +1965,24 @@ func (c *msgClient) UpdateRateLimiterFlags(ctx context.Context, in *MsgUpdateRat return out, nil } +func (c *msgClient) MigrateERC20CustodyFunds(ctx context.Context, in *MsgMigrateERC20CustodyFunds, opts ...grpc.CallOption) (*MsgMigrateERC20CustodyFundsResponse, error) { + out := new(MsgMigrateERC20CustodyFundsResponse) + err := c.cc.Invoke(ctx, "/zetachain.zetacore.crosschain.Msg/MigrateERC20CustodyFunds", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateERC20CustodyPauseStatus(ctx context.Context, in *MsgUpdateERC20CustodyPauseStatus, opts ...grpc.CallOption) (*MsgUpdateERC20CustodyPauseStatusResponse, error) { + out := new(MsgUpdateERC20CustodyPauseStatusResponse) + err := c.cc.Invoke(ctx, "/zetachain.zetacore.crosschain.Msg/UpdateERC20CustodyPauseStatus", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { AddOutboundTracker(context.Context, *MsgAddOutboundTracker) (*MsgAddOutboundTrackerResponse, error) @@ -1720,6 +1997,8 @@ type MsgServer interface { AbortStuckCCTX(context.Context, *MsgAbortStuckCCTX) (*MsgAbortStuckCCTXResponse, error) RefundAbortedCCTX(context.Context, *MsgRefundAbortedCCTX) (*MsgRefundAbortedCCTXResponse, error) UpdateRateLimiterFlags(context.Context, *MsgUpdateRateLimiterFlags) (*MsgUpdateRateLimiterFlagsResponse, error) + MigrateERC20CustodyFunds(context.Context, *MsgMigrateERC20CustodyFunds) (*MsgMigrateERC20CustodyFundsResponse, error) + UpdateERC20CustodyPauseStatus(context.Context, *MsgUpdateERC20CustodyPauseStatus) (*MsgUpdateERC20CustodyPauseStatusResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -1762,6 +2041,12 @@ func (*UnimplementedMsgServer) RefundAbortedCCTX(ctx context.Context, req *MsgRe func (*UnimplementedMsgServer) UpdateRateLimiterFlags(ctx context.Context, req *MsgUpdateRateLimiterFlags) (*MsgUpdateRateLimiterFlagsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateRateLimiterFlags not implemented") } +func (*UnimplementedMsgServer) MigrateERC20CustodyFunds(ctx context.Context, req *MsgMigrateERC20CustodyFunds) (*MsgMigrateERC20CustodyFundsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method MigrateERC20CustodyFunds not implemented") +} +func (*UnimplementedMsgServer) UpdateERC20CustodyPauseStatus(ctx context.Context, req *MsgUpdateERC20CustodyPauseStatus) (*MsgUpdateERC20CustodyPauseStatusResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateERC20CustodyPauseStatus not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -1983,6 +2268,42 @@ func _Msg_UpdateRateLimiterFlags_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _Msg_MigrateERC20CustodyFunds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgMigrateERC20CustodyFunds) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).MigrateERC20CustodyFunds(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/zetachain.zetacore.crosschain.Msg/MigrateERC20CustodyFunds", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).MigrateERC20CustodyFunds(ctx, req.(*MsgMigrateERC20CustodyFunds)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateERC20CustodyPauseStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateERC20CustodyPauseStatus) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateERC20CustodyPauseStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/zetachain.zetacore.crosschain.Msg/UpdateERC20CustodyPauseStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateERC20CustodyPauseStatus(ctx, req.(*MsgUpdateERC20CustodyPauseStatus)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "zetachain.zetacore.crosschain.Msg", HandlerType: (*MsgServer)(nil), @@ -2035,6 +2356,14 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "UpdateRateLimiterFlags", Handler: _Msg_UpdateRateLimiterFlags_Handler, }, + { + MethodName: "MigrateERC20CustodyFunds", + Handler: _Msg_MigrateERC20CustodyFunds_Handler, + }, + { + MethodName: "UpdateERC20CustodyPauseStatus", + Handler: _Msg_UpdateERC20CustodyPauseStatus_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "zetachain/zetacore/crosschain/tx.proto", @@ -2747,6 +3076,25 @@ func (m *MsgVoteInbound) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size, err := m.RevertOptions.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x8a + if m.ProtocolContractVersion != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.ProtocolContractVersion)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x80 + } if m.EventIndex != 0 { i = encodeVarintTx(dAtA, i, uint64(m.EventIndex)) i-- @@ -3052,56 +3400,220 @@ func (m *MsgUpdateRateLimiterFlagsResponse) MarshalToSizedBuffer(dAtA []byte) (i return len(dAtA) - i, nil } -func encodeVarintTx(dAtA []byte, offset int, v uint64) int { - offset -= sovTx(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *MsgMigrateTssFunds) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Creator) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.ChainId != 0 { - n += 1 + sovTx(uint64(m.ChainId)) +func (m *MsgMigrateERC20CustodyFunds) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - l = m.Amount.Size() - n += 1 + l + sovTx(uint64(l)) - return n + return dAtA[:n], nil } -func (m *MsgMigrateTssFundsResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n +func (m *MsgMigrateERC20CustodyFunds) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgUpdateTssAddress) Size() (n int) { - if m == nil { - return 0 - } +func (m *MsgMigrateERC20CustodyFunds) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.Creator) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - l = len(m.TssPubkey) - if l > 0 { + { + size := m.Amount.Size() + i -= size + if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if len(m.Erc20Address) > 0 { + i -= len(m.Erc20Address) + copy(dAtA[i:], m.Erc20Address) + i = encodeVarintTx(dAtA, i, uint64(len(m.Erc20Address))) + i-- + dAtA[i] = 0x22 + } + if len(m.NewCustodyAddress) > 0 { + i -= len(m.NewCustodyAddress) + copy(dAtA[i:], m.NewCustodyAddress) + i = encodeVarintTx(dAtA, i, uint64(len(m.NewCustodyAddress))) + i-- + dAtA[i] = 0x1a + } + if m.ChainId != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.ChainId)) + i-- + dAtA[i] = 0x10 + } + if len(m.Creator) > 0 { + i -= len(m.Creator) + copy(dAtA[i:], m.Creator) + i = encodeVarintTx(dAtA, i, uint64(len(m.Creator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgMigrateERC20CustodyFundsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgMigrateERC20CustodyFundsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgMigrateERC20CustodyFundsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CctxIndex) > 0 { + i -= len(m.CctxIndex) + copy(dAtA[i:], m.CctxIndex) + i = encodeVarintTx(dAtA, i, uint64(len(m.CctxIndex))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateERC20CustodyPauseStatus) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateERC20CustodyPauseStatus) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateERC20CustodyPauseStatus) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pause { + i-- + if m.Pause { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x18 + } + if m.ChainId != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.ChainId)) + i-- + dAtA[i] = 0x10 + } + if len(m.Creator) > 0 { + i -= len(m.Creator) + copy(dAtA[i:], m.Creator) + i = encodeVarintTx(dAtA, i, uint64(len(m.Creator))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateERC20CustodyPauseStatusResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateERC20CustodyPauseStatusResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateERC20CustodyPauseStatusResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CctxIndex) > 0 { + i -= len(m.CctxIndex) + copy(dAtA[i:], m.CctxIndex) + i = encodeVarintTx(dAtA, i, uint64(len(m.CctxIndex))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgMigrateTssFunds) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ChainId != 0 { + n += 1 + sovTx(uint64(m.ChainId)) + } + l = m.Amount.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgMigrateTssFundsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgUpdateTssAddress) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.TssPubkey) + if l > 0 { n += 1 + l + sovTx(uint64(l)) } return n @@ -3427,6 +3939,11 @@ func (m *MsgVoteInbound) Size() (n int) { if m.EventIndex != 0 { n += 1 + sovTx(uint64(m.EventIndex)) } + if m.ProtocolContractVersion != 0 { + n += 2 + sovTx(uint64(m.ProtocolContractVersion)) + } + l = m.RevertOptions.Size() + n += 2 + l + sovTx(uint64(l)) return n } @@ -3519,6 +4036,77 @@ func (m *MsgUpdateRateLimiterFlagsResponse) Size() (n int) { return n } +func (m *MsgMigrateERC20CustodyFunds) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ChainId != 0 { + n += 1 + sovTx(uint64(m.ChainId)) + } + l = len(m.NewCustodyAddress) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Erc20Address) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgMigrateERC20CustodyFundsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CctxIndex) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgUpdateERC20CustodyPauseStatus) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Creator) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.ChainId != 0 { + n += 1 + sovTx(uint64(m.ChainId)) + } + if m.Pause { + n += 2 + } + return n +} + +func (m *MsgUpdateERC20CustodyPauseStatusResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.CctxIndex) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -5997,17 +6585,69 @@ func (m *MsgVoteInbound) Unmarshal(dAtA []byte) error { break } } - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx + case 16: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolContractVersion", wireType) } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF + m.ProtocolContractVersion = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ProtocolContractVersion |= ProtocolContractVersion(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 17: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RevertOptions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.RevertOptions.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF } iNdEx += skippy } @@ -6593,6 +7233,490 @@ func (m *MsgUpdateRateLimiterFlagsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgMigrateERC20CustodyFunds) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMigrateERC20CustodyFunds: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMigrateERC20CustodyFunds: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Creator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + m.ChainId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ChainId |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NewCustodyAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NewCustodyAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Erc20Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Erc20Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgMigrateERC20CustodyFundsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgMigrateERC20CustodyFundsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgMigrateERC20CustodyFundsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CctxIndex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CctxIndex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateERC20CustodyPauseStatus) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateERC20CustodyPauseStatus: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateERC20CustodyPauseStatus: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Creator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Creator = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ChainId", wireType) + } + m.ChainId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ChainId |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Pause", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Pause = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateERC20CustodyPauseStatusResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateERC20CustodyPauseStatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateERC20CustodyPauseStatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CctxIndex", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CctxIndex = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/emissions/keeper/msg_server_update_params.go b/x/emissions/keeper/msg_server_update_params.go index 8652e4bb20..a91e1683be 100644 --- a/x/emissions/keeper/msg_server_update_params.go +++ b/x/emissions/keeper/msg_server_update_params.go @@ -28,7 +28,7 @@ func (k msgServer) UpdateParams( ctx := sdk.UnwrapSDKContext(goCtx) err := k.SetParams(ctx, msg.Params) if err != nil { - return nil, errors.Wrapf(types.ErrUnableToSetParams, err.Error()) + return nil, errors.Wrap(types.ErrUnableToSetParams, err.Error()) } return &types.MsgUpdateParamsResponse{}, nil diff --git a/x/fungible/keeper/deposits.go b/x/fungible/keeper/deposits.go index 42d1dd0f01..c289cca897 100644 --- a/x/fungible/keeper/deposits.go +++ b/x/fungible/keeper/deposits.go @@ -5,8 +5,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" eth "github.com/ethereum/go-ethereum/common" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" "github.com/zeta-chain/zetacore/pkg/coin" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" @@ -25,72 +26,99 @@ func (k Keeper) DepositCoinsToFungibleModule(ctx sdk.Context, amount *big.Int) e // ZRC20DepositAndCallContract deposits ZRC20 to the EVM account and calls the contract // returns [txResponse, isContractCall, error] -// isContractCall is true if the receiver is a contract and a contract call was made func (k Keeper) ZRC20DepositAndCallContract( ctx sdk.Context, from []byte, to eth.Address, amount *big.Int, senderChainID int64, - data []byte, + message []byte, coinType coin.CoinType, asset string, + protocolContractVersion crosschaintypes.ProtocolContractVersion, ) (*evmtypes.MsgEthereumTxResponse, bool, error) { - var ZRC20Contract eth.Address + // get ZRC20 contract + zrc20Contract, _, err := k.getAndCheckZRC20(ctx, amount, senderChainID, coinType, asset) + if err != nil { + return nil, false, err + } + + // handle the deposit for protocol contract version 2 + if protocolContractVersion == crosschaintypes.ProtocolContractVersion_V2 { + return k.ProcessV2Deposit(ctx, from, senderChainID, zrc20Contract, to, amount, message, coinType) + } + + // check if the receiver is a contract + // if it is, then the hook onCrossChainCall() will be called + // if not, the zrc20 are simply transferred to the receiver + acc := k.evmKeeper.GetAccount(ctx, to) + if acc != nil && acc.IsContract() { + context := systemcontract.ZContext{ + Origin: from, + Sender: eth.Address{}, + ChainID: big.NewInt(senderChainID), + } + res, err := k.DepositZRC20AndCallContract(ctx, context, zrc20Contract, to, amount, message) + return res, true, err + } + + // if the account is a EOC, no contract call can be made with the data + if len(message) > 0 { + return nil, false, types.ErrCallNonContract + } + + res, err := k.DepositZRC20(ctx, zrc20Contract, to, amount) + return res, false, err +} + +// getAndCheckZRC20 returns the ZRC20 contract address and foreign coin for the given chainID and asset +// it also checks if the foreign coin is paused and if the cap is reached +func (k Keeper) getAndCheckZRC20( + ctx sdk.Context, + amount *big.Int, + chainID int64, + coinType coin.CoinType, + asset string, +) (eth.Address, types.ForeignCoins, error) { + var zrc20Contract eth.Address var foreignCoin types.ForeignCoins var found bool // get foreign coin - if coinType == coin.CoinType_Gas { - foreignCoin, found = k.GetGasCoinForForeignCoin(ctx, senderChainID) + // retrieve the gas token of the chain for no asset call + // this simplify the current workflow and allow to pause calls by pausing the gas token + // TODO: refactor this logic and create specific workflow for no asset call + // https://github.com/zeta-chain/node/issues/2627 + if coinType == coin.CoinType_Gas || coinType == coin.CoinType_NoAssetCall { + foreignCoin, found = k.GetGasCoinForForeignCoin(ctx, chainID) if !found { - return nil, false, crosschaintypes.ErrGasCoinNotFound + return eth.Address{}, types.ForeignCoins{}, crosschaintypes.ErrGasCoinNotFound } } else { - foreignCoin, found = k.GetForeignCoinFromAsset(ctx, asset, senderChainID) + foreignCoin, found = k.GetForeignCoinFromAsset(ctx, asset, chainID) if !found { - return nil, false, crosschaintypes.ErrForeignCoinNotFound + return eth.Address{}, types.ForeignCoins{}, crosschaintypes.ErrForeignCoinNotFound } } - ZRC20Contract = eth.HexToAddress(foreignCoin.Zrc20ContractAddress) + zrc20Contract = eth.HexToAddress(foreignCoin.Zrc20ContractAddress) // check if foreign coin is paused if foreignCoin.Paused { - return nil, false, types.ErrPausedZRC20 + return eth.Address{}, types.ForeignCoins{}, types.ErrPausedZRC20 } // check foreign coins cap if it has a cap if !foreignCoin.LiquidityCap.IsNil() && !foreignCoin.LiquidityCap.IsZero() { liquidityCap := foreignCoin.LiquidityCap.BigInt() - totalSupply, err := k.TotalSupplyZRC4(ctx, ZRC20Contract) + totalSupply, err := k.TotalSupplyZRC4(ctx, zrc20Contract) if err != nil { - return nil, false, err + return eth.Address{}, types.ForeignCoins{}, err } newSupply := new(big.Int).Add(totalSupply, amount) if newSupply.Cmp(liquidityCap) > 0 { - return nil, false, types.ErrForeignCoinCapReached + return eth.Address{}, types.ForeignCoins{}, types.ErrForeignCoinCapReached } } - // check if the receiver is a contract - // if it is, then the hook onCrossChainCall() will be called - // if not, the zrc20 are simply transferred to the receiver - acc := k.evmKeeper.GetAccount(ctx, to) - if acc != nil && acc.IsContract() { - context := systemcontract.ZContext{ - Origin: from, - Sender: eth.Address{}, - ChainID: big.NewInt(senderChainID), - } - res, err := k.DepositZRC20AndCallContract(ctx, context, ZRC20Contract, to, amount, data) - return res, true, err - } - - // if the account is a EOC, no contract call can be made with the data - if len(data) > 0 { - return nil, false, types.ErrCallNonContract - } - - res, err := k.DepositZRC20(ctx, ZRC20Contract, to, amount) - return res, false, err + return zrc20Contract, foreignCoin, nil } diff --git a/x/fungible/keeper/deposits_test.go b/x/fungible/keeper/deposits_test.go index e394053edd..2f715bad30 100644 --- a/x/fungible/keeper/deposits_test.go +++ b/x/fungible/keeper/deposits_test.go @@ -44,6 +44,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.NoError(t, err) require.False(t, contractCall) @@ -76,6 +77,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_ERC20, assetAddress, + crosschaintypes.ProtocolContractVersion_V1, ) require.NoError(t, err) require.False(t, contractCall) @@ -108,6 +110,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte("DEADBEEF"), coin.CoinType_ERC20, assetAddress, + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, types.ErrCallNonContract) }) @@ -149,6 +152,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.NoError(t, err) require.False(t, contractCall) @@ -186,6 +190,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, types.ErrPausedZRC20) }) @@ -227,6 +232,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, types.ErrForeignCoinCapReached) }) @@ -253,6 +259,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, crosschaintypes.ErrGasCoinNotFound) }) @@ -279,6 +286,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_ERC20, assetAddress, + crosschaintypes.ProtocolContractVersion_V1, ) require.ErrorIs(t, err, crosschaintypes.ErrForeignCoinNotFound) }) @@ -309,6 +317,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.NoError(t, err) require.True(t, contractCall) @@ -347,6 +356,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { []byte{}, coin.CoinType_Gas, sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V1, ) require.Error(t, err) require.True(t, contractCall) @@ -355,6 +365,39 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { require.NoError(t, err) require.EqualValues(t, int64(0), balance.Int64()) }) + + t.Run("can deposit using V2", func(t *testing.T) { + // setup gas coin + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + chainList := chains.DefaultChainsList() + chain := chainList[0].ChainId + + // deploy the system contracts + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") + + // deposit + to := sample.EthAddress() + _, contractCall, err := k.ZRC20DepositAndCallContract( + ctx, + sample.EthAddress().Bytes(), + to, + big.NewInt(42), + chain, + []byte{}, + coin.CoinType_Gas, + sample.EthAddress().String(), + crosschaintypes.ProtocolContractVersion_V2, + ) + require.NoError(t, err) + require.False(t, contractCall) + + balance, err := k.BalanceOfZRC4(ctx, zrc20, to) + require.NoError(t, err) + require.Equal(t, big.NewInt(42), balance) + }) } func TestKeeper_DepositCoinZeta(t *testing.T) { diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index ed4ba9ec29..a63d4ed617 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -19,19 +19,21 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" - systemcontract "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" - zevmconnectorcontract "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zetaconnectorzevm.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" - "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" - "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/wzeta.sol" + zevmconnectorcontract "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/zetaconnectorzevm.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-core/contracts/uniswapv2factory.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/server/config" "github.com/zeta-chain/zetacore/x/fungible/types" - zetaObserverTypes "github.com/zeta-chain/zetacore/x/observer/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // TODO USE string constant @@ -46,15 +48,15 @@ var ( func (k Keeper) DeployContract( ctx sdk.Context, metadata *bind.MetaData, - ctorArguments ...interface{}, + constructorArguments ...interface{}, ) (common.Address, error) { contractABI, err := metadata.GetAbi() if err != nil { return common.Address{}, cosmoserrors.Wrapf(types.ErrABIGet, "failed to get ABI: %s", err.Error()) } - ctorArgs, err := contractABI.Pack( - "", // function--empty string for constructor - ctorArguments..., // feeToSetter + constructorArgumentsPacked, err := contractABI.Pack( + "", // function--empty string for constructor + constructorArguments..., // feeToSetter ) if err != nil { return common.Address{}, cosmoserrors.Wrapf( @@ -78,9 +80,9 @@ func (k Keeper) DeployContract( ) } - data := make([]byte, len(bin)+len(ctorArgs)) + data := make([]byte, len(bin)+len(constructorArgumentsPacked)) copy(data[:len(bin)], bin) - copy(data[len(bin):], ctorArgs) + copy(data[len(bin):], constructorArgumentsPacked) nonce, err := k.authKeeper.GetSequence(ctx, types.ModuleAddress.Bytes()) if err != nil { @@ -112,7 +114,7 @@ func (k Keeper) DeployZRC20Contract( ) (common.Address, error) { chain, found := chains.GetChainFromChainID(chainID, k.GetAuthorityKeeper().GetAdditionalChainList(ctx)) if !found { - return common.Address{}, cosmoserrors.Wrapf(zetaObserverTypes.ErrSupportedChains, "chain %d not found", chainID) + return common.Address{}, cosmoserrors.Wrapf(observertypes.ErrSupportedChains, "chain %d not found", chainID) } // Check if Contract has already been deployed for Asset @@ -125,7 +127,17 @@ func (k Keeper) DeployZRC20Contract( if !found { return common.Address{}, cosmoserrors.Wrapf(types.ErrSystemContractNotFound, "system contract not found") } - contractAddr, err := k.DeployContract(ctx, zrc20.ZRC20MetaData, + + // deployment fails if gateway is zero address + // if the gateway is not defined in the protocol yet, use the system contract as the gateway + gateway := system.Gateway + if gateway == "" { + gateway = system.SystemContract + } + + contractAddr, err := k.DeployContract( + ctx, + zrc20.ZRC20MetaData, name, // name symbol, // symbol decimals, // decimals @@ -134,6 +146,7 @@ func (k Keeper) DeployZRC20Contract( uint8(coinType), // coinType: 0: Zeta 1: gas 2 ERC20 gasLimit, //gas limit for transfer; 21k for gas asset; around 70k for ERC20 common.HexToAddress(system.SystemContract), + common.HexToAddress(gateway), ) if err != nil { return common.Address{}, cosmoserrors.Wrapf( @@ -562,7 +575,7 @@ func (k Keeper) BalanceOfZRC4( ) (*big.Int, error) { zrc4ABI, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { - return nil, cosmoserrors.Wrapf(types.ErrABIUnpack, err.Error()) + return nil, cosmoserrors.Wrap(types.ErrABIUnpack, err.Error()) } res, err := k.CallEVM(ctx, *zrc4ABI, types.ModuleAddressEVM, contract, BigIntZero, nil, false, false, "balanceOf", account) @@ -572,7 +585,7 @@ func (k Keeper) BalanceOfZRC4( unpacked, err := zrc4ABI.Unpack("balanceOf", res.Ret) if err != nil || len(unpacked) == 0 { - return nil, cosmoserrors.Wrapf(types.ErrABIUnpack, err.Error()) + return nil, cosmoserrors.Wrap(types.ErrABIUnpack, err.Error()) } balance, ok := unpacked[0].(*big.Int) @@ -590,7 +603,7 @@ func (k Keeper) TotalSupplyZRC4( ) (*big.Int, error) { abi, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { - return nil, cosmoserrors.Wrapf(types.ErrABIUnpack, err.Error()) + return nil, cosmoserrors.Wrap(types.ErrABIUnpack, err.Error()) } res, err := k.CallEVM(ctx, *abi, types.ModuleAddressEVM, contract, BigIntZero, nil, false, false, "totalSupply") if err != nil { @@ -599,12 +612,12 @@ func (k Keeper) TotalSupplyZRC4( unpacked, err := abi.Unpack("totalSupply", res.Ret) if err != nil || len(unpacked) == 0 { - return nil, cosmoserrors.Wrapf(types.ErrABIUnpack, err.Error()) + return nil, cosmoserrors.Wrap(types.ErrABIUnpack, err.Error()) } totalSupply, ok := unpacked[0].(*big.Int) if !ok { - return nil, cosmoserrors.Wrapf(types.ErrABIUnpack, "failed to unpack total supply") + return nil, cosmoserrors.Wrap(types.ErrABIUnpack, "failed to unpack total supply") } return totalSupply, nil @@ -617,7 +630,7 @@ func (k Keeper) QueryChainIDFromContract( ) (*big.Int, error) { abi, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { - return nil, cosmoserrors.Wrapf(types.ErrABIUnpack, err.Error()) + return nil, cosmoserrors.Wrap(types.ErrABIUnpack, err.Error()) } res, err := k.CallEVM(ctx, *abi, types.ModuleAddressEVM, contract, BigIntZero, nil, false, false, "CHAIN_ID") if err != nil { @@ -626,12 +639,12 @@ func (k Keeper) QueryChainIDFromContract( unpacked, err := abi.Unpack("CHAIN_ID", res.Ret) if err != nil || len(unpacked) == 0 { - return nil, cosmoserrors.Wrapf(types.ErrABIUnpack, err.Error()) + return nil, cosmoserrors.Wrap(types.ErrABIUnpack, err.Error()) } chainID, ok := unpacked[0].(*big.Int) if !ok { - return nil, cosmoserrors.Wrapf(types.ErrABIUnpack, "failed to unpack chain ID") + return nil, cosmoserrors.Wrap(types.ErrABIUnpack, "failed to unpack chain ID") } return chainID, nil @@ -673,7 +686,7 @@ func (k Keeper) CallEVM( if ok { errMes = fmt.Sprintf("%s, reason: %v", errMes, revertErr.ErrorData()) } - return resp, cosmoserrors.Wrapf(err, errMes) + return resp, cosmoserrors.Wrap(err, errMes) } return resp, nil } diff --git a/x/fungible/keeper/evm_test.go b/x/fungible/keeper/evm_test.go index 447cdf9fcc..f80893d193 100644 --- a/x/fungible/keeper/evm_test.go +++ b/x/fungible/keeper/evm_test.go @@ -2,6 +2,7 @@ package keeper_test import ( "encoding/json" + "fmt" "math/big" "testing" @@ -11,11 +12,16 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" - zrc20 "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/wzeta.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/erc1967proxy.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" + + "github.com/zeta-chain/zetacore/e2e/utils" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/server/config" @@ -54,6 +60,44 @@ func deploySystemContractsWithMockEvmKeeper( return deploySystemContracts(t, ctx, k, mockEVMKeeper) } +// deploy upgradable gateway contract and return its address +func deployGatewayContract( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk types.EVMKeeper, + wzeta, admin common.Address, +) common.Address { + // Deploy the gateway contract + implAddr, err := k.DeployContract(ctx, gatewayzevm.GatewayZEVMMetaData) + require.NoError(t, err) + require.NotEmpty(t, implAddr) + assertContractDeployment(t, evmk, ctx, implAddr) + + // Deploy the proxy contract + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + require.NoError(t, err) + + // Encode the initializer data + initializerData, err := gatewayABI.Pack("initialize", wzeta, admin) + require.NoError(t, err) + + gatewayContract, err := k.DeployContract(ctx, erc1967proxy.ERC1967ProxyMetaData, implAddr, initializerData) + require.NoError(t, err) + require.NotEmpty(t, gatewayContract) + assertContractDeployment(t, evmk, ctx, gatewayContract) + + // store the gateway in the system contract object + sys, found := k.GetSystemContract(ctx) + if !found { + sys = types.SystemContract{} + } + sys.Gateway = gatewayContract.Hex() + k.SetSystemContract(ctx, sys) + + return gatewayContract +} + // deploySystemContracts deploys the system contracts and returns their addresses. func deploySystemContracts( t *testing.T, @@ -88,6 +132,10 @@ func deploySystemContracts( require.NotEmpty(t, systemContract) assertContractDeployment(t, evmk, ctx, systemContract) + // deploy the gateway contract + contract := deployGatewayContract(t, ctx, k, evmk, wzeta, sample.EthAddress()) + require.NotEmpty(t, contract) + return } @@ -292,6 +340,66 @@ func TestKeeper_DeployZRC20Contract(t *testing.T) { require.NotNil(t, newBalance) require.Equal(t, amount.Int64(), newBalance.Int64()) }) + + t.Run("can deploy the zrc20 contract without a gateway address", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + chainID := getValidChainID(t) + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + + systemContract, found := k.GetSystemContract(ctx) + require.True(t, found) + systemContract.Gateway = "" + k.SetSystemContract(ctx, systemContract) + + addr, err := k.DeployZRC20Contract( + ctx, + "foo", + "bar", + 8, + chainID, + coin.CoinType_Gas, + "foobar", + big.NewInt(1000), + ) + require.NoError(t, err) + assertContractDeployment(t, sdkk.EvmKeeper, ctx, addr) + + // check foreign coin + foreignCoins, found := k.GetForeignCoins(ctx, addr.Hex()) + require.True(t, found) + require.Equal(t, "foobar", foreignCoins.Asset) + require.Equal(t, chainID, foreignCoins.ForeignChainId) + require.Equal(t, uint32(8), foreignCoins.Decimals) + require.Equal(t, "foo", foreignCoins.Name) + require.Equal(t, "bar", foreignCoins.Symbol) + require.Equal(t, coin.CoinType_Gas, foreignCoins.CoinType) + require.Equal(t, uint64(1000), foreignCoins.GasLimit) + + // can get the zrc20 data + zrc20Data, err := k.QueryZRC20Data(ctx, addr) + require.NoError(t, err) + require.Equal(t, "foo", zrc20Data.Name) + require.Equal(t, "bar", zrc20Data.Symbol) + require.Equal(t, uint8(8), zrc20Data.Decimals) + + // can deposit tokens + to := sample.EthAddress() + oldBalance, err := k.BalanceOfZRC4(ctx, addr, to) + require.NoError(t, err) + require.NotNil(t, oldBalance) + require.Equal(t, int64(0), oldBalance.Int64()) + + amount := big.NewInt(100) + _, err = k.DepositZRC20(ctx, addr, to, amount) + require.NoError(t, err) + + newBalance, err := k.BalanceOfZRC4(ctx, addr, to) + require.NoError(t, err) + require.NotNil(t, newBalance) + require.Equal(t, amount.Int64(), newBalance.Int64()) + }) } func TestKeeper_DeploySystemContracts(t *testing.T) { @@ -574,8 +682,7 @@ func TestKeeper_CallEVMWithData(t *testing.T) { require.True(t, types.IsContractReverted(res, err)) // check reason is included for revert error - // 0xbfb4ebcf is the hash of "Foo()" - require.ErrorContains(t, err, "reason: 0xbfb4ebcf") + require.ErrorContains(t, err, fmt.Sprintf("reason: %s", utils.ErrHashRevertFoo)) res, err = k.CallEVM( ctx, diff --git a/x/fungible/keeper/gas_coin_and_pool.go b/x/fungible/keeper/gas_coin_and_pool.go index 6d4d71bf92..877fae2edd 100644 --- a/x/fungible/keeper/gas_coin_and_pool.go +++ b/x/fungible/keeper/gas_coin_and_pool.go @@ -6,9 +6,9 @@ import ( cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - systemcontract "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - zrc20 "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" - uniswapv2router02 "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" diff --git a/x/fungible/keeper/gas_coin_and_pool_test.go b/x/fungible/keeper/gas_coin_and_pool_test.go index ac0aeae48d..2fc1929202 100644 --- a/x/fungible/keeper/gas_coin_and_pool_test.go +++ b/x/fungible/keeper/gas_coin_and_pool_test.go @@ -10,10 +10,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + evmkeeper "github.com/zeta-chain/ethermint/x/evm/keeper" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - uniswapv2router02 "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + uniswapv2router02 "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" "github.com/zeta-chain/zetacore/cmd/zetacored/config" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" diff --git a/x/fungible/keeper/gas_price.go b/x/fungible/keeper/gas_price.go index bc28f777f8..88ff0d6704 100644 --- a/x/fungible/keeper/gas_price.go +++ b/x/fungible/keeper/gas_price.go @@ -6,7 +6,7 @@ import ( cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - systemcontract "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" + systemcontract "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" "github.com/zeta-chain/zetacore/x/fungible/types" ) @@ -42,7 +42,7 @@ func (k Keeper) SetGasPrice(ctx sdk.Context, chainid *big.Int, gasPrice *big.Int gasPrice, ) if err != nil { - return 0, cosmoserrors.Wrapf(types.ErrContractCall, err.Error()) + return 0, cosmoserrors.Wrap(types.ErrContractCall, err.Error()) } if res.Failed() { return res.GasUsed, cosmoserrors.Wrapf(types.ErrContractCall, "setGasPrice tx failed") @@ -78,7 +78,7 @@ func (k Keeper) SetGasCoin(ctx sdk.Context, chainid *big.Int, address ethcommon. address, ) if err != nil { - return cosmoserrors.Wrapf(types.ErrContractCall, err.Error()) + return cosmoserrors.Wrap(types.ErrContractCall, err.Error()) } if res.Failed() { return cosmoserrors.Wrapf(types.ErrContractCall, "setGasCoinZRC20 tx failed") @@ -114,7 +114,7 @@ func (k Keeper) SetGasZetaPool(ctx sdk.Context, chainid *big.Int, pool ethcommon pool, ) if err != nil { - return cosmoserrors.Wrapf(types.ErrContractCall, err.Error()) + return cosmoserrors.Wrap(types.ErrContractCall, err.Error()) } if res.Failed() { return cosmoserrors.Wrapf(types.ErrContractCall, "setGasZetaPool tx failed") diff --git a/x/fungible/keeper/gas_price_test.go b/x/fungible/keeper/gas_price_test.go index ae79e105e7..dec3c729e9 100644 --- a/x/fungible/keeper/gas_price_test.go +++ b/x/fungible/keeper/gas_price_test.go @@ -6,7 +6,7 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" diff --git a/x/fungible/keeper/msg_server_deploy_fungible_coin_zrc20.go b/x/fungible/keeper/msg_server_deploy_fungible_coin_zrc20.go index 9461080fbc..2784c59d28 100644 --- a/x/fungible/keeper/msg_server_deploy_fungible_coin_zrc20.go +++ b/x/fungible/keeper/msg_server_deploy_fungible_coin_zrc20.go @@ -64,7 +64,16 @@ func (k msgServer) DeployFungibleCoinZRC20( } } else { // #nosec G115 always in range - address, err = k.DeployZRC20Contract(ctx, msg.Name, msg.Symbol, uint8(msg.Decimals), msg.ForeignChainId, msg.CoinType, msg.ERC20, big.NewInt(msg.GasLimit)) + address, err = k.DeployZRC20Contract( + ctx, + msg.Name, + msg.Symbol, + uint8(msg.Decimals), + msg.ForeignChainId, + msg.CoinType, + msg.ERC20, + big.NewInt(msg.GasLimit), + ) if err != nil { return nil, err } diff --git a/x/fungible/keeper/msg_server_update_system_contract.go b/x/fungible/keeper/msg_server_update_system_contract.go index 5477396eeb..750f0e44bf 100644 --- a/x/fungible/keeper/msg_server_update_system_contract.go +++ b/x/fungible/keeper/msg_server_update_system_contract.go @@ -8,8 +8,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/pkg/coin" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" diff --git a/x/fungible/keeper/msg_server_update_system_contract_test.go b/x/fungible/keeper/msg_server_update_system_contract_test.go index eed991365b..3e43c307db 100644 --- a/x/fungible/keeper/msg_server_update_system_contract_test.go +++ b/x/fungible/keeper/msg_server_update_system_contract_test.go @@ -7,9 +7,10 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - zrc20 "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/pkg/chains" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" diff --git a/x/fungible/keeper/msg_server_update_zrc20_withdraw_fee_test.go b/x/fungible/keeper/msg_server_update_zrc20_withdraw_fee_test.go index b1a6fa420d..7b7dc14b4e 100644 --- a/x/fungible/keeper/msg_server_update_zrc20_withdraw_fee_test.go +++ b/x/fungible/keeper/msg_server_update_zrc20_withdraw_fee_test.go @@ -8,8 +8,9 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" diff --git a/x/fungible/keeper/system_contract.go b/x/fungible/keeper/system_contract.go index 656593238d..03f864afc5 100644 --- a/x/fungible/keeper/system_contract.go +++ b/x/fungible/keeper/system_contract.go @@ -8,10 +8,10 @@ import ( "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/wzeta.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" - "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/systemcontract.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/zevm/wzeta.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/zrc20.sol" "github.com/zeta-chain/zetacore/x/fungible/types" ) diff --git a/x/fungible/keeper/v2_deposits.go b/x/fungible/keeper/v2_deposits.go new file mode 100644 index 0000000000..1f431e8a70 --- /dev/null +++ b/x/fungible/keeper/v2_deposits.go @@ -0,0 +1,104 @@ +package keeper + +import ( + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + "github.com/zeta-chain/protocol-contracts/v2/pkg/systemcontract.sol" + + "github.com/zeta-chain/zetacore/pkg/coin" +) + +// ProcessV2Deposit handles a deposit from an inbound tx with protocol version 2 +// returns [txResponse, isContractCall, error] +// isContractCall is true if the message is non empty +func (k Keeper) ProcessV2Deposit( + ctx sdk.Context, + from []byte, + senderChainID int64, + zrc20Addr ethcommon.Address, + to ethcommon.Address, + amount *big.Int, + message []byte, + coinType coin.CoinType, +) (*evmtypes.MsgEthereumTxResponse, bool, error) { + context := systemcontract.ZContext{ + Origin: from, + Sender: ethcommon.Address{}, + ChainID: big.NewInt(senderChainID), + } + + if len(message) == 0 { + // simple deposit + res, err := k.DepositZRC20(ctx, zrc20Addr, to, amount) + return res, false, err + } else if coinType == coin.CoinType_NoAssetCall { + // simple call + res, err := k.CallExecute(ctx, context, zrc20Addr, amount, to, message) + return res, true, err + } + // deposit and call + res, err := k.CallDepositAndCallZRC20(ctx, context, zrc20Addr, amount, to, message) + return res, true, err +} + +// ProcessV2RevertDeposit handles a revert deposit from an inbound tx with protocol version 2 +func (k Keeper) ProcessV2RevertDeposit( + ctx sdk.Context, + amount *big.Int, + chainID int64, + coinType coin.CoinType, + asset string, + revertAddress ethcommon.Address, + callOnRevert bool, + revertMessage []byte, +) error { + // get the zrc20 contract + zrc20Addr, _, err := k.getAndCheckZRC20( + ctx, + amount, + chainID, + coinType, + asset, + ) + if err != nil { + return err + } + + switch coinType { + case coin.CoinType_NoAssetCall: + + if callOnRevert { + // no asset, call simple revert + _, err := k.CallExecuteRevert(ctx, zrc20Addr, amount, revertAddress, revertMessage) + return err + } else { + // no asset, no call, do nothing + return nil + } + case coin.CoinType_Zeta: + return errors.New("ZETA asset is currently unsupported for revert with V2 protocol contracts") + case coin.CoinType_ERC20, coin.CoinType_Gas: + if callOnRevert { + // revert with a ZRC20 asset + _, err := k.CallDepositAndRevert( + ctx, + zrc20Addr, + amount, + revertAddress, + revertMessage, + ) + return err + } else { + // simply deposit back to the revert address + _, err := k.DepositZRC20(ctx, zrc20Addr, revertAddress, amount) + return err + } + } + + return fmt.Errorf("unsupported coin type for revert %s", coinType) +} diff --git a/x/fungible/keeper/v2_deposits_test.go b/x/fungible/keeper/v2_deposits_test.go new file mode 100644 index 0000000000..acf11bf7ae --- /dev/null +++ b/x/fungible/keeper/v2_deposits_test.go @@ -0,0 +1,153 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/contracts/testdappv2" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + fungiblekeeper "github.com/zeta-chain/zetacore/x/fungible/keeper" + "github.com/zeta-chain/zetacore/x/fungible/types" + "math/big" + "testing" +) + +// deployTestDAppV2 deploys the test dapp v2 contract and returns its address +func deployTestDAppV2(t *testing.T, ctx sdk.Context, k *fungiblekeeper.Keeper, evmk types.EVMKeeper) common.Address { + testDAppV2, err := k.DeployContract(ctx, testdappv2.TestDAppV2MetaData) + require.NoError(t, err) + require.NotEmpty(t, testDAppV2) + assertContractDeployment(t, evmk, ctx, testDAppV2) + + return testDAppV2 +} + +// assertTestDAppV2MessageAndAmount asserts the message and amount of the test dapp v2 contract +func assertTestDAppV2MessageAndAmount( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + contract common.Address, + expectedMessage string, + expectedAmount int64, +) { + testDAppABI, err := testdappv2.TestDAppV2MetaData.GetAbi() + require.NoError(t, err) + + // message + res, err := k.CallEVM( + ctx, + *testDAppABI, + types.ModuleAddressEVM, + contract, + fungiblekeeper.BigIntZero, + nil, + false, + false, + "getCalledWithMessage", + expectedMessage, + ) + require.NoError(t, err) + + unpacked, err := testDAppABI.Unpack("getCalledWithMessage", res.Ret) + require.NoError(t, err) + require.Len(t, unpacked, 1) + found, ok := unpacked[0].(bool) + require.True(t, ok) + require.True(t, found) + + // amount + res, err = k.CallEVM( + ctx, + *testDAppABI, + types.ModuleAddressEVM, + contract, + fungiblekeeper.BigIntZero, + nil, + false, + false, + "getAmountWithMessage", + expectedMessage, + ) + require.NoError(t, err) + + unpacked, err = testDAppABI.Unpack("getAmountWithMessage", res.Ret) + require.NoError(t, err) + require.Len(t, unpacked, 1) + amount, ok := unpacked[0].(*big.Int) + require.True(t, ok) + require.Equal(t, expectedAmount, amount.Int64()) +} + +func TestKeeper_ProcessV2Deposit(t *testing.T) { + t.Run("should process no-call deposit", func(t *testing.T) { + // ARRANGE + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + chainID := chains.DefaultChainsList()[0].ChainId + receiver := sample.EthAddress() + + // deploy the system contracts + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + + // ACT + _, contractCall, err := k.ProcessV2Deposit( + ctx, + sample.EthAddress().Bytes(), + chainID, + zrc20, + receiver, + big.NewInt(42), + []byte{}, + coin.CoinType_Gas, + ) + + // ASSERT + require.NoError(t, err) + require.False(t, contractCall) + + balance, err := k.BalanceOfZRC4(ctx, zrc20, receiver) + require.NoError(t, err) + require.Equal(t, big.NewInt(42), balance) + }) + + t.Run("should process deposit and call", func(t *testing.T) { + // ARRANGE + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + chainID := chains.DefaultChainsList()[0].ChainId + + // deploy test dapp + testDapp := deployTestDAppV2(t, ctx, k, sdkk.EvmKeeper) + + // deploy the system contracts + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + + // ACT + _, contractCall, err := k.ProcessV2Deposit( + ctx, + sample.EthAddress().Bytes(), + chainID, + zrc20, + testDapp, + big.NewInt(82), + []byte("foo"), + coin.CoinType_Gas, + ) + + // ASSERT + require.NoError(t, err) + require.True(t, contractCall) + balance, err := k.BalanceOfZRC4(ctx, zrc20, testDapp) + require.NoError(t, err) + require.Equal(t, big.NewInt(82), balance) + assertTestDAppV2MessageAndAmount(t, ctx, k, testDapp, "foo", 82) + }) +} diff --git a/x/fungible/keeper/v2_evm.go b/x/fungible/keeper/v2_evm.go new file mode 100644 index 0000000000..3015471f1c --- /dev/null +++ b/x/fungible/keeper/v2_evm.go @@ -0,0 +1,216 @@ +package keeper + +import ( + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" + evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayzevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/revert.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/systemcontract.sol" + + "github.com/zeta-chain/zetacore/pkg/crypto" + "github.com/zeta-chain/zetacore/x/fungible/types" +) + +// CallDepositAndCallZRC20 calls the depositAndCall (ZRC20 version) function on the gateway contract +// Callable only by the fungible module account +// returns directly CallEVM() +// function depositAndCall( +// +// zContext calldata context, +// address zrc20, +// uint256 amount, +// address target, +// bytes calldata message +// ) +func (k Keeper) CallDepositAndCallZRC20( + ctx sdk.Context, + context systemcontract.ZContext, + zrc20 common.Address, + amount *big.Int, + target common.Address, + message []byte, +) (*evmtypes.MsgEthereumTxResponse, error) { + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + if err != nil { + return nil, err + } + + systemContract, found := k.GetSystemContract(ctx) + if !found { + return nil, types.ErrSystemContractNotFound + } + gatewayAddr := common.HexToAddress(systemContract.Gateway) + if crypto.IsEmptyAddress(gatewayAddr) { + return nil, types.ErrGatewayContractNotSet + } + + // NOTE: + // depositAndCall: ZETA version for depositAndCall method + // depositAndCall0: ZRC20 version for depositAndCall method + return k.CallEVM( + ctx, + *gatewayABI, + types.ModuleAddressEVM, + gatewayAddr, + BigIntZero, + nil, + true, + false, + "depositAndCall0", + context, + zrc20, + amount, + target, + message, + ) +} + +// CallExecute calls the execute function on the gateway contract +// function execute( +// +// zContext calldata context, +// address zrc20, +// uint256 amount, +// address target, +// bytes calldata message +// +// ) +func (k Keeper) CallExecute( + ctx sdk.Context, + context systemcontract.ZContext, + zrc20 common.Address, + amount *big.Int, + target common.Address, + message []byte, +) (*evmtypes.MsgEthereumTxResponse, error) { + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + if err != nil { + return nil, err + } + + systemContract, found := k.GetSystemContract(ctx) + if !found { + return nil, types.ErrSystemContractNotFound + } + gatewayAddr := common.HexToAddress(systemContract.Gateway) + if crypto.IsEmptyAddress(gatewayAddr) { + return nil, types.ErrGatewayContractNotSet + } + + return k.CallEVM( + ctx, + *gatewayABI, + types.ModuleAddressEVM, + gatewayAddr, + BigIntZero, + nil, + true, + false, + "execute", + context, + zrc20, + amount, + target, + message, + ) +} + +// CallExecuteRevert calls the executeRevert function on the gateway contract +// +// function executeRevert( +// address target, +// RevertContext revertContext, +// ) +func (k Keeper) CallExecuteRevert( + ctx sdk.Context, + zrc20 common.Address, + amount *big.Int, + target common.Address, + message []byte, +) (*evmtypes.MsgEthereumTxResponse, error) { + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + if err != nil { + return nil, err + } + + systemContract, found := k.GetSystemContract(ctx) + if !found { + return nil, types.ErrSystemContractNotFound + } + gatewayAddr := common.HexToAddress(systemContract.Gateway) + if crypto.IsEmptyAddress(gatewayAddr) { + return nil, types.ErrGatewayContractNotSet + } + + return k.CallEVM( + ctx, + *gatewayABI, + types.ModuleAddressEVM, + gatewayAddr, + BigIntZero, + nil, + true, + false, + "executeRevert", + target, + revert.RevertContext{ + Asset: zrc20, + Amount: amount.Uint64(), + RevertMessage: message, + }, + ) +} + +// CallDepositAndRevert calls the depositAndRevert function on the gateway contract +// +//function depositAndRevert( +// address zrc20, +// uint256 amount, +// address target, +// RevertContext revertContext +//) + +func (k Keeper) CallDepositAndRevert( + ctx sdk.Context, + zrc20 common.Address, + amount *big.Int, + target common.Address, + message []byte, +) (*evmtypes.MsgEthereumTxResponse, error) { + gatewayABI, err := gatewayzevm.GatewayZEVMMetaData.GetAbi() + if err != nil { + return nil, err + } + + systemContract, found := k.GetSystemContract(ctx) + if !found { + return nil, types.ErrSystemContractNotFound + } + gatewayAddr := common.HexToAddress(systemContract.Gateway) + if crypto.IsEmptyAddress(gatewayAddr) { + return nil, types.ErrGatewayContractNotSet + } + + return k.CallEVM( + ctx, + *gatewayABI, + types.ModuleAddressEVM, + gatewayAddr, + BigIntZero, + nil, + true, + false, + "depositAndRevert", + zrc20, + amount, + target, + revert.RevertContext{ + Asset: zrc20, + Amount: amount.Uint64(), + RevertMessage: message, + }, + ) +} diff --git a/x/fungible/types/errors.go b/x/fungible/types/errors.go index dad99cfd9b..7e426a3178 100644 --- a/x/fungible/types/errors.go +++ b/x/fungible/types/errors.go @@ -28,4 +28,5 @@ var ( ErrForeignCoinAlreadyExist = cosmoserrors.Register(ModuleName, 1125, "foreign coin already exist") ErrNilGasPrice = cosmoserrors.Register(ModuleName, 1127, "nil gas price") ErrAccountNotFound = cosmoserrors.Register(ModuleName, 1128, "account not found") + ErrGatewayContractNotSet = cosmoserrors.Register(ModuleName, 1129, "gateway contract not set") ) diff --git a/x/observer/client/querytests/chain_nonces.go b/x/observer/client/querytests/chain_nonces.go deleted file mode 100644 index 2eacbc99c8..0000000000 --- a/x/observer/client/querytests/chain_nonces.go +++ /dev/null @@ -1,123 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - "github.com/cosmos/cosmos-sdk/client/flags" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/zeta-chain/zetacore/x/observer/client/cli" - "github.com/zeta-chain/zetacore/x/observer/types" -) - -func (s *CliTestSuite) TestShowChainNonces() { - ctx := s.network.Validators[0].ClientCtx - objs := s.observerState.ChainNonces - common := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - for _, tc := range []struct { - desc string - id int64 - args []string - err error - obj types.ChainNonces - }{ - { - desc: "found", - id: objs[0].ChainId, - args: common, - obj: objs[0], - }, - { - desc: "not found", - id: 1000, - args: common, - err: status.Error(codes.InvalidArgument, "not found"), - }, - } { - tc := tc - s.Run(tc.desc, func() { - chainIDStr := fmt.Sprintf("%d", tc.id) - args := []string{chainIDStr} - args = append(args, tc.args...) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowChainNonces(), args) - if tc.err != nil { - stat, ok := status.FromError(tc.err) - s.Require().True(ok) - s.Require().ErrorIs(stat.Err(), tc.err) - } else { - s.Require().NoError(err) - var resp types.QueryGetChainNoncesResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NotNil(resp.ChainNonces) - s.Require().Equal(tc.obj, resp.ChainNonces) - } - }) - } -} - -func (s *CliTestSuite) TestListChainNonces() { - ctx := s.network.Validators[0].ClientCtx - objs := s.observerState.ChainNonces - request := func(next []byte, offset, limit uint64, total bool) []string { - args := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - if next == nil { - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagOffset, offset)) - } else { - args = append(args, fmt.Sprintf("--%s=%s", flags.FlagPageKey, next)) - } - args = append(args, fmt.Sprintf("--%s=%d", flags.FlagLimit, limit)) - if total { - args = append(args, fmt.Sprintf("--%s", flags.FlagCountTotal)) - } - return args - } - s.Run("ByOffset", func() { - step := 2 - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(nil, uint64(i), uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListChainNonces(), args) - s.Require().NoError(err) - var resp types.QueryAllChainNoncesResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - for j := i; j < len(objs) && j < i+step; j++ { - s.Assert().Equal(objs[j], resp.ChainNonces[j-i]) - } - } - }) - s.Run("ByKey", func() { - step := 2 - var next []byte - for i := 0; i < len(objs); i += step { - // #nosec G115 always in range - args := request(next, 0, uint64(step), false) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListChainNonces(), args) - s.Require().NoError(err) - var resp types.QueryAllChainNoncesResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - for j := i; j < len(objs) && j < i+step; j++ { - s.Assert().Equal(objs[j], resp.ChainNonces[j-i]) - } - next = resp.Pagination.NextKey - } - }) - s.Run("Total", func() { - // #nosec G115 always in range - args := request(nil, 0, uint64(len(objs)), true) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdListChainNonces(), args) - s.Require().NoError(err) - var resp types.QueryAllChainNoncesResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NoError(err) - // #nosec G115 always in range - s.Require().Equal(len(objs), int(resp.Pagination.Total)) - s.Require().Equal(objs, resp.ChainNonces) - }) -} diff --git a/x/observer/client/querytests/cli_test.go b/x/observer/client/querytests/cli_test.go deleted file mode 100644 index 777fc81528..0000000000 --- a/x/observer/client/querytests/cli_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package querytests - -import ( - "testing" - - tmdb "github.com/cometbft/cometbft-db" - "github.com/cosmos/cosmos-sdk/baseapp" - servertypes "github.com/cosmos/cosmos-sdk/server/types" - pruningtypes "github.com/cosmos/cosmos-sdk/store/pruning/types" - simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" - "github.com/cosmos/cosmos-sdk/types/module/testutil" - "github.com/stretchr/testify/suite" - - "github.com/zeta-chain/zetacore/app" - "github.com/zeta-chain/zetacore/testutil/network" -) - -func TestCLIQuerySuite(t *testing.T) { - cfg := network.DefaultConfig(NewTestNetworkFixture) - suite.Run(t, NewCLITestSuite(cfg)) -} - -func NewTestNetworkFixture() network.TestFixture { - encoding := app.MakeEncodingConfig() - appCtr := func(val network.ValidatorI) servertypes.Application { - return app.New( - val.GetCtx().Logger, tmdb.NewMemDB(), nil, true, map[int64]bool{}, val.GetCtx().Config.RootDir, 0, - encoding, - simtestutil.EmptyAppOptions{}, - baseapp.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)), - baseapp.SetMinGasPrices(val.GetAppConfig().MinGasPrices), - baseapp.SetChainID("athens_8888-2"), - ) - } - - return network.TestFixture{ - AppConstructor: appCtr, - GenesisState: app.ModuleBasics.DefaultGenesis(encoding.Codec), - EncodingConfig: testutil.TestEncodingConfig{ - InterfaceRegistry: encoding.InterfaceRegistry, - Codec: encoding.Codec, - TxConfig: encoding.TxConfig, - Amino: encoding.Amino, - }, - } -} diff --git a/x/observer/client/querytests/crosschain_flags.go b/x/observer/client/querytests/crosschain_flags.go deleted file mode 100644 index e9756e750f..0000000000 --- a/x/observer/client/querytests/crosschain_flags.go +++ /dev/null @@ -1,53 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "google.golang.org/grpc/status" - - "github.com/zeta-chain/zetacore/testutil/nullify" - "github.com/zeta-chain/zetacore/x/observer/client/cli" - "github.com/zeta-chain/zetacore/x/observer/types" -) - -func (s *CliTestSuite) TestShowCrosschainFlags() { - ctx := s.network.Validators[0].ClientCtx - obj := s.observerState.CrosschainFlags - common := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - for _, tc := range []struct { - desc string - args []string - err error - obj *types.CrosschainFlags - }{ - { - desc: "get", - args: common, - obj: obj, - }, - } { - s.Run(tc.desc, func() { - var args []string - args = append(args, tc.args...) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowCrosschainFlags(), args) - if tc.err != nil { - stat, ok := status.FromError(tc.err) - s.Require().True(ok) - s.Require().ErrorIs(stat.Err(), tc.err) - } else { - s.Require().NoError(err) - var resp types.QueryGetCrosschainFlagsResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NotNil(resp.CrosschainFlags) - tc := tc - s.Require().Equal(nullify.Fill(&tc.obj), - nullify.Fill(&resp.CrosschainFlags), - ) - } - }) - } -} diff --git a/x/observer/client/querytests/keygen.go b/x/observer/client/querytests/keygen.go deleted file mode 100644 index 904f254a15..0000000000 --- a/x/observer/client/querytests/keygen.go +++ /dev/null @@ -1,50 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "google.golang.org/grpc/status" - - "github.com/zeta-chain/zetacore/x/observer/client/cli" - observerTypes "github.com/zeta-chain/zetacore/x/observer/types" -) - -func (s *CliTestSuite) TestShowKeygen() { - ctx := s.network.Validators[0].ClientCtx - obj := s.observerState.Keygen - common := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - for _, tc := range []struct { - desc string - args []string - err error - obj *observerTypes.Keygen - }{ - { - desc: "get", - args: common, - obj: obj, - }, - } { - tc := tc - s.Run(tc.desc, func() { - var args []string - args = append(args, tc.args...) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowKeygen(), args) - if tc.err != nil { - stat, ok := status.FromError(tc.err) - s.Require().True(ok) - s.Require().ErrorIs(stat.Err(), tc.err) - } else { - s.Require().NoError(err) - var resp observerTypes.QueryGetKeygenResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NotNil(resp.Keygen) - s.Require().Equal(tc.obj, resp.Keygen) - } - }) - } -} diff --git a/x/observer/client/querytests/node_account.go b/x/observer/client/querytests/node_account.go deleted file mode 100644 index 1e72d561b0..0000000000 --- a/x/observer/client/querytests/node_account.go +++ /dev/null @@ -1,59 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - "github.com/zeta-chain/zetacore/x/observer/client/cli" - zetaObserverTypes "github.com/zeta-chain/zetacore/x/observer/types" -) - -func (s *CliTestSuite) TestShowNodeAccount() { - ctx := s.network.Validators[0].ClientCtx - objs := s.observerState.NodeAccountList - common := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - for _, tc := range []struct { - desc string - id string - args []string - err error - obj *zetaObserverTypes.NodeAccount - }{ - { - desc: "found", - id: objs[0].Operator, - args: common, - obj: objs[0], - }, - { - desc: "not found", - id: "not_found", - args: common, - err: status.Error(codes.InvalidArgument, "not found"), - }, - } { - tc := tc - s.Run(tc.desc, func() { - args := []string{tc.id} - args = append(args, tc.args...) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowNodeAccount(), args) - if tc.err != nil { - stat, ok := status.FromError(tc.err) - s.Require().True(ok) - s.Require().ErrorIs(stat.Err(), tc.err) - } else { - s.Require().NoError(err) - var resp zetaObserverTypes.QueryGetNodeAccountResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NotNil(resp.NodeAccount) - s.Require().Equal(tc.obj, resp.NodeAccount) - } - }) - } -} diff --git a/x/observer/client/querytests/tss.go b/x/observer/client/querytests/tss.go deleted file mode 100644 index 72d15d1452..0000000000 --- a/x/observer/client/querytests/tss.go +++ /dev/null @@ -1,51 +0,0 @@ -package querytests - -import ( - "fmt" - - tmcli "github.com/cometbft/cometbft/libs/cli" - clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" - "google.golang.org/grpc/status" - - "github.com/zeta-chain/zetacore/x/observer/client/cli" - "github.com/zeta-chain/zetacore/x/observer/types" -) - -func (s *CliTestSuite) TestShowTSS() { - ctx := s.network.Validators[0].ClientCtx - obj := s.observerState.Tss - common := []string{ - fmt.Sprintf("--%s=json", tmcli.OutputFlag), - } - for _, tc := range []struct { - desc string - args []string - err error - obj *types.TSS - }{ - { - desc: "get", - args: common, - obj: obj, - err: nil, - }, - } { - tc := tc - s.Run(tc.desc, func() { - var args []string - args = append(args, tc.args...) - out, err := clitestutil.ExecTestCLICmd(ctx, cli.CmdShowTSS(), args) - if tc.err != nil { - stat, ok := status.FromError(tc.err) - s.Require().True(ok) - s.Require().ErrorIs(stat.Err(), tc.err) - } else { - s.Require().NoError(err) - var resp types.QueryGetTSSResponse - s.Require().NoError(s.network.Config.Codec.UnmarshalJSON(out.Bytes(), &resp)) - s.Require().NotNil(resp.TSS) - s.Require().Equal(*tc.obj, resp.TSS) - } - }) - } -} diff --git a/x/observer/keeper/msg_server_add_observer.go b/x/observer/keeper/msg_server_add_observer.go index 11f7701dde..2c7368eade 100644 --- a/x/observer/keeper/msg_server_add_observer.go +++ b/x/observer/keeper/msg_server_add_observer.go @@ -52,13 +52,15 @@ func (k msgServer) AddObserver( return &types.MsgAddObserverResponse{}, nil } - k.AddObserverToSet(ctx, msg.ObserverAddress) - observerSet, _ := k.GetObserverSet(ctx) + // Add observer to the observer set and update the observer count + count, err := k.AddObserverToSet(ctx, msg.ObserverAddress) + if err != nil { + return &types.MsgAddObserverResponse{}, err + } - k.SetLastObserverCount(ctx, &types.LastObserverCount{Count: observerSet.LenUint()}) EmitEventAddObserver( ctx, - observerSet.LenUint(), + count, msg.ObserverAddress, granteeAddress.String(), msg.ZetaclientGranteePubkey, diff --git a/x/observer/keeper/msg_server_add_observer_test.go b/x/observer/keeper/msg_server_add_observer_test.go index b26a2fe5d0..183a97c8e8 100644 --- a/x/observer/keeper/msg_server_add_observer_test.go +++ b/x/observer/keeper/msg_server_add_observer_test.go @@ -52,7 +52,37 @@ func TestMsgServer_AddObserver(t *testing.T) { require.Equal(t, &types.MsgAddObserverResponse{}, res) }) - t.Run("should add if add node account only false", func(t *testing.T) { + t.Run("unable to add observer if observer already exists", func(t *testing.T) { + //ARRANGE + k, ctx, _, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMockOptions{ + UseAuthorityMock: true, + }) + authorityMock := keepertest.GetObserverAuthorityMock(t, k) + admin := sample.AccAddress() + observerAddress := sample.AccAddress() + wctx := sdk.WrapSDKContext(ctx) + + _, found := k.GetLastObserverCount(ctx) + require.False(t, found) + srv := keeper.NewMsgServerImpl(*k) + k.SetObserverSet(ctx, types.ObserverSet{ObserverList: []string{observerAddress}}) + + msg := types.MsgAddObserver{ + Creator: admin, + ZetaclientGranteePubkey: sample.PubKeyString(), + AddNodeAccountOnly: false, + ObserverAddress: observerAddress, + } + keepertest.MockCheckAuthorization(&authorityMock.Mock, &msg, nil) + + // ACT + _, err := srv.AddObserver(wctx, &msg) + + // ASSERT + require.ErrorIs(t, err, types.ErrDuplicateObserver) + }) + + t.Run("should add observer if add node account only false", func(t *testing.T) { k, ctx, _, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMockOptions{ UseAuthorityMock: true, }) diff --git a/x/observer/keeper/msg_server_update_observer_test.go b/x/observer/keeper/msg_server_update_observer_test.go index 2a4308590f..66e646eb53 100644 --- a/x/observer/keeper/msg_server_update_observer_test.go +++ b/x/observer/keeper/msg_server_update_observer_test.go @@ -73,6 +73,61 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.Equal(t, newOperatorAddress.String(), acc.Operator) }) + t.Run( + "unable to update a tombstoned observer if the new address already exists in the observer set", + func(t *testing.T) { + //ARRANGE + k, ctx, _, _ := keepertest.ObserverKeeper(t) + srv := keeper.NewMsgServerImpl(*k) + // #nosec G404 test purpose - weak randomness is not an issue here + r := rand.New(rand.NewSource(9)) + // Set validator in the store + validator := sample.Validator(t, r) + validatorNew := sample.Validator(t, r) + validatorNew.Status = stakingtypes.Bonded + k.GetStakingKeeper().SetValidator(ctx, validatorNew) + k.GetStakingKeeper().SetValidator(ctx, validator) + + consAddress, err := validator.GetConsAddr() + require.NoError(t, err) + k.GetSlashingKeeper().SetValidatorSigningInfo(ctx, consAddress, slashingtypes.ValidatorSigningInfo{ + Address: consAddress.String(), + StartHeight: 0, + JailedUntil: ctx.BlockHeader().Time.Add(1000000 * time.Second), + Tombstoned: true, + MissedBlocksCounter: 1, + }) + + accAddressOfValidator, err := types.GetAccAddressFromOperatorAddress(validator.OperatorAddress) + require.NoError(t, err) + + newOperatorAddress, err := types.GetAccAddressFromOperatorAddress(validatorNew.OperatorAddress) + require.NoError(t, err) + + observerList := []string{accAddressOfValidator.String(), newOperatorAddress.String()} + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: observerList, + }) + k.SetNodeAccount(ctx, types.NodeAccount{ + Operator: accAddressOfValidator.String(), + }) + k.SetLastObserverCount(ctx, &types.LastObserverCount{ + Count: uint64(len(observerList)), + }) + + //ACT + _, err = srv.UpdateObserver(sdk.WrapSDKContext(ctx), &types.MsgUpdateObserver{ + Creator: accAddressOfValidator.String(), + OldObserverAddress: accAddressOfValidator.String(), + NewObserverAddress: newOperatorAddress.String(), + UpdateReason: types.ObserverUpdateReason_Tombstoned, + }) + + // ASSERT + require.ErrorContains(t, err, types.ErrDuplicateObserver.Error()) + }, + ) + t.Run("unable to update to a non validator address", func(t *testing.T) { k, ctx, _, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) diff --git a/x/observer/keeper/observer_set.go b/x/observer/keeper/observer_set.go index c8a22e0e0f..4a6a7044f1 100644 --- a/x/observer/keeper/observer_set.go +++ b/x/observer/keeper/observer_set.go @@ -1,6 +1,7 @@ package keeper import ( + "cosmossdk.io/errors" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" @@ -36,23 +37,29 @@ func (k Keeper) IsAddressPartOfObserverSet(ctx sdk.Context, address string) bool return false } -func (k Keeper) AddObserverToSet(ctx sdk.Context, address string) { +// AddObserverToSet adds an observer to the observer set.It makes sure the updated observer set is valid. +// It also sets the observer count and returns the updated length of the observer set. +func (k Keeper) AddObserverToSet(ctx sdk.Context, address string) (uint64, error) { observerSet, found := k.GetObserverSet(ctx) if !found { - k.SetObserverSet(ctx, types.ObserverSet{ - ObserverList: []string{address}, - }) - return - } - for _, addr := range observerSet.ObserverList { - if addr == address { - return + observerSet = types.ObserverSet{ + ObserverList: []string{}, } } + observerSet.ObserverList = append(observerSet.ObserverList, address) + if err := observerSet.Validate(); err != nil { + return 0, err + } + k.SetObserverSet(ctx, observerSet) + newCount := observerSet.LenUint() + k.SetLastObserverCount(ctx, &types.LastObserverCount{Count: newCount}) + + return newCount, nil } +// RemoveObserverFromSet removes an observer from the observer set. func (k Keeper) RemoveObserverFromSet(ctx sdk.Context, address string) { observerSet, found := k.GetObserverSet(ctx) if !found { @@ -67,17 +74,28 @@ func (k Keeper) RemoveObserverFromSet(ctx sdk.Context, address string) { } } +// UpdateObserverAddress updates an observer address in the observer set.It makes sure the updated observer set is valid. func (k Keeper) UpdateObserverAddress(ctx sdk.Context, oldObserverAddress, newObserverAddress string) error { observerSet, found := k.GetObserverSet(ctx) if !found { return types.ErrObserverSetNotFound } + found = false for i, addr := range observerSet.ObserverList { if addr == oldObserverAddress { observerSet.ObserverList[i] = newObserverAddress - k.SetObserverSet(ctx, observerSet) - return nil + found = true + break } } - return types.ErrUpdateObserver + if !found { + return errors.Wrapf(types.ErrObserverNotFound, "observer %s", oldObserverAddress) + } + + err := observerSet.Validate() + if err != nil { + return errors.Wrap(types.ErrUpdateObserver, err.Error()) + } + k.SetObserverSet(ctx, observerSet) + return nil } diff --git a/x/observer/keeper/observer_set_test.go b/x/observer/keeper/observer_set_test.go index 3ae4a99cf2..41366943f4 100644 --- a/x/observer/keeper/observer_set_test.go +++ b/x/observer/keeper/observer_set_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/x/observer/types" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" "github.com/zeta-chain/zetacore/testutil/sample" @@ -35,33 +36,64 @@ func TestKeeper_IsAddressPartOfObserverSet(t *testing.T) { func TestKeeper_AddObserverToSet(t *testing.T) { t.Run("add observer to set", func(t *testing.T) { + // ARRANGE k, ctx, _, _ := keepertest.ObserverKeeper(t) os := sample.ObserverSet(10) k.SetObserverSet(ctx, os) newObserver := sample.AccAddress() - k.AddObserverToSet(ctx, newObserver) + + // ACT + countReturned, err := k.AddObserverToSet(ctx, newObserver) + + // ASSERT + require.NoError(t, err) require.True(t, k.IsAddressPartOfObserverSet(ctx, newObserver)) require.False(t, k.IsAddressPartOfObserverSet(ctx, sample.AccAddress())) osNew, found := k.GetObserverSet(ctx) require.True(t, found) require.Len(t, osNew.ObserverList, len(os.ObserverList)+1) + count, found := k.GetLastObserverCount(ctx) + require.True(t, found) + require.Equal(t, osNew.LenUint(), count.Count) + require.Equal(t, osNew.LenUint(), countReturned) }) t.Run("add observer to set if set doesn't exist", func(t *testing.T) { + // ARRANGE k, ctx, _, _ := keepertest.ObserverKeeper(t) newObserver := sample.AccAddress() - k.AddObserverToSet(ctx, newObserver) + + // ACT + countReturned, err := k.AddObserverToSet(ctx, newObserver) + + // ASSERT + require.NoError(t, err) require.True(t, k.IsAddressPartOfObserverSet(ctx, newObserver)) osNew, found := k.GetObserverSet(ctx) require.True(t, found) require.Len(t, osNew.ObserverList, 1) + count, found := k.GetLastObserverCount(ctx) + require.True(t, found) + require.Equal(t, osNew.LenUint(), count.Count) + require.Equal(t, osNew.LenUint(), countReturned) + }) - // add same address again, len doesn't change - k.AddObserverToSet(ctx, newObserver) + t.Run("cannot add observer to set the address is already part of the set", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := keepertest.ObserverKeeper(t) + newObserver := sample.AccAddress() + _, err := k.AddObserverToSet(ctx, newObserver) + require.NoError(t, err) require.True(t, k.IsAddressPartOfObserverSet(ctx, newObserver)) - osNew, found = k.GetObserverSet(ctx) + osNew, found := k.GetObserverSet(ctx) require.True(t, found) require.Len(t, osNew.ObserverList, 1) + + // ACT + _, err = k.AddObserverToSet(ctx, newObserver) + + // ASSERT + require.ErrorIs(t, err, types.ErrDuplicateObserver) }) } @@ -95,6 +127,33 @@ func TestKeeper_UpdateObserverAddress(t *testing.T) { require.True(t, found) require.Equal(t, newObserverAddress, observerSet.ObserverList[len(observerSet.ObserverList)-1]) }) + t.Run("unable to update observer list observe set not found", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := keepertest.ObserverKeeper(t) + oldObserverAddress := sample.AccAddress() + newObserverAddress := sample.AccAddress() + + // ACT + err := k.UpdateObserverAddress(ctx, oldObserverAddress, newObserverAddress) + + // ASSERT + require.ErrorIs(t, err, types.ErrObserverSetNotFound) + }) + t.Run("unable to update observer list if the new list is not valid", func(t *testing.T) { + // ARRANGE + k, ctx, _, _ := keepertest.ObserverKeeper(t) + oldObserverAddress := sample.AccAddress() + newObserverAddress := sample.AccAddress() + observerSet := sample.ObserverSet(10) + observerSet.ObserverList = append(observerSet.ObserverList, []string{oldObserverAddress, newObserverAddress}...) + k.SetObserverSet(ctx, observerSet) + + // ACT + err := k.UpdateObserverAddress(ctx, oldObserverAddress, newObserverAddress) + + // ASSERT + require.ErrorContains(t, err, types.ErrDuplicateObserver.Error()) + }) t.Run("should error if observer address not found", func(t *testing.T) { k, ctx, _, _ := keepertest.ObserverKeeper(t) oldObserverAddress := sample.AccAddress() @@ -103,7 +162,7 @@ func TestKeeper_UpdateObserverAddress(t *testing.T) { observerSet.ObserverList = append(observerSet.ObserverList, oldObserverAddress) k.SetObserverSet(ctx, observerSet) err := k.UpdateObserverAddress(ctx, sample.AccAddress(), newObserverAddress) - require.Error(t, err) + require.ErrorIs(t, err, types.ErrObserverNotFound) }) t.Run("update observer address long observerList", func(t *testing.T) { k, ctx, _, _ := keepertest.ObserverKeeper(t) diff --git a/x/observer/types/chain_params.go b/x/observer/types/chain_params.go index 5fb41aab2e..d1bc9f1b3b 100644 --- a/x/observer/types/chain_params.go +++ b/x/observer/types/chain_params.go @@ -10,13 +10,10 @@ import ( ethchains "github.com/ethereum/go-ethereum/common" "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/constant" solanacontracts "github.com/zeta-chain/zetacore/pkg/contracts/solana" ) -const ( - zeroAddress = "0x0000000000000000000000000000000000000000" -) - var ( DefaultMinObserverDelegation = sdk.MustNewDecFromStr("1000000000000000000000") DefaultBallotThreshold = sdk.MustNewDecFromStr("0.66") @@ -161,9 +158,9 @@ func GetDefaultEthMainnetChainParams() *ChainParams { return &ChainParams{ ChainId: chains.Ethereum.ChainId, ConfirmationCount: 14, - ZetaTokenContractAddress: zeroAddress, - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, InboundTicker: 12, OutboundTicker: 15, WatchUtxoTicker: 0, @@ -179,9 +176,9 @@ func GetDefaultBscMainnetChainParams() *ChainParams { return &ChainParams{ ChainId: chains.BscMainnet.ChainId, ConfirmationCount: 14, - ZetaTokenContractAddress: zeroAddress, - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, InboundTicker: 5, OutboundTicker: 15, WatchUtxoTicker: 0, @@ -197,9 +194,9 @@ func GetDefaultBtcMainnetChainParams() *ChainParams { return &ChainParams{ ChainId: chains.BitcoinMainnet.ChainId, ConfirmationCount: 2, - ZetaTokenContractAddress: zeroAddress, - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, WatchUtxoTicker: 30, InboundTicker: 120, OutboundTicker: 60, @@ -217,8 +214,8 @@ func GetDefaultGoerliTestnetChainParams() *ChainParams { ConfirmationCount: 6, // This is the actual Zeta token Goerli testnet, we need to specify this address for the integration tests to pass ZetaTokenContractAddress: "0x0000c304d2934c00db1d51995b9f6996affd17c0", - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, InboundTicker: 12, OutboundTicker: 15, WatchUtxoTicker: 0, @@ -234,9 +231,9 @@ func GetDefaultBscTestnetChainParams() *ChainParams { return &ChainParams{ ChainId: chains.BscTestnet.ChainId, ConfirmationCount: 6, - ZetaTokenContractAddress: zeroAddress, - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, InboundTicker: 5, OutboundTicker: 15, WatchUtxoTicker: 0, @@ -252,9 +249,9 @@ func GetDefaultMumbaiTestnetChainParams() *ChainParams { return &ChainParams{ ChainId: chains.Mumbai.ChainId, ConfirmationCount: 12, - ZetaTokenContractAddress: zeroAddress, - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, InboundTicker: 2, OutboundTicker: 15, WatchUtxoTicker: 0, @@ -270,9 +267,9 @@ func GetDefaultBtcTestnetChainParams() *ChainParams { return &ChainParams{ ChainId: chains.BitcoinTestnet.ChainId, ConfirmationCount: 2, - ZetaTokenContractAddress: zeroAddress, - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, WatchUtxoTicker: 30, InboundTicker: 120, OutboundTicker: 12, @@ -288,9 +285,9 @@ func GetDefaultBtcRegtestChainParams() *ChainParams { return &ChainParams{ ChainId: chains.BitcoinRegtest.ChainId, ConfirmationCount: 1, - ZetaTokenContractAddress: zeroAddress, - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, GasPriceTicker: 5, WatchUtxoTicker: 1, InboundTicker: 1, @@ -306,9 +303,9 @@ func GetDefaultSolanaLocalnetChainParams() *ChainParams { return &ChainParams{ ChainId: chains.SolanaLocalnet.ChainId, ConfirmationCount: 32, - ZetaTokenContractAddress: zeroAddress, - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, GasPriceTicker: 5, WatchUtxoTicker: 0, InboundTicker: 2, @@ -337,15 +334,16 @@ func GetDefaultGoerliLocalnetChainParams() *ChainParams { BallotThreshold: DefaultBallotThreshold, MinObserverDelegation: DefaultMinObserverDelegation, IsSupported: false, + GatewayAddress: "0xF0deebCB0E9C829519C4baa794c5445171973826", } } func GetDefaultZetaPrivnetChainParams() *ChainParams { return &ChainParams{ ChainId: chains.ZetaChainPrivnet.ChainId, ConfirmationCount: 1, - ZetaTokenContractAddress: zeroAddress, - ConnectorContractAddress: zeroAddress, - Erc20CustodyContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, + ConnectorContractAddress: constant.EVMZeroAddress, + Erc20CustodyContractAddress: constant.EVMZeroAddress, InboundTicker: 2, OutboundTicker: 2, WatchUtxoTicker: 0, diff --git a/x/observer/types/chain_params_test.go b/x/observer/types/chain_params_test.go index dff1311a9c..64c27d5757 100644 --- a/x/observer/types/chain_params_test.go +++ b/x/observer/types/chain_params_test.go @@ -68,6 +68,7 @@ func (s *UpdateChainParamsSuite) SetupTest() { BallotThreshold: types.DefaultBallotThreshold, MinObserverDelegation: types.DefaultMinObserverDelegation, IsSupported: false, + GatewayAddress: "0xF0deebCB0E9C829519C4baa794c5445171973826", } s.btcParams = &types.ChainParams{ ConfirmationCount: 1, diff --git a/x/observer/types/errors.go b/x/observer/types/errors.go index 6485e613ed..218521f242 100644 --- a/x/observer/types/errors.go +++ b/x/observer/types/errors.go @@ -46,7 +46,10 @@ var ( ErrObserverSetNotFound = errorsmod.Register(ModuleName, 1130, "observer set not found") ErrTssNotFound = errorsmod.Register(ModuleName, 1131, "tss not found") - ErrInboundDisabled = errorsmod.Register(ModuleName, 1132, "inbound tx processing is disabled") - ErrInvalidZetaCoinTypes = errorsmod.Register(ModuleName, 1133, "invalid zeta coin types") - ErrNotObserver = errorsmod.Register(ModuleName, 1134, "sender is not an observer") + ErrInboundDisabled = errorsmod.Register(ModuleName, 1132, "inbound tx processing is disabled") + ErrInvalidZetaCoinTypes = errorsmod.Register(ModuleName, 1133, "invalid zeta coin types") + ErrNotObserver = errorsmod.Register(ModuleName, 1134, "sender is not an observer") + ErrDuplicateObserver = errorsmod.Register(ModuleName, 1135, "observer already exists") + ErrObserverNotFound = errorsmod.Register(ModuleName, 1136, "observer not found") + ErrInvalidObserverAddress = errorsmod.Register(ModuleName, 1137, "invalid observer address") ) diff --git a/x/observer/types/message_update_chain_params.go b/x/observer/types/message_update_chain_params.go index 52bd399f02..e9db71920b 100644 --- a/x/observer/types/message_update_chain_params.go +++ b/x/observer/types/message_update_chain_params.go @@ -45,7 +45,7 @@ func (msg *MsgUpdateChainParams) ValidateBasic() error { } if err := ValidateChainParams(msg.ChainParams); err != nil { - return cosmoserrors.Wrapf(ErrInvalidChainParams, err.Error()) + return cosmoserrors.Wrap(ErrInvalidChainParams, err.Error()) } return nil diff --git a/x/observer/types/message_vote_block_header.go b/x/observer/types/message_vote_block_header.go index ba52975251..383ed6464c 100644 --- a/x/observer/types/message_vote_block_header.go +++ b/x/observer/types/message_vote_block_header.go @@ -55,7 +55,7 @@ func (msg *MsgVoteBlockHeader) GetSignBytes() []byte { func (msg *MsgVoteBlockHeader) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, err.Error()) + return cosmoserrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) } if len(msg.BlockHash) != 32 { diff --git a/x/observer/types/observer_set.go b/x/observer/types/observer_set.go index ffa2d1c05a..db9473f187 100644 --- a/x/observer/types/observer_set.go +++ b/x/observer/types/observer_set.go @@ -1,6 +1,7 @@ package types import ( + "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/pkg/chains" @@ -14,13 +15,23 @@ func (m *ObserverSet) LenUint() uint64 { return uint64(len(m.ObserverList)) } -// Validate observer mapper contains an existing chain +// Validate observer set verifies that the observer set is valid +// - All observer addresses are valid +// - No duplicate observer addresses func (m *ObserverSet) Validate() error { + observers := make(map[string]struct{}) for _, observerAddress := range m.ObserverList { + // Check for valid observer addresses _, err := sdk.AccAddressFromBech32(observerAddress) if err != nil { - return err + return errors.Wrapf(ErrInvalidObserverAddress, "observer %s err %s", observerAddress, err.Error()) } + // Check for duplicates + if _, ok := observers[observerAddress]; ok { + return errors.Wrapf(ErrDuplicateObserver, "observer %s", observerAddress) + } + + observers[observerAddress] = struct{}{} } return nil } diff --git a/x/observer/types/observer_set_test.go b/x/observer/types/observer_set_test.go index 69a9a19f96..8344757b18 100644 --- a/x/observer/types/observer_set_test.go +++ b/x/observer/types/observer_set_test.go @@ -10,17 +10,39 @@ import ( "github.com/zeta-chain/zetacore/x/observer/types" ) -func TestObserverSet(t *testing.T) { - observerSet := sample.ObserverSet(4) +func TestObserverSet_Validate(t *testing.T) { + observer1Address := sample.AccAddress() + tt := []struct { + name string + observer types.ObserverSet + wantErr require.ErrorAssertionFunc + }{ + { + name: "observer set with duplicate observer", + observer: types.ObserverSet{ObserverList: []string{observer1Address, observer1Address}}, + wantErr: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorIs(t, err, types.ErrDuplicateObserver) + }, + }, + { + name: "observer set with invalid observer", + observer: types.ObserverSet{ObserverList: []string{"invalid"}}, + wantErr: func(t require.TestingT, err error, i ...interface{}) { + require.ErrorContains(t, err, "decoding bech32 failed") + }, + }, + { + name: "observer set with valid observer", + observer: types.ObserverSet{ObserverList: []string{observer1Address}}, + wantErr: require.NoError, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + tc.wantErr(t, tc.observer.Validate()) + }) - require.Equal(t, int(4), observerSet.Len()) - require.Equal(t, uint64(4), observerSet.LenUint()) - err := observerSet.Validate() - require.NoError(t, err) - - observerSet.ObserverList[0] = "invalid" - err = observerSet.Validate() - require.Error(t, err) + } } func TestCheckReceiveStatus(t *testing.T) { diff --git a/zetaclient/chains/base/observer.go b/zetaclient/chains/base/observer.go index 6021b9219e..cf5491366b 100644 --- a/zetaclient/chains/base/observer.go +++ b/zetaclient/chains/base/observer.go @@ -17,6 +17,7 @@ import ( observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/db" + "github.com/zeta-chain/zetacore/zetaclient/logs" "github.com/zeta-chain/zetacore/zetaclient/metrics" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "github.com/zeta-chain/zetacore/zetaclient/zetacore" @@ -301,13 +302,13 @@ func (ob *Observer) Logger() *ObserverLogger { // WithLogger attaches a new logger to the observer. func (ob *Observer) WithLogger(logger Logger) *Observer { - chainLogger := logger.Std.With().Int64("chain", ob.chain.ChainId).Logger() + chainLogger := logger.Std.With().Int64(logs.FieldChain, ob.chain.ChainId).Logger() ob.logger = ObserverLogger{ Chain: chainLogger, - Inbound: chainLogger.With().Str("module", "inbound").Logger(), - Outbound: chainLogger.With().Str("module", "outbound").Logger(), - GasPrice: chainLogger.With().Str("module", "gasprice").Logger(), - Headers: chainLogger.With().Str("module", "headers").Logger(), + Inbound: chainLogger.With().Str(logs.FieldModule, logs.ModNameInbound).Logger(), + Outbound: chainLogger.With().Str(logs.FieldModule, logs.ModNameOutbound).Logger(), + GasPrice: chainLogger.With().Str(logs.FieldModule, logs.ModNameGasPrice).Logger(), + Headers: chainLogger.With().Str(logs.FieldModule, logs.ModNameHeaders).Logger(), Compliance: logger.Compliance, } return ob diff --git a/zetaclient/chains/base/observer_test.go b/zetaclient/chains/base/observer_test.go index 50148a551b..009d9a53cf 100644 --- a/zetaclient/chains/base/observer_test.go +++ b/zetaclient/chains/base/observer_test.go @@ -54,7 +54,7 @@ func TestNewObserver(t *testing.T) { // constructor parameters chain := chains.Ethereum chainParams := *sample.ChainParams(chain.ChainId) - appContext := zctx.New(config.New(false), zerolog.Nop()) + appContext := zctx.New(config.New(false), nil, zerolog.Nop()) zetacoreClient := mocks.NewZetacoreClient(t) tss := mocks.NewTSSMainnet() blockCacheSize := base.DefaultBlockCacheSize diff --git a/zetaclient/chains/base/signer.go b/zetaclient/chains/base/signer.go index 6618c338de..781288b513 100644 --- a/zetaclient/chains/base/signer.go +++ b/zetaclient/chains/base/signer.go @@ -5,6 +5,7 @@ import ( "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/logs" "github.com/zeta-chain/zetacore/zetaclient/metrics" ) @@ -38,7 +39,10 @@ func NewSigner(chain chains.Chain, tss interfaces.TSSSigner, ts *metrics.Telemet tss: tss, ts: ts, logger: Logger{ - Std: logger.Std.With().Int64("chain", chain.ChainId).Str("module", "signer").Logger(), + Std: logger.Std.With(). + Int64(logs.FieldChain, chain.ChainId). + Str(logs.FieldModule, "signer"). + Logger(), Compliance: logger.Compliance, }, outboundBeingReported: make(map[string]bool), diff --git a/zetaclient/chains/bitcoin/signer/signer.go b/zetaclient/chains/bitcoin/signer/signer.go index 7e701e523d..b49278d25c 100644 --- a/zetaclient/chains/bitcoin/signer/signer.go +++ b/zetaclient/chains/bitcoin/signer/signer.go @@ -205,7 +205,7 @@ func (signer *Signer) SignWithdrawTx( signer.Logger(). Std.Error(). Err(err). - Msgf("SignWithdrawTx: FetchUTXOs error: nonce %d chain %d", nonce, chain.ChainId) + Msgf("SignGasWithdraw: FetchUTXOs error: nonce %d chain %d", nonce, chain.ChainId) } // select N UTXOs to cover the total expense @@ -416,7 +416,7 @@ func (signer *Signer) TryProcessOutbound( true, chain.ChainId, cctx.Index, cctx.InboundParams.Sender, params.Receiver, "BTC") amount = 0.0 // zero out the amount to cancel the tx } - logger.Info().Msgf("SignWithdrawTx: to %s, value %d sats", to.EncodeAddress(), params.Amount.Uint64()) + logger.Info().Msgf("SignGasWithdraw: to %s, value %d sats", to.EncodeAddress(), params.Amount.Uint64()) // sign withdraw tx tx, err := signer.SignWithdrawTx( @@ -432,7 +432,9 @@ func (signer *Signer) TryProcessOutbound( cancelTx, ) if err != nil { - logger.Warn().Err(err).Msgf("SignOutbound error: nonce %d chain %d", outboundTssNonce, params.ReceiverChainId) + logger.Warn(). + Err(err). + Msgf("SignConnectorOnReceive error: nonce %d chain %d", outboundTssNonce, params.ReceiverChainId) return } logger.Info(). diff --git a/zetaclient/chains/evm/cctx.go b/zetaclient/chains/evm/cctx.go new file mode 100644 index 0000000000..df8515e175 --- /dev/null +++ b/zetaclient/chains/evm/cctx.go @@ -0,0 +1,84 @@ +package evm + +import ( + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/x/crosschain/types" +) + +// OutboundType enumerate the different types of outbound transactions +// NOTE: only used for v2 protocol contracts and currently excludes ZETA withdraws +type OutboundType int + +const ( + // OutboundTypeUnknown is an unknown outbound transaction + OutboundTypeUnknown OutboundType = iota + + // OutboundTypeGasWithdraw is a gas withdraw transaction + OutboundTypeGasWithdraw + + // OutboundTypeERC20Withdraw is an ERC20 withdraw transaction + OutboundTypeERC20Withdraw + + // OutboundTypeGasWithdrawAndCall is a gas withdraw and call transaction + OutboundTypeGasWithdrawAndCall + + // OutboundTypeERC20WithdrawAndCall is an ERC20 withdraw and call transaction + OutboundTypeERC20WithdrawAndCall + + // OutboundTypeCall is a no-asset call transaction + OutboundTypeCall + + // OutboundTypeGasWithdrawRevert is a gas withdraw revert + OutboundTypeGasWithdrawRevert + + // OutboundTypeGasWithdrawRevertAndCallOnRevert is a gas withdraw revert and call on revert + OutboundTypeGasWithdrawRevertAndCallOnRevert + + // OutboundTypeERC20WithdrawRevert is an ERC20 withdraw revert + OutboundTypeERC20WithdrawRevert + + // OutboundTypeERC20WithdrawRevertAndCallOnRevert is an ERC20 withdraw revert and call on revert + OutboundTypeERC20WithdrawRevertAndCallOnRevert +) + +// ParseOutboundTypeFromCCTX returns the outbound type from the CCTX +func ParseOutboundTypeFromCCTX(cctx types.CrossChainTx) OutboundType { + switch cctx.InboundParams.CoinType { + case coin.CoinType_Gas: + switch cctx.CctxStatus.Status { + case types.CctxStatus_PendingOutbound: + if len(cctx.RelayedMessage) == 0 { + return OutboundTypeGasWithdraw + } else { + return OutboundTypeGasWithdrawAndCall + } + case types.CctxStatus_PendingRevert: + if cctx.RevertOptions.CallOnRevert { + return OutboundTypeGasWithdrawRevertAndCallOnRevert + } else { + return OutboundTypeGasWithdrawRevert + } + } + case coin.CoinType_ERC20: + switch cctx.CctxStatus.Status { + case types.CctxStatus_PendingOutbound: + if len(cctx.RelayedMessage) == 0 { + return OutboundTypeERC20Withdraw + } else { + return OutboundTypeERC20WithdrawAndCall + } + case types.CctxStatus_PendingRevert: + if cctx.RevertOptions.CallOnRevert { + return OutboundTypeERC20WithdrawRevertAndCallOnRevert + } else { + return OutboundTypeERC20WithdrawRevert + } + } + case coin.CoinType_NoAssetCall: + if cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound { + return OutboundTypeCall + } + } + + return OutboundTypeUnknown +} diff --git a/zetaclient/chains/evm/constant.go b/zetaclient/chains/evm/constant.go index b754d57f30..65398b3013 100644 --- a/zetaclient/chains/evm/constant.go +++ b/zetaclient/chains/evm/constant.go @@ -3,12 +3,13 @@ package evm import "time" const ( - // ZetaBlockTime is the block time of the Zeta network - ZetaBlockTime = 6500 * time.Millisecond - // OutboundInclusionTimeout is the timeout for waiting for an outbound to be included in a block OutboundInclusionTimeout = 20 * time.Minute + // ReorgProtectBlockCount is confirmations count to protect against reorg + // Short 1~2 block reorgs could happen often on Ethereum due to network congestion or block production race conditions + ReorgProtectBlockCount = 2 + // OutboundTrackerReportTimeout is the timeout for waiting for an outbound tracker report OutboundTrackerReportTimeout = 10 * time.Minute @@ -39,4 +40,34 @@ const ( // [signature, asset] // https://github.com/zeta-chain/protocol-contracts/blob/d65814debf17648a6c67d757ba03646415842790/contracts/evm/ERC20Custody.sol#L42 TopicsDeposited = 2 + + // V2 contracts + + // TopicsGatewayDeposit is the number of topics for a gateway deposit event + // [signature, sender, receiver] + TopicsGatewayDeposit = 3 + + // TopicsGatewayCall is the number of topics for a gateway call event + // [signature, sender, receiver] + TopicsGatewayCall = 3 + + // TopicsGatewayExecuted is the number of topics for a gateway executed event + // [signature, destination] + TopicsGatewayExecuted = 2 + + // TopicsGatewayExecutedWithERC20 is the number of topics for a gateway executed with ERC20 event + // [signature, token, destination] + TopicsGatewayExecutedWithERC20 = 3 + + // TopicsGatewayReverted is the number of topics for a reverted event + // [signature, destination] + TopicsGatewayReverted = 3 + + // TopicsERC20CustodyWithdraw is the number of topics for an ERC20 custody withdraw event + // [signature, recipient, asset] + TopicsERC20CustodyWithdraw = 3 + + // TopicsERC20CustodyWithdrawAndCall is the number of topics for an ERC20 custody withdraw and call event + // [signature, recipient, asset] + TopicsERC20CustodyWithdrawAndCall = 3 ) diff --git a/zetaclient/chains/evm/observer/inbound.go b/zetaclient/chains/evm/observer/inbound.go index abf21e7e5b..5d72d0a6e7 100644 --- a/zetaclient/chains/evm/observer/inbound.go +++ b/zetaclient/chains/evm/observer/inbound.go @@ -17,8 +17,8 @@ import ( "github.com/onrik/ethrpc" "github.com/pkg/errors" "github.com/rs/zerolog" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.non-eth.sol" "github.com/zeta-chain/zetacore/pkg/bg" "github.com/zeta-chain/zetacore/pkg/chains" @@ -223,6 +223,22 @@ func (ob *Observer) ObserveInbound(ctx context.Context, sampledLogger zerolog.Lo return errors.Wrap(err, "unable to observe TSSReceive") } + // query the gateway logs + // TODO: refactor in a more declarative design. Example: storing the list of contract and events to listen in an array + // https://github.com/zeta-chain/node/issues/2493 + lastScannedGatewayDeposit, err := ob.ObserveGatewayDeposit(ctx, startBlock, toBlock) + if err != nil { + ob.Logger().Inbound.Error(). + Err(err). + Msgf("ObserveInbound: error observing deposit events from Gateway contract") + } + lastScannedGatewayCall, err := ob.ObserveGatewayCall(ctx, startBlock, toBlock) + if err != nil { + ob.Logger().Inbound.Error(). + Err(err). + Msgf("ObserveInbound: error observing call events from Gateway contract") + } + // note: using lowest height for all 3 events is not perfect, but it's simple and good enough lastScannedLowest := lastScannedZetaSent if lastScannedDeposited < lastScannedLowest { @@ -231,6 +247,12 @@ func (ob *Observer) ObserveInbound(ctx context.Context, sampledLogger zerolog.Lo if lastScannedTssRecvd < lastScannedLowest { lastScannedLowest = lastScannedTssRecvd } + if lastScannedGatewayDeposit < lastScannedLowest { + lastScannedLowest = lastScannedGatewayDeposit + } + if lastScannedGatewayCall < lastScannedLowest { + lastScannedLowest = lastScannedGatewayCall + } // update last scanned block height for all 3 events (ZetaSent, Deposited, TssRecvd), ignore db error if lastScannedLowest > lastScanned { diff --git a/zetaclient/chains/evm/observer/inbound_test.go b/zetaclient/chains/evm/observer/inbound_test.go index 231a3ae6c7..26290fc6c0 100644 --- a/zetaclient/chains/evm/observer/inbound_test.go +++ b/zetaclient/chains/evm/observer/inbound_test.go @@ -501,7 +501,7 @@ func Test_ObserveTSSReceiveInBlock(t *testing.T) { func makeAppContext(t *testing.T) (context.Context, *zctx.AppContext) { var ( - app = zctx.New(config.New(false), zerolog.New(zerolog.NewTestWriter(t))) + app = zctx.New(config.New(false), nil, zerolog.New(zerolog.NewTestWriter(t))) ctx = context.Background() ) diff --git a/zetaclient/chains/evm/observer/observer.go b/zetaclient/chains/evm/observer/observer.go index 4959b13b9a..6dd1234018 100644 --- a/zetaclient/chains/evm/observer/observer.go +++ b/zetaclient/chains/evm/observer/observer.go @@ -13,10 +13,12 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/onrik/ethrpc" "github.com/pkg/errors" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zeta.non-eth.sol" - zetaconnectoreth "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.eth.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zeta.non-eth.sol" + zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.non-eth.sol" + erc20custodyv2 "github.com/zeta-chain/protocol-contracts/v2/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" "github.com/zeta-chain/zetacore/pkg/bg" observertypes "github.com/zeta-chain/zetacore/x/observer/types" @@ -43,9 +45,6 @@ type Observer struct { // evmJSONRPC is the EVM JSON RPC client for the observed chain evmJSONRPC interfaces.EVMJSONRPCClient - // outboundPendingTransactions is the map to index pending transactions by hash - outboundPendingTransactions map[string]*ethtypes.Transaction - // outboundConfirmedReceipts is the map to index confirmed receipts by hash outboundConfirmedReceipts map[string]*ethtypes.Receipt @@ -92,7 +91,6 @@ func NewObserver( Observer: *baseObserver, evmClient: evmClient, evmJSONRPC: ethrpc.NewEthRPC(evmCfg.Endpoint), - outboundPendingTransactions: make(map[string]*ethtypes.Transaction), outboundConfirmedReceipts: make(map[string]*ethtypes.Receipt), outboundConfirmedTransactions: make(map[string]*ethtypes.Transaction), priorityFeeConfig: priorityFeeConfig{}, @@ -153,6 +151,23 @@ func (ob *Observer) GetERC20CustodyContract() (ethcommon.Address, *erc20custody. return addr, contract, err } +// GetERC20CustodyV2Contract returns ERC20CustodyV2 contract address and binder +// NOTE: we use the same address as gateway v1 +// this simplify the migration process v1 will be completely removed in the future +// currently the ABI for withdraw is identical, therefore both contract instances can be used +func (ob *Observer) GetERC20CustodyV2Contract() (ethcommon.Address, *erc20custodyv2.ERC20Custody, error) { + addr := ethcommon.HexToAddress(ob.GetChainParams().Erc20CustodyContractAddress) + contract, err := erc20custodyv2.NewERC20Custody(addr, ob.evmClient) + return addr, contract, err +} + +// GetGatewayContract returns the gateway contract address and binder +func (ob *Observer) GetGatewayContract() (ethcommon.Address, *gatewayevm.GatewayEVM, error) { + addr := ethcommon.HexToAddress(ob.GetChainParams().GatewayAddress) + contract, err := gatewayevm.NewGatewayEVM(addr, ob.evmClient) + return addr, contract, err +} + // FetchConnectorContractEth returns the Eth connector address and binder // TODO(revamp): move this to a contract package func FetchConnectorContractEth( @@ -230,25 +245,10 @@ func (ob *Observer) WatchRPCStatus(ctx context.Context) error { } } -// SetPendingTx sets the pending transaction in memory -func (ob *Observer) SetPendingTx(nonce uint64, transaction *ethtypes.Transaction) { - ob.Mu().Lock() - defer ob.Mu().Unlock() - ob.outboundPendingTransactions[ob.OutboundID(nonce)] = transaction -} - -// GetPendingTx gets the pending transaction from memory -func (ob *Observer) GetPendingTx(nonce uint64) *ethtypes.Transaction { - ob.Mu().Lock() - defer ob.Mu().Unlock() - return ob.outboundPendingTransactions[ob.OutboundID(nonce)] -} - // SetTxNReceipt sets the receipt and transaction in memory func (ob *Observer) SetTxNReceipt(nonce uint64, receipt *ethtypes.Receipt, transaction *ethtypes.Transaction) { ob.Mu().Lock() defer ob.Mu().Unlock() - delete(ob.outboundPendingTransactions, ob.OutboundID(nonce)) // remove pending transaction, if any ob.outboundConfirmedReceipts[ob.OutboundID(nonce)] = receipt ob.outboundConfirmedTransactions[ob.OutboundID(nonce)] = transaction } diff --git a/zetaclient/chains/evm/observer/observer_test.go b/zetaclient/chains/evm/observer/observer_test.go index 95d2ed2140..69ff6a977d 100644 --- a/zetaclient/chains/evm/observer/observer_test.go +++ b/zetaclient/chains/evm/observer/observer_test.go @@ -59,7 +59,7 @@ func getAppContext( logger := zerolog.New(zerolog.NewTestWriter(t)) // create AppContext - appContext := zctx.New(cfg, logger) + appContext := zctx.New(cfg, nil, logger) chainParams := map[int64]*observertypes.ChainParams{ evmChain.ChainId: evmChainParams, chains.ZetaChainMainnet.ChainId: ptr.Ptr( diff --git a/zetaclient/chains/evm/observer/outbound.go b/zetaclient/chains/evm/observer/outbound.go index 680fb4e3af..2a10ffc5af 100644 --- a/zetaclient/chains/evm/observer/outbound.go +++ b/zetaclient/chains/evm/observer/outbound.go @@ -13,25 +13,35 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/pkg/errors" "github.com/rs/zerolog" - "github.com/rs/zerolog/log" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.non-eth.sol" + erc20custodyv2 "github.com/zeta-chain/protocol-contracts/v2/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" + crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/chains/evm" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/compliance" zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/logs" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "github.com/zeta-chain/zetacore/zetaclient/zetacore" ) // WatchOutbound watches evm chain for outgoing txs status // TODO(revamp): move ticker function to ticker file -// TODO(revamp): move inner logic to a separate function func (ob *Observer) WatchOutbound(ctx context.Context) error { + // get app context + app, err := zctx.FromContext(ctx) + if err != nil { + return err + } + + // create outbound ticker + chainID := ob.Chain().ChainId ticker, err := clienttypes.NewDynamicTicker( fmt.Sprintf("EVM_WatchOutbound_%d", ob.Chain().ChainId), ob.GetChainParams().OutboundTicker, @@ -41,11 +51,6 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { return err } - app, err := zctx.FromContext(ctx) - if err != nil { - return err - } - ob.Logger().Outbound.Info().Msgf("WatchOutbound started for chain %d", ob.Chain().ChainId) sampledLogger := ob.Logger().Outbound.Sample(&zerolog.BasicSampler{N: 10}) defer ticker.Stop() @@ -57,38 +62,16 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { Msgf("WatchOutbound: outbound observation is disabled for chain %d", ob.Chain().ChainId) continue } - trackers, err := ob.ZetacoreClient(). - GetAllOutboundTrackerByChain(ctx, ob.Chain().ChainId, interfaces.Ascending) + + // process outbound trackers + err := ob.ProcessOutboundTrackers(ctx) if err != nil { - continue - } - for _, tracker := range trackers { - nonceInt := tracker.Nonce - if ob.IsTxConfirmed(nonceInt) { // Go to next tracker if this one already has a confirmed tx - continue - } - txCount := 0 - var outboundReceipt *ethtypes.Receipt - var outbound *ethtypes.Transaction - for _, txHash := range tracker.HashList { - if receipt, tx, ok := ob.checkConfirmedTx(ctx, txHash.TxHash, nonceInt); ok { - txCount++ - outboundReceipt = receipt - outbound = tx - ob.Logger().Outbound.Info(). - Msgf("WatchOutbound: confirmed outbound %s for chain %d nonce %d", txHash.TxHash, ob.Chain().ChainId, nonceInt) - if txCount > 1 { - ob.Logger().Outbound.Error().Msgf( - "WatchOutbound: checkConfirmedTx passed, txCount %d chain %d nonce %d receipt %v transaction %v", txCount, ob.Chain().ChainId, nonceInt, outboundReceipt, outbound) - } - } - } - if txCount == 1 { // should be only one txHash confirmed for each nonce. - ob.SetTxNReceipt(nonceInt, outboundReceipt, outbound) - } else if txCount > 1 { // should not happen. We can't tell which txHash is true. It might happen (e.g. glitchy/hacked endpoint) - ob.Logger().Outbound.Error().Msgf("WatchOutbound: confirmed multiple (%d) outbound for chain %d nonce %d", txCount, ob.Chain().ChainId, nonceInt) - } + ob.Logger(). + Outbound.Error(). + Err(err). + Msgf("WatchOutbound: error ProcessOutboundTrackers for chain %d", chainID) } + ticker.UpdateInterval(ob.GetChainParams().OutboundTicker, ob.Logger().Outbound) case <-ob.StopChannel(): ob.Logger().Outbound.Info().Msg("WatchOutbound: stopped") @@ -97,6 +80,61 @@ func (ob *Observer) WatchOutbound(ctx context.Context) error { } } +// ProcessOutboundTrackers processes outbound trackers +func (ob *Observer) ProcessOutboundTrackers(ctx context.Context) error { + chainID := ob.Chain().ChainId + trackers, err := ob.ZetacoreClient().GetAllOutboundTrackerByChain(ctx, ob.Chain().ChainId, interfaces.Ascending) + if err != nil { + return errors.Wrap(err, "GetAllOutboundTrackerByChain error") + } + + // prepare logger fields + logger := ob.Logger().Outbound.With(). + Str(logs.FieldMethod, "ProcessOutboundTrackers"). + Int64(logs.FieldChain, chainID). + Logger() + + // process outbound trackers + for _, tracker := range trackers { + // go to next tracker if this one already has a confirmed tx + nonce := tracker.Nonce + if ob.IsTxConfirmed(nonce) { + continue + } + + // check each txHash and save tx and receipt if it's legit and confirmed + txCount := 0 + var outboundReceipt *ethtypes.Receipt + var outbound *ethtypes.Transaction + for _, txHash := range tracker.HashList { + if receipt, tx, ok := ob.checkConfirmedTx(ctx, txHash.TxHash, nonce); ok { + txCount++ + outboundReceipt = receipt + outbound = tx + logger.Info().Msgf("confirmed outbound %s for chain %d nonce %d", txHash.TxHash, chainID, nonce) + if txCount > 1 { + logger.Error(). + Msgf("checkConfirmedTx passed, txCount %d chain %d nonce %d receipt %v tx %v", txCount, chainID, nonce, receipt, tx) + } + } + } + + // should be only one txHash confirmed for each nonce. + if txCount == 1 { + ob.SetTxNReceipt(nonce, outboundReceipt, outbound) + } else if txCount > 1 { + // should not happen. We can't tell which txHash is true. It might happen (e.g. bug, glitchy/hacked endpoint) + ob.Logger().Outbound.Error().Msgf("WatchOutbound: confirmed multiple (%d) outbound for chain %d nonce %d", txCount, chainID, nonce) + } else { + if len(tracker.HashList) == crosschainkeeper.MaxOutboundTrackerHashes { + ob.Logger().Outbound.Error().Msgf("WatchOutbound: outbound tracker is full of hashes for chain %d nonce %d", chainID, nonce) + } + } + } + + return nil +} + // PostVoteOutbound posts vote to zetacore for the confirmed outbound func (ob *Observer) PostVoteOutbound( ctx context.Context, @@ -181,6 +219,14 @@ func (ob *Observer) VoteOutboundIfConfirmed( if err != nil { return true, errors.Wrapf(err, "error getting erc20 custody for chain %d", ob.Chain().ChainId) } + gatewayAddr, gateway, err := ob.GetGatewayContract() + if err != nil { + return true, errors.Wrap(err, "error getting gateway for chain") + } + _, custodyV2, err := ob.GetERC20CustodyV2Contract() + if err != nil { + return true, errors.Wrapf(err, "error getting erc20 custody v2 for chain %d", ob.Chain().ChainId) + } // define a few common variables var receiveValue *big.Int @@ -200,7 +246,7 @@ func (ob *Observer) VoteOutboundIfConfirmed( } // parse the received value from the outbound receipt - receiveValue, receiveStatus, err = ParseOutboundReceivedValue( + receiveValue, receiveStatus, err = parseOutboundReceivedValue( cctx, receipt, transaction, @@ -209,6 +255,9 @@ func (ob *Observer) VoteOutboundIfConfirmed( connector, custodyAddr, custody, + custodyV2, + gatewayAddr, + gateway, ) if err != nil { logger.Error(). @@ -222,6 +271,70 @@ func (ob *Observer) VoteOutboundIfConfirmed( return false, nil } +// parseOutboundReceivedValue parses the received value and status from the outbound receipt +// The receivd value is the amount of Zeta/ERC20/Gas token (released from connector/custody/TSS) sent to the receiver +// TODO: simplify this function and reduce the number of argument +// https://github.com/zeta-chain/node/issues/2627 +// https://github.com/zeta-chain/node/pull/2666#discussion_r1718379784 +func parseOutboundReceivedValue( + cctx *crosschaintypes.CrossChainTx, + receipt *ethtypes.Receipt, + transaction *ethtypes.Transaction, + cointype coin.CoinType, + connectorAddress ethcommon.Address, + connector *zetaconnector.ZetaConnectorNonEth, + custodyAddress ethcommon.Address, + custody *erc20custody.ERC20Custody, + custodyV2 *erc20custodyv2.ERC20Custody, + gatewayAddress ethcommon.Address, + gateway *gatewayevm.GatewayEVM, +) (*big.Int, chains.ReceiveStatus, error) { + // determine the receive status and value + // https://docs.nethereum.com/en/latest/nethereum-receipt-status/ + receiveValue := big.NewInt(0) + receiveStatus := chains.ReceiveStatus_failed + if receipt.Status == ethtypes.ReceiptStatusSuccessful { + receiveValue = transaction.Value() + receiveStatus = chains.ReceiveStatus_success + } + + // parse outbound event for protocol contract v2 + if cctx.ProtocolContractVersion == crosschaintypes.ProtocolContractVersion_V2 { + return parseOutboundEventV2(cctx, receipt, transaction, custodyAddress, custodyV2, gatewayAddress, gateway) + } + + // parse receive value from the outbound receipt for Zeta and ERC20 + switch cointype { + case coin.CoinType_Zeta: + if receipt.Status == ethtypes.ReceiptStatusSuccessful { + receivedLog, revertedLog, err := ParseAndCheckZetaEvent(cctx, receipt, connectorAddress, connector) + if err != nil { + return nil, chains.ReceiveStatus_failed, err + } + // use the value in ZetaReceived/ZetaReverted event for vote message + if receivedLog != nil { + receiveValue = receivedLog.ZetaValue + } else if revertedLog != nil { + receiveValue = revertedLog.RemainingZetaValue + } + } + case coin.CoinType_ERC20: + if receipt.Status == ethtypes.ReceiptStatusSuccessful { + withdrawn, err := ParseAndCheckWithdrawnEvent(cctx, receipt, custodyAddress, custody) + if err != nil { + return nil, chains.ReceiveStatus_failed, err + } + // use the value in Withdrawn event for vote message + receiveValue = withdrawn.Amount + } + case coin.CoinType_Gas, coin.CoinType_Cmd: + // nothing to do for CoinType_Gas/CoinType_Cmd, no need to parse event + default: + return nil, chains.ReceiveStatus_failed, fmt.Errorf("unknown coin type %s", cointype) + } + return receiveValue, receiveStatus, nil +} + // ParseAndCheckZetaEvent parses and checks ZetaReceived/ZetaReverted event from the outbound receipt // It either returns an ZetaReceived or an ZetaReverted event, or an error if no event found func ParseAndCheckZetaEvent( @@ -314,59 +427,6 @@ func ParseAndCheckWithdrawnEvent( return nil, errors.New("no ERC20 Withdrawn event found") } -// ParseOutboundReceivedValue parses the received value and status from the outbound receipt -// The receivd value is the amount of Zeta/ERC20/Gas token (released from connector/custody/TSS) sent to the receiver -func ParseOutboundReceivedValue( - cctx *crosschaintypes.CrossChainTx, - receipt *ethtypes.Receipt, - transaction *ethtypes.Transaction, - cointype coin.CoinType, - connectorAddress ethcommon.Address, - connector *zetaconnector.ZetaConnectorNonEth, - custodyAddress ethcommon.Address, - custody *erc20custody.ERC20Custody, -) (*big.Int, chains.ReceiveStatus, error) { - // determine the receive status and value - // https://docs.nethereum.com/en/latest/nethereum-receipt-status/ - receiveValue := big.NewInt(0) - receiveStatus := chains.ReceiveStatus_failed - if receipt.Status == ethtypes.ReceiptStatusSuccessful { - receiveValue = transaction.Value() - receiveStatus = chains.ReceiveStatus_success - } - - // parse receive value from the outbound receipt for Zeta and ERC20 - switch cointype { - case coin.CoinType_Zeta: - if receipt.Status == ethtypes.ReceiptStatusSuccessful { - receivedLog, revertedLog, err := ParseAndCheckZetaEvent(cctx, receipt, connectorAddress, connector) - if err != nil { - return nil, chains.ReceiveStatus_failed, err - } - // use the value in ZetaReceived/ZetaReverted event for vote message - if receivedLog != nil { - receiveValue = receivedLog.ZetaValue - } else if revertedLog != nil { - receiveValue = revertedLog.RemainingZetaValue - } - } - case coin.CoinType_ERC20: - if receipt.Status == ethtypes.ReceiptStatusSuccessful { - withdrawn, err := ParseAndCheckWithdrawnEvent(cctx, receipt, custodyAddress, custody) - if err != nil { - return nil, chains.ReceiveStatus_failed, err - } - // use the value in Withdrawn event for vote message - receiveValue = withdrawn.Amount - } - case coin.CoinType_Gas, coin.CoinType_Cmd: - // nothing to do for CoinType_Gas/CoinType_Cmd, no need to parse event - default: - return nil, chains.ReceiveStatus_failed, fmt.Errorf("unknown coin type %s", cointype) - } - return receiveValue, receiveStatus, nil -} - // checkConfirmedTx checks if a txHash is confirmed // returns (receipt, transaction, true) if confirmed or (nil, nil, false) otherwise func (ob *Observer) checkConfirmedTx( @@ -377,23 +437,27 @@ func (ob *Observer) checkConfirmedTx( ctx, cancel := context.WithTimeout(ctx, 3*time.Second) defer cancel() + // prepare logger + logger := ob.Logger().Outbound.With(). + Str(logs.FieldMethod, "checkConfirmedTx"). + Int64(logs.FieldChain, ob.Chain().ChainId). + Uint64(logs.FieldNonce, nonce). + Str(logs.FieldTx, txHash). + Logger() + // query transaction transaction, isPending, err := ob.evmClient.TransactionByHash(ctx, ethcommon.HexToHash(txHash)) if err != nil { - log.Error(). - Err(err). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Int64("chainID", ob.Chain().ChainId). - Msg("error getting transaction for outbound") + logger.Error().Err(err).Msg("TransactionByHash error") return nil, nil, false } if transaction == nil { // should not happen - log.Error(). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Uint64("nonce", nonce). - Msg("transaction is nil for txHash") + logger.Error().Msg("transaction is nil") + return nil, nil, false + } + if isPending { + // should not happen when we are here. The outbound tracker reporter won't report a pending tx. + logger.Error().Msg("transaction is pending") return nil, nil, false } @@ -401,12 +465,7 @@ func (ob *Observer) checkConfirmedTx( signer := ethtypes.NewLondonSigner(big.NewInt(ob.Chain().ChainId)) from, err := signer.Sender(transaction) if err != nil { - log.Error(). - Err(err). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", transaction.Hash().Hex()). - Int64("chainID", ob.Chain().ChainId). - Msg("local recovery of sender address failed for outbound") + logger.Error().Err(err).Msg("local recovery of sender address failed") return nil, nil, false } if from != ob.TSS().EVMAddress() { // must be TSS address @@ -416,13 +475,8 @@ func (ob *Observer) checkConfirmedTx( // TODO : improve this logic to verify that the correct TSS address is the from address. // https://github.com/zeta-chain/node/issues/2487 - log.Info(). - Str("function", "confirmTxByHash"). - Str("sender", from.Hex()). - Str("outboundTxHash", transaction.Hash().Hex()). - Int64("chainID", ob.Chain().ChainId). - Str("currentTSSAddress", ob.TSS().EVMAddress().Hex()). - Msg("sender is not current TSS address") + logger.Warn(). + Msgf("tx sender %s is not matching current TSS address %s", from.String(), ob.TSS().EVMAddress().String()) addressList := ob.TSS().EVMAddressList() isOldTssAddress := false for _, addr := range addressList { @@ -431,70 +485,35 @@ func (ob *Observer) checkConfirmedTx( } } if !isOldTssAddress { - log.Error(). - Str("function", "confirmTxByHash"). - Str("sender", from.Hex()). - Str("outboundTxHash", transaction.Hash().Hex()). - Int64("chainID", ob.Chain().ChainId). - Str("currentTSSAddress", ob.TSS().EVMAddress().Hex()). - Msg("sender is not current or old TSS address") + logger.Error().Msgf("tx sender %s is not matching any of the TSS addresses", from.String()) return nil, nil, false } } - if transaction.Nonce() != nonce { // must match cctx nonce - log.Error(). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Uint64("wantedNonce", nonce). - Uint64("gotTxNonce", transaction.Nonce()). - Msg("outbound nonce mismatch") - return nil, nil, false - } - - // save pending transaction - if isPending { - ob.SetPendingTx(nonce, transaction) + if transaction.Nonce() != nonce { // must match tracker nonce + logger.Error().Msgf("tx nonce %d is not matching tracker nonce", nonce) return nil, nil, false } // query receipt receipt, err := ob.evmClient.TransactionReceipt(ctx, ethcommon.HexToHash(txHash)) if err != nil { - log.Error(). - Err(err). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Uint64("nonce", nonce). - Msg("transactionReceipt error") + logger.Error().Err(err).Msg("TransactionReceipt error") return nil, nil, false } if receipt == nil { // should not happen - log.Error(). - Str("function", "confirmTxByHash"). - Str("outboundTxHash", txHash). - Uint64("nonce", nonce). - Msg("receipt is nil") + logger.Error().Msg("receipt is nil") return nil, nil, false } ob.LastBlock() // check confirmations lastHeight, err := ob.evmClient.BlockNumber(ctx) if err != nil { - log.Error(). - Str("function", "confirmTxByHash"). - Err(err). - Int64("chainID", ob.GetChainParams().ChainId). - Msg("error getting block number for chain") + logger.Error().Err(err).Msg("BlockNumber error") return nil, nil, false } if !ob.HasEnoughConfirmations(receipt, lastHeight) { - log.Debug(). - Str("function", "confirmTxByHash"). - Str("txHash", txHash). - Uint64("nonce", nonce). - Uint64("receiptBlock", receipt.BlockNumber.Uint64()). - Uint64("currentBlock", lastHeight). - Msg("txHash included but not confirmed") + logger.Debug(). + Msgf("tx included but not confirmed, receipt block %d current block %d", receipt.BlockNumber.Uint64(), lastHeight) return nil, nil, false } @@ -502,13 +521,7 @@ func (ob *Observer) checkConfirmedTx( // Note: a guard for false BlockNumber in receipt. The blob-carrying tx won't come here err = ob.CheckTxInclusion(transaction, receipt) if err != nil { - log.Error(). - Err(err). - Str("function", "confirmTxByHash"). - Str("errorContext", "checkTxInclusion"). - Str("txHash", txHash). - Uint64("nonce", nonce). - Msg("checkTxInclusion error") + logger.Error().Err(err).Msg("CheckTxInclusion error") return nil, nil, false } diff --git a/zetaclient/chains/evm/observer/outbound_test.go b/zetaclient/chains/evm/observer/outbound_test.go index 4081f9a76c..68b3da13ae 100644 --- a/zetaclient/chains/evm/observer/outbound_test.go +++ b/zetaclient/chains/evm/observer/outbound_test.go @@ -7,8 +7,8 @@ import ( ethcommon "github.com/ethereum/go-ethereum/common" "github.com/rs/zerolog" "github.com/stretchr/testify/require" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.non-eth.sol" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/testutil/sample" @@ -413,188 +413,191 @@ func Test_ParseERC20WithdrawnEvent(t *testing.T) { }) } -func Test_ParseOutboundReceivedValue(t *testing.T) { - chainID := chains.Ethereum.ChainId - connector, connectorAddr, custody, custodyAddr := getContractsByChainID(t, chainID) - - t.Run("should parse and check ZetaReceived event from archived outbound receipt", func(t *testing.T) { - // load archived outbound receipt that contains ZetaReceived event - // https://etherscan.io/tx/0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f - nonce := uint64(9718) - coinType := coin.CoinType_Zeta - cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( - t, - TestDataDir, - chainID, - nonce, - testutils.EventZetaReceived, - ) - params := cctx.GetCurrentOutboundParam() - value, status, err := observer.ParseOutboundReceivedValue( - cctx, - receipt, - outbound, - coinType, - connectorAddr, - connector, - custodyAddr, - custody, - ) - require.NoError(t, err) - require.True(t, params.Amount.BigInt().Cmp(value) == 0) - require.Equal(t, chains.ReceiveStatus_success, status) - }) - t.Run("should parse and check ZetaReverted event from archived outbound receipt", func(t *testing.T) { - // load archived outbound receipt that contains ZetaReverted event - // use local network tx: 0x1487e6a31dd430306667250b72bf15b8390b73108b69f3de5c1b2efe456036a7 - localChainID := chains.GoerliLocalnet.ChainId - nonce := uint64(14) - coinType := coin.CoinType_Zeta - connectorLocal, connectorAddrLocal, custodyLocal, custodyAddrLocal := getContractsByChainID(t, localChainID) - cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( - t, - TestDataDir, - localChainID, - nonce, - testutils.EventZetaReverted, - ) - params := cctx.GetCurrentOutboundParam() - value, status, err := observer.ParseOutboundReceivedValue( - cctx, receipt, outbound, coinType, connectorAddrLocal, connectorLocal, custodyAddrLocal, custodyLocal) - require.NoError(t, err) - require.True(t, params.Amount.BigInt().Cmp(value) == 0) - require.Equal(t, chains.ReceiveStatus_success, status) - }) - t.Run("should parse and check ERC20 Withdrawn event from archived outbound receipt", func(t *testing.T) { - // load archived outbound receipt that contains ERC20 Withdrawn event - // https://etherscan.io/tx/0xd2eba7ac3da1b62800165414ea4bcaf69a3b0fb9b13a0fc32f4be11bfef79146 - nonce := uint64(8014) - coinType := coin.CoinType_ERC20 - cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( - t, - TestDataDir, - chainID, - nonce, - testutils.EventERC20Withdraw, - ) - params := cctx.GetCurrentOutboundParam() - value, status, err := observer.ParseOutboundReceivedValue( - cctx, - receipt, - outbound, - coinType, - connectorAddr, - connector, - custodyAddr, - custody, - ) - require.NoError(t, err) - require.True(t, params.Amount.BigInt().Cmp(value) == 0) - require.Equal(t, chains.ReceiveStatus_success, status) - }) - t.Run("nothing to parse if coinType is Gas", func(t *testing.T) { - // load archived outbound receipt of Gas token transfer - // https://etherscan.io/tx/0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3 - nonce := uint64(7260) - coinType := coin.CoinType_Gas - cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt(t, TestDataDir, chainID, nonce, "") - params := cctx.GetCurrentOutboundParam() - value, status, err := observer.ParseOutboundReceivedValue( - cctx, - receipt, - outbound, - coinType, - connectorAddr, - connector, - custodyAddr, - custody, - ) - require.NoError(t, err) - require.True(t, params.Amount.BigInt().Cmp(value) == 0) - require.Equal(t, chains.ReceiveStatus_success, status) - }) - t.Run("should fail on unknown coin type", func(t *testing.T) { - // load archived outbound receipt that contains ZetaReceived event - // https://etherscan.io/tx/0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f - nonce := uint64(9718) - coinType := coin.CoinType(5) // unknown coin type - cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( - t, - TestDataDir, - chainID, - nonce, - testutils.EventZetaReceived, - ) - value, status, err := observer.ParseOutboundReceivedValue( - cctx, - receipt, - outbound, - coinType, - connectorAddr, - connector, - custodyAddr, - custody, - ) - require.ErrorContains(t, err, "unknown coin type") - require.Nil(t, value) - require.Equal(t, chains.ReceiveStatus_failed, status) - }) - t.Run("should fail if unable to parse ZetaReceived event", func(t *testing.T) { - // load archived outbound receipt that contains ZetaReceived event - // https://etherscan.io/tx/0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f - nonce := uint64(9718) - coinType := coin.CoinType_Zeta - cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( - t, - TestDataDir, - chainID, - nonce, - testutils.EventZetaReceived, - ) - - // use an arbitrary address to make event parsing fail - fakeConnectorAddress := sample.EthAddress() - value, status, err := observer.ParseOutboundReceivedValue( - cctx, - receipt, - outbound, - coinType, - fakeConnectorAddress, - connector, - custodyAddr, - custody, - ) - require.Error(t, err) - require.Nil(t, value) - require.Equal(t, chains.ReceiveStatus_failed, status) - }) - t.Run("should fail if unable to parse ERC20 Withdrawn event", func(t *testing.T) { - // load archived outbound receipt that contains ERC20 Withdrawn event - // https://etherscan.io/tx/0xd2eba7ac3da1b62800165414ea4bcaf69a3b0fb9b13a0fc32f4be11bfef79146 - nonce := uint64(8014) - coinType := coin.CoinType_ERC20 - cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( - t, - TestDataDir, - chainID, - nonce, - testutils.EventERC20Withdraw, - ) - - // use an arbitrary address to make event parsing fail - fakeCustodyAddress := sample.EthAddress() - value, status, err := observer.ParseOutboundReceivedValue( - cctx, - receipt, - outbound, - coinType, - connectorAddr, - connector, - fakeCustodyAddress, - custody, - ) - require.Error(t, err) - require.Nil(t, value) - require.Equal(t, chains.ReceiveStatus_failed, status) - }) -} +// TODO: create mocks for gateway and ERC20CustodyV2 and uncomment these tests +// https://github.com/zeta-chain/node/issues/2669 +// +//func Test_ParseOutboundReceivedValue(t *testing.T) { +// chainID := chains.Ethereum.ChainId +// connector, connectorAddr, custody, custodyAddr := getContractsByChainID(t, chainID) +// +// t.Run("should parse and check ZetaReceived event from archived outbound receipt", func(t *testing.T) { +// // load archived outbound receipt that contains ZetaReceived event +// // https://etherscan.io/tx/0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f +// nonce := uint64(9718) +// coinType := coin.CoinType_Zeta +// cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( +// t, +// TestDataDir, +// chainID, +// nonce, +// testutils.EventZetaReceived, +// ) +// params := cctx.GetCurrentOutboundParam() +// value, status, err := observer.parseOutboundReceivedValue( +// cctx, +// receipt, +// outbound, +// coinType, +// connectorAddr, +// connector, +// custodyAddr, +// custody, +// ) +// require.NoError(t, err) +// require.True(t, params.Amount.BigInt().Cmp(value) == 0) +// require.Equal(t, chains.ReceiveStatus_success, status) +// }) +// t.Run("should parse and check ZetaReverted event from archived outbound receipt", func(t *testing.T) { +// // load archived outbound receipt that contains ZetaReverted event +// // use local network tx: 0x1487e6a31dd430306667250b72bf15b8390b73108b69f3de5c1b2efe456036a7 +// localChainID := chains.GoerliLocalnet.ChainId +// nonce := uint64(14) +// coinType := coin.CoinType_Zeta +// connectorLocal, connectorAddrLocal, custodyLocal, custodyAddrLocal := getContractsByChainID(t, localChainID) +// cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( +// t, +// TestDataDir, +// localChainID, +// nonce, +// testutils.EventZetaReverted, +// ) +// params := cctx.GetCurrentOutboundParam() +// value, status, err := observer.parseOutboundReceivedValue( +// cctx, receipt, outbound, coinType, connectorAddrLocal, connectorLocal, custodyAddrLocal, custodyLocal) +// require.NoError(t, err) +// require.True(t, params.Amount.BigInt().Cmp(value) == 0) +// require.Equal(t, chains.ReceiveStatus_success, status) +// }) +// t.Run("should parse and check ERC20 Withdrawn event from archived outbound receipt", func(t *testing.T) { +// // load archived outbound receipt that contains ERC20 Withdrawn event +// // https://etherscan.io/tx/0xd2eba7ac3da1b62800165414ea4bcaf69a3b0fb9b13a0fc32f4be11bfef79146 +// nonce := uint64(8014) +// coinType := coin.CoinType_ERC20 +// cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( +// t, +// TestDataDir, +// chainID, +// nonce, +// testutils.EventERC20Withdraw, +// ) +// params := cctx.GetCurrentOutboundParam() +// value, status, err := observer.parseOutboundReceivedValue( +// cctx, +// receipt, +// outbound, +// coinType, +// connectorAddr, +// connector, +// custodyAddr, +// custody, +// ) +// require.NoError(t, err) +// require.True(t, params.Amount.BigInt().Cmp(value) == 0) +// require.Equal(t, chains.ReceiveStatus_success, status) +// }) +// t.Run("nothing to parse if coinType is Gas", func(t *testing.T) { +// // load archived outbound receipt of Gas token transfer +// // https://etherscan.io/tx/0xd13b593eb62b5500a00e288cc2fb2c8af1339025c0e6bc6183b8bef2ebbed0d3 +// nonce := uint64(7260) +// coinType := coin.CoinType_Gas +// cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt(t, TestDataDir, chainID, nonce, "") +// params := cctx.GetCurrentOutboundParam() +// value, status, err := observer.parseOutboundReceivedValue( +// cctx, +// receipt, +// outbound, +// coinType, +// connectorAddr, +// connector, +// custodyAddr, +// custody, +// ) +// require.NoError(t, err) +// require.True(t, params.Amount.BigInt().Cmp(value) == 0) +// require.Equal(t, chains.ReceiveStatus_success, status) +// }) +// t.Run("should fail on unknown coin type", func(t *testing.T) { +// // load archived outbound receipt that contains ZetaReceived event +// // https://etherscan.io/tx/0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f +// nonce := uint64(9718) +// coinType := coin.CoinType(5) // unknown coin type +// cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( +// t, +// TestDataDir, +// chainID, +// nonce, +// testutils.EventZetaReceived, +// ) +// value, status, err := observer.parseOutboundReceivedValue( +// cctx, +// receipt, +// outbound, +// coinType, +// connectorAddr, +// connector, +// custodyAddr, +// custody, +// ) +// require.ErrorContains(t, err, "unknown coin type") +// require.Nil(t, value) +// require.Equal(t, chains.ReceiveStatus_failed, status) +// }) +// t.Run("should fail if unable to parse ZetaReceived event", func(t *testing.T) { +// // load archived outbound receipt that contains ZetaReceived event +// // https://etherscan.io/tx/0x81342051b8a85072d3e3771c1a57c7bdb5318e8caf37f5a687b7a91e50a7257f +// nonce := uint64(9718) +// coinType := coin.CoinType_Zeta +// cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( +// t, +// TestDataDir, +// chainID, +// nonce, +// testutils.EventZetaReceived, +// ) +// +// // use an arbitrary address to make event parsing fail +// fakeConnectorAddress := sample.EthAddress() +// value, status, err := observer.parseOutboundReceivedValue( +// cctx, +// receipt, +// outbound, +// coinType, +// fakeConnectorAddress, +// connector, +// custodyAddr, +// custody, +// ) +// require.Error(t, err) +// require.Nil(t, value) +// require.Equal(t, chains.ReceiveStatus_failed, status) +// }) +// t.Run("should fail if unable to parse ERC20 Withdrawn event", func(t *testing.T) { +// // load archived outbound receipt that contains ERC20 Withdrawn event +// // https://etherscan.io/tx/0xd2eba7ac3da1b62800165414ea4bcaf69a3b0fb9b13a0fc32f4be11bfef79146 +// nonce := uint64(8014) +// coinType := coin.CoinType_ERC20 +// cctx, outbound, receipt := testutils.LoadEVMCctxNOutboundNReceipt( +// t, +// TestDataDir, +// chainID, +// nonce, +// testutils.EventERC20Withdraw, +// ) +// +// // use an arbitrary address to make event parsing fail +// fakeCustodyAddress := sample.EthAddress() +// value, status, err := observer.parseOutboundReceivedValue( +// cctx, +// receipt, +// outbound, +// coinType, +// connectorAddr, +// connector, +// fakeCustodyAddress, +// custody, +// ) +// require.Error(t, err) +// require.Nil(t, value) +// require.Equal(t, chains.ReceiveStatus_failed, status) +// }) +//} diff --git a/zetaclient/chains/evm/observer/v2_inbound.go b/zetaclient/chains/evm/observer/v2_inbound.go new file mode 100644 index 0000000000..88ddcf8017 --- /dev/null +++ b/zetaclient/chains/evm/observer/v2_inbound.go @@ -0,0 +1,330 @@ +package observer + +import ( + "bytes" + "context" + "encoding/hex" + "sort" + + "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/pkg/coin" + "github.com/zeta-chain/zetacore/pkg/constant" + "github.com/zeta-chain/zetacore/pkg/crypto" + "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm" + "github.com/zeta-chain/zetacore/zetaclient/compliance" + "github.com/zeta-chain/zetacore/zetaclient/config" + "github.com/zeta-chain/zetacore/zetaclient/metrics" + "github.com/zeta-chain/zetacore/zetaclient/zetacore" +) + +// checkEventProcessability checks if the event is processable +func (ob *Observer) checkEventProcessability( + sender, receiver ethcommon.Address, + txHash ethcommon.Hash, + payload []byte, +) bool { + // compliance check + if config.ContainRestrictedAddress(sender.Hex(), receiver.Hex()) { + compliance.PrintComplianceLog( + ob.Logger().Inbound, + ob.Logger().Compliance, + false, + ob.Chain().ChainId, + txHash.Hex(), + sender.Hex(), + receiver.Hex(), + "Deposit", + ) + return false + } + + // donation check + if bytes.Equal(payload, []byte(constant.DonationMessage)) { + logFields := map[string]any{ + "chain": ob.Chain().ChainId, + "tx": txHash.Hex(), + } + ob.Logger().Inbound.Info().Fields(logFields). + Msgf("thank you rich folk for your donation!") + return false + } + + return true +} + +// ObserveGatewayDeposit queries the gateway contract for deposit events +// returns the last block successfully scanned +func (ob *Observer) ObserveGatewayDeposit(ctx context.Context, startBlock, toBlock uint64) (uint64, error) { + // filter ERC20CustodyDeposited logs + gatewayAddr, gatewayContract, err := ob.GetGatewayContract() + if err != nil { + // lastScanned is startBlock - 1 + return startBlock - 1, errors.Wrap(err, "can't get gateway contract") + } + + // get iterator for the events for the block range + eventIterator, err := gatewayContract.FilterDeposited(&bind.FilterOpts{ + Start: startBlock, + End: &toBlock, + Context: ctx, + }, []ethcommon.Address{}, []ethcommon.Address{}) + if err != nil { + return startBlock - 1, errors.Wrapf( + err, + "error filtering deposits from block %d to %d for chain %d", + startBlock, + toBlock, + ob.Chain().ChainId, + ) + } + + // parse and validate events + events := ob.parseAndValidateDepositEvents(eventIterator, gatewayAddr) + + // increment prom counter + metrics.GetFilterLogsPerChain.WithLabelValues(ob.Chain().Name).Inc() + + // post to zetacore + lastScanned := uint64(0) + for _, event := range events { + // remember which block we are scanning (there could be multiple events in the same block) + if event.Raw.BlockNumber > lastScanned { + lastScanned = event.Raw.BlockNumber + } + + // check if the event is processable + if !ob.checkEventProcessability(event.Sender, event.Receiver, event.Raw.TxHash, event.Payload) { + continue + } + + msg := ob.newDepositInboundVote(event) + + ob.Logger().Inbound.Info(). + Msgf("ObserveGateway: Deposit inbound detected on chain %d tx %s block %d from %s value %s message %s", + ob.Chain(). + ChainId, event.Raw.TxHash.Hex(), event.Raw.BlockNumber, event.Sender.Hex(), event.Amount.String(), hex.EncodeToString(event.Payload)) + + _, err = ob.PostVoteInbound(ctx, &msg, zetacore.PostVoteInboundExecutionGasLimit) + if err != nil { + // decrement the last scanned block so we have to re-scan from this block next time + return lastScanned - 1, errors.Wrap(err, "error posting vote inbound") + } + } + + // successfully processed all events in [startBlock, toBlock] + return toBlock, nil +} + +// parseAndValidateDepositEvents collects and sorts events by block number, tx index, and log index +func (ob *Observer) parseAndValidateDepositEvents( + iterator *gatewayevm.GatewayEVMDepositedIterator, + gatewayAddr ethcommon.Address, +) []*gatewayevm.GatewayEVMDeposited { + // collect and sort events by block number, then tx index, then log index (ascending) + events := make([]*gatewayevm.GatewayEVMDeposited, 0) + for iterator.Next() { + events = append(events, iterator.Event) + err := evm.ValidateEvmTxLog(&iterator.Event.Raw, gatewayAddr, "", evm.TopicsGatewayDeposit) + if err == nil { + events = append(events, iterator.Event) + continue + } + ob.Logger().Inbound.Warn(). + Err(err). + Msgf("ObserveGateway: invalid Deposited event in tx %s on chain %d at height %d", + iterator.Event.Raw.TxHash.Hex(), ob.Chain().ChainId, iterator.Event.Raw.BlockNumber) + } + sort.SliceStable(events, func(i, j int) bool { + if events[i].Raw.BlockNumber == events[j].Raw.BlockNumber { + if events[i].Raw.TxIndex == events[j].Raw.TxIndex { + return events[i].Raw.Index < events[j].Raw.Index + } + return events[i].Raw.TxIndex < events[j].Raw.TxIndex + } + return events[i].Raw.BlockNumber < events[j].Raw.BlockNumber + }) + + // filter events from same tx + filtered := make([]*gatewayevm.GatewayEVMDeposited, 0) + guard := make(map[string]bool) + for _, event := range events { + // guard against multiple events in the same tx + if guard[event.Raw.TxHash.Hex()] { + ob.Logger().Inbound.Warn(). + Msgf("ObserveGateway: multiple remote call events detected in same tx %s", event.Raw.TxHash) + continue + } + guard[event.Raw.TxHash.Hex()] = true + filtered = append(filtered, event) + } + + return filtered +} + +// newDepositInboundVote creates a MsgVoteInbound message for a Deposit event +func (ob *Observer) newDepositInboundVote(event *gatewayevm.GatewayEVMDeposited) types.MsgVoteInbound { + // if event.Asset is zero, it's a native token + coinType := coin.CoinType_ERC20 + if crypto.IsEmptyAddress(event.Asset) { + coinType = coin.CoinType_Gas + } + + return *types.NewMsgVoteInbound( + ob.ZetacoreClient().GetKeys().GetOperatorAddress().String(), + event.Sender.Hex(), + ob.Chain().ChainId, + "", + event.Receiver.Hex(), + ob.ZetacoreClient().Chain().ChainId, + sdkmath.NewUintFromBigInt(event.Amount), + hex.EncodeToString(event.Payload), + event.Raw.TxHash.Hex(), + event.Raw.BlockNumber, + 1_500_000, + coinType, + event.Asset.Hex(), + event.Raw.Index, + types.ProtocolContractVersion_V2, + types.WithEVMRevertOptions(event.RevertOptions), + ) +} + +// ObserveGatewayCall queries the gateway contract for call events +// returns the last block successfully scanned +// TODO: there are lot of similarities between this function and ObserveGatewayDeposit +// logic should be factorized using interfaces and generics +// https://github.com/zeta-chain/node/issues/2493 +func (ob *Observer) ObserveGatewayCall(ctx context.Context, startBlock, toBlock uint64) (uint64, error) { + // filter ERC20CustodyDeposited logs + gatewayAddr, gatewayContract, err := ob.GetGatewayContract() + if err != nil { + // lastScanned is startBlock - 1 + return startBlock - 1, errors.Wrap(err, "can't get gateway contract") + } + + // get iterator for the events for the block range + eventIterator, err := gatewayContract.FilterCalled(&bind.FilterOpts{ + Start: startBlock, + End: &toBlock, + Context: ctx, + }, []ethcommon.Address{}, []ethcommon.Address{}) + if err != nil { + return startBlock - 1, errors.Wrapf( + err, + "error filtering calls from block %d to %d for chain %d", + startBlock, + toBlock, + ob.Chain().ChainId, + ) + } + + // parse and validate events + events := ob.parseAndValidateCallEvents(eventIterator, gatewayAddr) + + // increment prom counter + metrics.GetFilterLogsPerChain.WithLabelValues(ob.Chain().Name).Inc() + + // post to zetacore + lastScanned := uint64(0) + for _, event := range events { + // remember which block we are scanning (there could be multiple events in the same block) + if event.Raw.BlockNumber > lastScanned { + lastScanned = event.Raw.BlockNumber + } + + // check if the event is processable + if !ob.checkEventProcessability(event.Sender, event.Receiver, event.Raw.TxHash, event.Payload) { + continue + } + + msg := ob.newCallInboundVote(event) + + ob.Logger().Inbound.Info(). + Msgf("ObserveGateway: Call inbound detected on chain %d tx %s block %d from %s value message %s", + ob.Chain(). + ChainId, event.Raw.TxHash.Hex(), event.Raw.BlockNumber, event.Sender.Hex(), hex.EncodeToString(event.Payload)) + + _, err = ob.PostVoteInbound(ctx, &msg, zetacore.PostVoteInboundExecutionGasLimit) + if err != nil { + // decrement the last scanned block so we have to re-scan from this block next time + return lastScanned - 1, errors.Wrap(err, "error posting vote inbound") + } + } + + // successfully processed all events in [startBlock, toBlock] + return toBlock, nil +} + +// parseAndValidateCallEvents collects and sorts events by block number, tx index, and log index +func (ob *Observer) parseAndValidateCallEvents( + iterator *gatewayevm.GatewayEVMCalledIterator, + gatewayAddr ethcommon.Address, +) []*gatewayevm.GatewayEVMCalled { + // collect and sort events by block number, then tx index, then log index (ascending) + events := make([]*gatewayevm.GatewayEVMCalled, 0) + for iterator.Next() { + events = append(events, iterator.Event) + err := evm.ValidateEvmTxLog(&iterator.Event.Raw, gatewayAddr, "", evm.TopicsGatewayCall) + if err == nil { + events = append(events, iterator.Event) + continue + } + ob.Logger().Inbound.Warn(). + Err(err). + Msgf("ObserveGateway: invalid Call event in tx %s on chain %d at height %d", + iterator.Event.Raw.TxHash.Hex(), ob.Chain().ChainId, iterator.Event.Raw.BlockNumber) + } + sort.SliceStable(events, func(i, j int) bool { + if events[i].Raw.BlockNumber == events[j].Raw.BlockNumber { + if events[i].Raw.TxIndex == events[j].Raw.TxIndex { + return events[i].Raw.Index < events[j].Raw.Index + } + return events[i].Raw.TxIndex < events[j].Raw.TxIndex + } + return events[i].Raw.BlockNumber < events[j].Raw.BlockNumber + }) + + // filter events from same tx + filtered := make([]*gatewayevm.GatewayEVMCalled, 0) + guard := make(map[string]bool) + for _, event := range events { + // guard against multiple events in the same tx + if guard[event.Raw.TxHash.Hex()] { + ob.Logger().Inbound.Warn(). + Msgf("ObserveGateway: multiple remote call events detected in same tx %s", event.Raw.TxHash) + continue + } + guard[event.Raw.TxHash.Hex()] = true + filtered = append(filtered, event) + } + + return filtered +} + +// newCallInboundVote creates a MsgVoteInbound message for a Call event +func (ob *Observer) newCallInboundVote(event *gatewayevm.GatewayEVMCalled) types.MsgVoteInbound { + return *types.NewMsgVoteInbound( + ob.ZetacoreClient().GetKeys().GetOperatorAddress().String(), + event.Sender.Hex(), + ob.Chain().ChainId, + "", + event.Receiver.Hex(), + ob.ZetacoreClient().Chain().ChainId, + sdkmath.ZeroUint(), + hex.EncodeToString(event.Payload), + event.Raw.TxHash.Hex(), + event.Raw.BlockNumber, + 1_500_000, + coin.CoinType_NoAssetCall, + "", + event.Raw.Index, + types.ProtocolContractVersion_V2, + types.WithEVMRevertOptions(event.RevertOptions), + ) +} diff --git a/zetaclient/chains/evm/observer/v2_outbound.go b/zetaclient/chains/evm/observer/v2_outbound.go new file mode 100644 index 0000000000..3ad5a33601 --- /dev/null +++ b/zetaclient/chains/evm/observer/v2_outbound.go @@ -0,0 +1,303 @@ +package observer + +import ( + "bytes" + "encoding/hex" + "fmt" + "math/big" + "strings" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + "github.com/zeta-chain/protocol-contracts/v2/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + + "github.com/zeta-chain/zetacore/pkg/chains" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm" +) + +// parseOutboundEventV2 parses an event from an outbound with protocol contract v2 +func parseOutboundEventV2( + cctx *crosschaintypes.CrossChainTx, + receipt *ethtypes.Receipt, + transaction *ethtypes.Transaction, + custodyAddr ethcommon.Address, + custody *erc20custody.ERC20Custody, + gatewayAddr ethcommon.Address, + gateway *gatewayevm.GatewayEVM, +) (*big.Int, chains.ReceiveStatus, error) { + // return failed status if receipt status is failed + if receipt.Status == ethtypes.ReceiptStatusFailed { + return big.NewInt(0), chains.ReceiveStatus_failed, nil + } + + outboundType := evm.ParseOutboundTypeFromCCTX(*cctx) + switch outboundType { + case evm.OutboundTypeGasWithdraw, evm.OutboundTypeGasWithdrawRevert: + // simple transfer, no need to parse event + return transaction.Value(), chains.ReceiveStatus_success, nil + case evm.OutboundTypeERC20Withdraw, evm.OutboundTypeERC20WithdrawRevert: + return parseAndCheckERC20CustodyWithdraw(cctx, receipt, custodyAddr, custody) + case evm.OutboundTypeERC20WithdrawAndCall: + return parseAndCheckERC20CustodyWithdrawAndCall(cctx, receipt, custodyAddr, custody) + case evm.OutboundTypeGasWithdrawAndCall, evm.OutboundTypeCall: + // both gas withdraw and call and no-asset call uses gateway execute + // no-asset call simply hash msg.value == 0 + return parseAndCheckGatewayExecuted(cctx, receipt, gatewayAddr, gateway) + case evm.OutboundTypeGasWithdrawRevertAndCallOnRevert, evm.OutboundTypeERC20WithdrawRevertAndCallOnRevert: + return parseAndCheckGatewayReverted(cctx, receipt, gatewayAddr, gateway) + } + return big.NewInt(0), chains.ReceiveStatus_failed, fmt.Errorf("unsupported outbound type %d", outboundType) +} + +// parseAndCheckGatewayExecuted parses and checks the gateway execute event +func parseAndCheckGatewayExecuted( + cctx *crosschaintypes.CrossChainTx, + receipt *ethtypes.Receipt, + gatewayAddr ethcommon.Address, + gateway *gatewayevm.GatewayEVM, +) (*big.Int, chains.ReceiveStatus, error) { + params := cctx.GetCurrentOutboundParam() + + for _, vLog := range receipt.Logs { + executed, err := gateway.GatewayEVMFilterer.ParseExecuted(*vLog) + if err != nil { + continue + } + // basic event check + if err := evm.ValidateEvmTxLog(vLog, gatewayAddr, receipt.TxHash.Hex(), evm.TopicsGatewayExecuted); err != nil { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, errors.Wrap( + err, + "failed to validate gateway executed event", + ) + } + // destination + if !strings.EqualFold(executed.Destination.Hex(), params.Receiver) { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "receiver address mismatch in event, want %s got %s", + params.Receiver, + executed.Destination.Hex(), + ) + } + // amount + if executed.Value.Cmp(params.Amount.BigInt()) != 0 { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "amount mismatch in event, want %s got %s", + params.Amount.String(), + executed.Value.String(), + ) + } + // data + if err := checkCCTXMessage(executed.Data, cctx.RelayedMessage); err != nil { + return big.NewInt(0), chains.ReceiveStatus_failed, err + } + + return executed.Value, chains.ReceiveStatus_success, nil + } + + return big.NewInt(0), chains.ReceiveStatus_failed, errors.New("gateway execute event not found") +} + +// parseAndCheckGatewayReverted parses and checks the gateway reverted event +func parseAndCheckGatewayReverted( + cctx *crosschaintypes.CrossChainTx, + receipt *ethtypes.Receipt, + gatewayAddr ethcommon.Address, + gateway *gatewayevm.GatewayEVM, +) (*big.Int, chains.ReceiveStatus, error) { + params := cctx.GetCurrentOutboundParam() + + for _, vLog := range receipt.Logs { + reverted, err := gateway.GatewayEVMFilterer.ParseReverted(*vLog) + if err != nil { + continue + } + // basic event check + if err := evm.ValidateEvmTxLog(vLog, gatewayAddr, receipt.TxHash.Hex(), evm.TopicsGatewayReverted); err != nil { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, errors.Wrap( + err, + "failed to validate gateway reverte event", + ) + } + // destination + if !strings.EqualFold(reverted.To.Hex(), params.Receiver) { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "receiver address mismatch in event, want %s got %s", + params.Receiver, + reverted.To.Hex(), + ) + } + // token + if !strings.EqualFold(reverted.Token.Hex(), cctx.InboundParams.Asset) { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "asset address mismatch in event, want %s got %s", + cctx.InboundParams.Asset, + reverted.Token.Hex(), + ) + } + // amount + if reverted.Amount.Cmp(params.Amount.BigInt()) != 0 { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "amount mismatch in event, want %s got %s", + params.Amount.String(), + reverted.Amount.String(), + ) + } + + return reverted.Amount, chains.ReceiveStatus_success, nil + } + + return big.NewInt(0), chains.ReceiveStatus_failed, errors.New("erc20 custody withdraw event not found") +} + +// parseAndCheckERC20CustodyWithdraw parses and checks the ERC20 custody withdraw event +func parseAndCheckERC20CustodyWithdraw( + cctx *crosschaintypes.CrossChainTx, + receipt *ethtypes.Receipt, + custodyAddr ethcommon.Address, + custody *erc20custody.ERC20Custody, +) (*big.Int, chains.ReceiveStatus, error) { + params := cctx.GetCurrentOutboundParam() + + for _, vLog := range receipt.Logs { + withdrawn, err := custody.ERC20CustodyFilterer.ParseWithdrawn(*vLog) + if err != nil { + continue + } + // basic event check + if err := evm.ValidateEvmTxLog(vLog, custodyAddr, receipt.TxHash.Hex(), evm.TopicsERC20CustodyWithdraw); err != nil { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, errors.Wrap( + err, + "failed to validate erc20 custody withdrawn event", + ) + } + // destination + if !strings.EqualFold(withdrawn.To.Hex(), params.Receiver) { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "receiver address mismatch in event, want %s got %s", + params.Receiver, + withdrawn.To.Hex(), + ) + } + // token + if !strings.EqualFold(withdrawn.Token.Hex(), cctx.InboundParams.Asset) { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "asset address mismatch in event, want %s got %s", + cctx.InboundParams.Asset, + withdrawn.Token.Hex(), + ) + } + // amount + if withdrawn.Amount.Cmp(params.Amount.BigInt()) != 0 { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "amount mismatch in event, want %s got %s", + params.Amount.String(), + withdrawn.Amount.String(), + ) + } + + return withdrawn.Amount, chains.ReceiveStatus_success, nil + } + + return big.NewInt(0), chains.ReceiveStatus_failed, errors.New("erc20 custody withdraw event not found") +} + +// parseAndCheckERC20CustodyWithdrawAndCall parses and checks the ERC20 custody withdraw and call event +func parseAndCheckERC20CustodyWithdrawAndCall( + cctx *crosschaintypes.CrossChainTx, + receipt *ethtypes.Receipt, + custodyAddr ethcommon.Address, + custody *erc20custody.ERC20Custody, +) (*big.Int, chains.ReceiveStatus, error) { + params := cctx.GetCurrentOutboundParam() + + for _, vLog := range receipt.Logs { + withdrawn, err := custody.ERC20CustodyFilterer.ParseWithdrawnAndCalled(*vLog) + if err != nil { + continue + } + // basic event check + if err := evm.ValidateEvmTxLog(vLog, custodyAddr, receipt.TxHash.Hex(), evm.TopicsERC20CustodyWithdrawAndCall); err != nil { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, errors.Wrap( + err, + "failed to validate erc20 custody withdraw and call event", + ) + } + // destination + if !strings.EqualFold(withdrawn.To.Hex(), params.Receiver) { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "receiver address mismatch in event, want %s got %s", + params.Receiver, + withdrawn.To.Hex(), + ) + } + // token + if !strings.EqualFold(withdrawn.Token.Hex(), cctx.InboundParams.Asset) { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "asset address mismatch in event, want %s got %s", + cctx.InboundParams.Asset, + withdrawn.Token.Hex(), + ) + } + // amount + if withdrawn.Amount.Cmp(params.Amount.BigInt()) != 0 { + return big.NewInt( + 0, + ), chains.ReceiveStatus_failed, fmt.Errorf( + "amount mismatch in event, want %s got %s", + params.Amount.String(), + withdrawn.Amount.String(), + ) + } + // data + if err := checkCCTXMessage(withdrawn.Data, cctx.RelayedMessage); err != nil { + return big.NewInt(0), chains.ReceiveStatus_failed, err + } + + return withdrawn.Amount, chains.ReceiveStatus_success, nil + } + + return big.NewInt(0), chains.ReceiveStatus_failed, errors.New("erc20 custody withdraw and call event not found") +} + +// checkCCTXMessage checks the message of cctx with the emitted data of the event +func checkCCTXMessage(emittedData []byte, message string) error { + messageBytes, err := hex.DecodeString(message) + if err != nil { + return errors.Wrap(err, "failed to decode message") + } + if !bytes.Equal(emittedData, messageBytes) { + return fmt.Errorf("message mismatch, want %s got %s", message, hex.EncodeToString(emittedData)) + } + return nil +} diff --git a/zetaclient/chains/evm/rpc/rpc.go b/zetaclient/chains/evm/rpc/rpc.go new file mode 100644 index 0000000000..6fcc3d007c --- /dev/null +++ b/zetaclient/chains/evm/rpc/rpc.go @@ -0,0 +1,52 @@ +package rpc + +import ( + "context" + + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" +) + +// IsTxConfirmed checks if the transaction is confirmed with given confirmations +func IsTxConfirmed( + ctx context.Context, + client interfaces.EVMRPCClient, + txHash string, + confirmations uint64, +) (bool, error) { + // query the tx + _, isPending, err := client.TransactionByHash(ctx, ethcommon.HexToHash(txHash)) + if err != nil { + return false, errors.Wrapf(err, "error getting transaction for tx %s", txHash) + } + if isPending { + return false, nil + } + + // query receipt + receipt, err := client.TransactionReceipt(ctx, ethcommon.HexToHash(txHash)) + if err != nil { + return false, errors.Wrapf(err, "error getting transaction receipt for tx %s", txHash) + } + + // should not happen + if receipt == nil { + return false, errors.Errorf("receipt is nil for tx %s", txHash) + } + + // query last block height + lastHeight, err := client.BlockNumber(ctx) + if err != nil { + return false, errors.Wrap(err, "error getting block number") + } + + // check confirmations + if lastHeight < receipt.BlockNumber.Uint64() { + return false, nil + } + blocks := lastHeight - receipt.BlockNumber.Uint64() + 1 + + return blocks >= confirmations, nil +} diff --git a/zetaclient/chains/evm/rpc/rpc_live_test.go b/zetaclient/chains/evm/rpc/rpc_live_test.go new file mode 100644 index 0000000000..0c420c830e --- /dev/null +++ b/zetaclient/chains/evm/rpc/rpc_live_test.go @@ -0,0 +1,45 @@ +package rpc_test + +import ( + "context" + "math" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm/rpc" + + "testing" +) + +const ( + URLEthMainnet = "https://rpc.ankr.com/eth" + URLEthSepolia = "https://rpc.ankr.com/eth_sepolia" + URLBscMainnet = "https://rpc.ankr.com/bsc" + URLPolygonMainnet = "https://rpc.ankr.com/polygon" +) + +// Test_EVMRPCLive is a phony test to run each live test individually +func Test_EVMRPCLive(t *testing.T) { + // LiveTest_IsTxConfirmed(t) +} + +func LiveTest_IsTxConfirmed(t *testing.T) { + client, err := ethclient.Dial(URLEthMainnet) + require.NoError(t, err) + + // check if the transaction is confirmed + ctx := context.Background() + txHash := "0xd2eba7ac3da1b62800165414ea4bcaf69a3b0fb9b13a0fc32f4be11bfef79146" + + t.Run("should confirm tx", func(t *testing.T) { + confirmed, err := rpc.IsTxConfirmed(ctx, client, txHash, 12) + require.NoError(t, err) + require.True(t, confirmed) + }) + + t.Run("should not confirm tx if confirmations is not enough", func(t *testing.T) { + confirmed, err := rpc.IsTxConfirmed(ctx, client, txHash, math.MaxUint64) + require.NoError(t, err) + require.False(t, confirmed) + }) +} diff --git a/zetaclient/chains/evm/signer/outbound_data.go b/zetaclient/chains/evm/signer/outbound_data.go index 399f4e00e0..3af1338b6b 100644 --- a/zetaclient/chains/evm/signer/outbound_data.go +++ b/zetaclient/chains/evm/signer/outbound_data.go @@ -13,7 +13,6 @@ import ( "github.com/zeta-chain/zetacore/pkg/coin" "github.com/zeta-chain/zetacore/x/crosschain/types" - "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" zctx "github.com/zeta-chain/zetacore/zetaclient/context" ) @@ -40,6 +39,9 @@ type OutboundData struct { // outboundParams field contains data detailing the receiver chain and outbound transaction outboundParams *types.OutboundParams + + // revertOptions field contains data detailing the revert options + revertOptions types.RevertOptions } // NewOutboundData creates OutboundData from the given CCTX. @@ -47,7 +49,6 @@ type OutboundData struct { func NewOutboundData( ctx context.Context, cctx *types.CrossChainTx, - observer *observer.Observer, height uint64, logger zerolog.Logger, ) (*OutboundData, bool, error) { @@ -56,8 +57,6 @@ func NewOutboundData( } outboundParams := cctx.GetCurrentOutboundParam() - nonce := outboundParams.TssNonce - if err := validateParams(outboundParams); err != nil { return nil, false, errors.Wrap(err, "invalid outboundParams") } @@ -67,10 +66,22 @@ func NewOutboundData( return nil, false, errors.Wrap(err, "unable to get app from context") } - // recipient + destination chain - to, toChainID, skip := getDestination(cctx, logger) - if skip { - return nil, true, nil + var ( + to ethcommon.Address + toChainID *big.Int + ) + + // in protocol contract v2, receiver is always set in the outbound + if cctx.ProtocolContractVersion == types.ProtocolContractVersion_V2 { + to = ethcommon.HexToAddress(cctx.GetCurrentOutboundParam().Receiver) + toChainID = big.NewInt(cctx.GetCurrentOutboundParam().ReceiverChainId) + } else { + // recipient + destination chain + var skip bool + to, toChainID, skip = getDestination(cctx, logger) + if skip { + return nil, true, nil + } } // ensure that chain exists in app's context @@ -88,32 +99,23 @@ func NewOutboundData( return nil, false, errors.Wrap(err, "unable to get cctx index") } - // In case there is a pending tx, make sure this keySign is a tx replacement - if tx := observer.GetPendingTx(nonce); tx != nil { - evt := logger.Info(). - Str("cctx.pending_tx_hash", tx.Hash().Hex()). - Uint64("cctx.pending_tx_nonce", nonce) - - // new gas price is less or equal to pending tx gas - if gas.Price.Cmp(tx.GasPrice()) <= 0 { - evt.Msg("Please wait for pending outbound to be included in the block") - return nil, true, nil - } - - evt. - Uint64("cctx.gas_price", gas.Price.Uint64()). - Uint64("cctx.priority_fee", gas.PriorityFee.Uint64()). - Msg("Replacing pending outbound transaction with higher gas fees") - } - // Base64 decode message var message []byte if cctx.InboundParams.CoinType != coin.CoinType_Cmd { - msg, errDecode := base64.StdEncoding.DecodeString(cctx.RelayedMessage) - if errDecode != nil { - logger.Err(err).Str("cctx.relayed_message", cctx.RelayedMessage).Msg("Unable to decode relayed message") + // protocol contract v2 uses hex encoding + if cctx.ProtocolContractVersion == types.ProtocolContractVersion_V2 { + message, err = hex.DecodeString(cctx.RelayedMessage) + if err != nil { + logger.Err(err).Msgf("decode CCTX.Message %s error", cctx.RelayedMessage) + message = []byte{} + } } else { - message = msg + msg, errDecode := base64.StdEncoding.DecodeString(cctx.RelayedMessage) + if errDecode != nil { + logger.Err(err).Str("cctx.relayed_message", cctx.RelayedMessage).Msg("Unable to decode relayed message") + } else { + message = msg + } } } @@ -136,6 +138,8 @@ func NewOutboundData( cctxIndex: cctxIndex, outboundParams: outboundParams, + + revertOptions: cctx.RevertOptions, }, false, nil } diff --git a/zetaclient/chains/evm/signer/outbound_data_test.go b/zetaclient/chains/evm/signer/outbound_data_test.go index 03c1329ef1..2f53bab028 100644 --- a/zetaclient/chains/evm/signer/outbound_data_test.go +++ b/zetaclient/chains/evm/signer/outbound_data_test.go @@ -13,15 +13,12 @@ import ( ) func TestNewOutboundData(t *testing.T) { - mockObserver, err := getNewEvmChainObserver(t, nil) - require.NoError(t, err) - logger := zerolog.New(zerolog.NewTestWriter(t)) ctx := makeCtx(t) newOutbound := func(cctx *types.CrossChainTx) (*OutboundData, bool, error) { - return NewOutboundData(ctx, cctx, mockObserver, 123, logger) + return NewOutboundData(ctx, cctx, 123, logger) } t.Run("success", func(t *testing.T) { diff --git a/zetaclient/chains/evm/signer/outbound_tracker_reporter.go b/zetaclient/chains/evm/signer/outbound_tracker_reporter.go new file mode 100644 index 0000000000..7a9b6bcfa2 --- /dev/null +++ b/zetaclient/chains/evm/signer/outbound_tracker_reporter.go @@ -0,0 +1,85 @@ +// Package signer implements the ChainSigner interface for EVM chains +package signer + +import ( + "context" + "time" + + "github.com/rs/zerolog" + + "github.com/zeta-chain/zetacore/pkg/bg" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm/rpc" + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/logs" +) + +// reportToOutboundTracker reports outboundHash to tracker only when tx receipt is available +func (signer *Signer) reportToOutboundTracker( + ctx context.Context, + zetacoreClient interfaces.ZetacoreClient, + chainID int64, + nonce uint64, + outboundHash string, + logger zerolog.Logger, +) { + // prepare logger + logger = logger.With(). + Str(logs.FieldMethod, "reportToOutboundTracker"). + Int64(logs.FieldChain, chainID). + Uint64(logs.FieldNonce, nonce). + Str(logs.FieldTx, outboundHash). + Logger() + + // set being reported flag to avoid duplicate reporting + alreadySet := signer.SetBeingReportedFlag(outboundHash) + if alreadySet { + logger.Info().Msg("outbound is being reported to tracker") + return + } + + // launch a goroutine to monitor tx confirmation status + bg.Work(ctx, func(ctx context.Context) error { + defer func() { + signer.ClearBeingReportedFlag(outboundHash) + }() + + // try monitoring tx inclusion status for 20 minutes + tStart := time.Now() + for { + // take a rest between each check + time.Sleep(10 * time.Second) + + // give up (forget about the tx) after 20 minutes of monitoring, there are 2 reasons: + // 1. the gas stability pool should have kicked in and replaced the tx by then. + // 2. even if there is a chance that the tx is included later, most likely it's going to be a false tx hash (either replaced or dropped). + // 3. we prefer missed tx hash over potentially invalid txhash. + if time.Since(tStart) > evm.OutboundInclusionTimeout { + logger.Info().Msgf("timeout waiting outbound inclusion") + return nil + } + + // check tx confirmation status + confirmed, err := rpc.IsTxConfirmed(ctx, signer.client, outboundHash, evm.ReorgProtectBlockCount) + if err != nil { + logger.Err(err).Msg("unable to check confirmation status of outbound") + continue + } + if !confirmed { + continue + } + + // report outbound hash to tracker + zetaHash, err := zetacoreClient.AddOutboundTracker(ctx, chainID, nonce, outboundHash, nil, "", -1) + if err != nil { + logger.Err(err).Msg("error adding outbound to tracker") + } else if zetaHash != "" { + logger.Info().Msgf("added outbound to tracker; zeta txhash %s", zetaHash) + } else { + // exit goroutine until the tracker contains the hash (reported by either this or other signers) + logger.Info().Msg("outbound now exists in tracker") + return nil + } + } + }, bg.WithName("TrackerReporterEVM"), bg.WithLogger(logger)) +} diff --git a/zetaclient/chains/evm/signer/sign.go b/zetaclient/chains/evm/signer/sign.go new file mode 100644 index 0000000000..b524849786 --- /dev/null +++ b/zetaclient/chains/evm/signer/sign.go @@ -0,0 +1,230 @@ +package signer + +import ( + "context" + "fmt" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + connectorevm "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.base.sol" + + "github.com/zeta-chain/zetacore/zetaclient/chains/evm" +) + +// SignConnectorOnReceive +// function onReceive( +// +// bytes calldata originSenderAddress, +// uint256 originChainId, +// address destinationAddress, +// uint zetaAmount, +// bytes calldata message, +// bytes32 internalSendHash +// +// ) external virtual {} +func (signer *Signer) SignConnectorOnReceive(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + var data []byte + var err error + + zetaConnectorABI, err := connectorevm.ZetaConnectorBaseMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get ZetaConnectorZEVMMetaData ABI") + } + + data, err = zetaConnectorABI.Pack("onReceive", + txData.sender.Bytes(), + txData.srcChainID, + txData.to, + txData.amount, + txData.message, + txData.cctxIndex) + if err != nil { + return nil, errors.Wrap(err, "onReceive pack error") + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.zetaConnectorAddress, + zeroValue, + txData.gas, + txData.nonce, + txData.height) + if err != nil { + return nil, errors.Wrap(err, "sign onReceive error") + } + + return tx, nil +} + +// SignConnectorOnRevert +// function onRevert( +// address originSenderAddress, +// uint256 originChainId, +// bytes calldata destinationAddress, +// uint256 destinationChainId, +// uint256 zetaAmount, +// bytes calldata message, +// bytes32 internalSendHash +// ) external override whenNotPaused onlyTssAddress +func (signer *Signer) SignConnectorOnRevert(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + var data []byte + var err error + + zetaConnectorABI, err := connectorevm.ZetaConnectorBaseMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get ZetaConnectorZEVMMetaData ABI") + } + + data, err = zetaConnectorABI.Pack("onRevert", + txData.sender, + txData.srcChainID, + txData.to.Bytes(), + txData.toChainID, + txData.amount, + txData.message, + txData.cctxIndex) + if err != nil { + return nil, errors.Wrap(err, "onRevert pack error") + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.zetaConnectorAddress, + zeroValue, + txData.gas, + txData.nonce, + txData.height) + if err != nil { + return nil, errors.Wrap(err, "sign onRevert error") + } + + return tx, nil +} + +// SignCancel signs a transaction from TSS address to itself with a zero amount in order to increment the nonce +func (signer *Signer) SignCancel(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + txData.gas.Limit = evm.EthTransferGasLimit + tx, _, _, err := signer.Sign( + ctx, + nil, + signer.TSS().EVMAddress(), + zeroValue, // zero out the amount to cancel the tx + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, errors.Wrap(err, "SignCancel error") + } + + return tx, nil +} + +// SignGasWithdraw signs a withdrawal transaction sent from the TSS address to the destination +func (signer *Signer) SignGasWithdraw(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + txData.gas.Limit = evm.EthTransferGasLimit + tx, _, _, err := signer.Sign( + ctx, + nil, + txData.to, + txData.amount, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, errors.Wrap(err, "SignGasWithdraw error") + } + + return tx, nil +} + +// SignERC20Withdraw +// function withdraw( +// address recipient, +// address asset, +// uint256 amount, +// ) external onlyTssAddress +func (signer *Signer) SignERC20Withdraw(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + var data []byte + var err error + + erc20CustodyV1ABI, err := erc20custody.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get ERC20CustodyMetaData ABI") + } + + data, err = erc20CustodyV1ABI.Pack("withdraw", txData.to, txData.asset, txData.amount) + if err != nil { + return nil, fmt.Errorf("withdraw pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.er20CustodyAddress, + zeroValue, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign withdraw error: %w", err) + } + + return tx, nil +} + +// SignWhitelistERC20Cmd signs a whitelist command for ERC20 token +func (signer *Signer) SignWhitelistERC20Cmd( + ctx context.Context, + txData *OutboundData, + params string, +) (*ethtypes.Transaction, error) { + erc20 := ethcommon.HexToAddress(params) + if erc20 == (ethcommon.Address{}) { + return nil, fmt.Errorf("SignCommandTx: invalid erc20 address %s", params) + } + custodyAbi, err := erc20custody.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, err + } + data, err := custodyAbi.Pack("whitelist", erc20) + if err != nil { + return nil, fmt.Errorf("whitelist pack error: %w", err) + } + tx, _, _, err := signer.Sign( + ctx, + data, + txData.to, + zeroValue, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign whitelist error: %w", err) + } + return tx, nil +} + +// SignMigrateTssFundsCmd signs a migrate TSS funds command +func (signer *Signer) SignMigrateTssFundsCmd(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + tx, _, _, err := signer.Sign( + ctx, + nil, + txData.to, + txData.amount, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("SignMigrateTssFundsCmd error: %w", err) + } + return tx, nil +} diff --git a/zetaclient/chains/evm/signer/sign_test.go b/zetaclient/chains/evm/signer/sign_test.go new file mode 100644 index 0000000000..e8fdca3f24 --- /dev/null +++ b/zetaclient/chains/evm/signer/sign_test.go @@ -0,0 +1,252 @@ +package signer + +import ( + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/rs/zerolog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" + "math/big" + "testing" +) + +func TestSigner_SignConnectorOnReceive(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + + cctx := getCCTX(t) + require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + require.False(t, skip) + require.NoError(t, err) + + t.Run("SignConnectorOnReceive - should successfully sign", func(t *testing.T) { + // Call SignConnectorOnReceive + tx, err := evmSigner.SignConnectorOnReceive(ctx, txData) + require.NoError(t, err) + + // Verify Signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + }) + t.Run("SignConnectorOnReceive - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + // Call SignConnectorOnReceive + tx, err := evmSigner.SignConnectorOnReceive(ctx, txData) + require.ErrorContains(t, err, "sign onReceive error") + require.Nil(t, tx) + tss.Unpause() + }) + + t.Run("SignOutbound - should successfully sign LegacyTx", func(t *testing.T) { + // Call SignOutbound + tx, err := evmSigner.SignConnectorOnReceive(ctx, txData) + require.NoError(t, err) + + // Verify Signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // check that by default tx type is legacy tx + assert.Equal(t, ethtypes.LegacyTxType, int(tx.Type())) + }) + + t.Run("SignOutbound - should successfully sign DynamicFeeTx", func(t *testing.T) { + // ARRANGE + const ( + gwei = 1_000_000_000 + priorityFee = 1 * gwei + gasPrice = 3 * gwei + ) + + // Given a CCTX with gas price and priority fee + cctx := getCCTX(t) + cctx.OutboundParams[0].GasPrice = big.NewInt(gasPrice).String() + cctx.OutboundParams[0].GasPriorityFee = big.NewInt(priorityFee).String() + + // Given outbound data + txData, skip, err := NewOutboundData(ctx, cctx, 123, makeLogger(t)) + require.False(t, skip) + require.NoError(t, err) + + // Given a working TSS + tss.Unpause() + + // ACT + tx, err := evmSigner.SignConnectorOnReceive(ctx, txData) + require.NoError(t, err) + + // ASSERT + verifyTxSignature(t, tx, mocks.NewTSSMainnet().Pubkey(), evmSigner.EvmSigner()) + + // check that by default tx type is a dynamic fee tx + assert.Equal(t, ethtypes.DynamicFeeTxType, int(tx.Type())) + + // check that the gasPrice & priorityFee are set correctly + assert.Equal(t, int64(gasPrice), tx.GasFeeCap().Int64()) + assert.Equal(t, int64(priorityFee), tx.GasTipCap().Int64()) + }) +} + +func TestSigner_SignConnectorOnRevert(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + require.False(t, skip) + require.NoError(t, err) + + t.Run("SignConnectorOnRevert - should successfully sign", func(t *testing.T) { + // Call SignConnectorOnRevert + tx, err := evmSigner.SignConnectorOnRevert(ctx, txData) + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + // Note: Revert tx calls connector contract with 0 gas token + verifyTxBodyBasics(t, tx, evmSigner.zetaConnectorAddress, txData.nonce, big.NewInt(0)) + }) + t.Run("SignConnectorOnRevert - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + // Call SignConnectorOnRevert + tx, err := evmSigner.SignConnectorOnRevert(ctx, txData) + require.ErrorContains(t, err, "sign onRevert error") + require.Nil(t, tx) + }) +} + +func TestSigner_SignCancel(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + require.False(t, skip) + require.NoError(t, err) + + t.Run("SignCancel - should successfully sign", func(t *testing.T) { + // Call SignConnectorOnRevert + tx, err := evmSigner.SignCancel(ctx, txData) + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + // Note: Cancel tx sends 0 gas token to TSS self address + verifyTxBodyBasics(t, tx, evmSigner.TSS().EVMAddress(), txData.nonce, big.NewInt(0)) + }) + t.Run("SignCancel - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + // Call SignCancel + tx, err := evmSigner.SignCancel(ctx, txData) + require.ErrorContains(t, err, "SignCancel error") + require.Nil(t, tx) + }) +} + +func TestSigner_SignGasWithdraw(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + require.False(t, skip) + require.NoError(t, err) + + t.Run("SignGasWithdraw - should successfully sign", func(t *testing.T) { + // Call SignGasWithdraw + tx, err := evmSigner.SignGasWithdraw(ctx, txData) + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) + }) + t.Run("SignGasWithdraw - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + // Call SignGasWithdraw + tx, err := evmSigner.SignGasWithdraw(ctx, txData) + require.ErrorContains(t, err, "SignGasWithdraw error") + require.Nil(t, tx) + }) +} + +func TestSigner_SignERC20Withdraw(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + require.False(t, skip) + require.NoError(t, err) + + t.Run("SignERC20WithdrawTx - should successfully sign", func(t *testing.T) { + // Call SignERC20WithdrawTx + tx, err := evmSigner.SignERC20Withdraw(ctx, txData) + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + // Note: Withdraw tx calls erc20 custody contract with 0 gas token + verifyTxBodyBasics(t, tx, evmSigner.er20CustodyAddress, txData.nonce, big.NewInt(0)) + }) + + t.Run("SignERC20WithdrawTx - should fail if keysign fails", func(t *testing.T) { + // pause tss to make keysign fail + tss.Pause() + + // Call SignERC20WithdrawTx + tx, err := evmSigner.SignERC20Withdraw(ctx, txData) + require.ErrorContains(t, err, "sign withdraw error") + require.Nil(t, tx) + }) +} diff --git a/zetaclient/chains/evm/signer/signer.go b/zetaclient/chains/evm/signer/signer.go index d30b887ea8..c77d4e36e6 100644 --- a/zetaclient/chains/evm/signer/signer.go +++ b/zetaclient/chains/evm/signer/signer.go @@ -10,29 +10,24 @@ import ( "strings" "time" - "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" ethcommon "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" ethrpc "github.com/ethereum/go-ethereum/rpc" + "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" - "github.com/zeta-chain/zetacore/pkg/constant" - crosschainkeeper "github.com/zeta-chain/zetacore/x/crosschain/keeper" - "github.com/zeta-chain/zetacore/x/crosschain/types" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" - "github.com/zeta-chain/zetacore/zetaclient/chains/evm" - "github.com/zeta-chain/zetacore/zetaclient/chains/evm/observer" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" "github.com/zeta-chain/zetacore/zetaclient/compliance" zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/logs" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outboundprocessor" "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" @@ -64,17 +59,14 @@ type Signer struct { // ethSigner encapsulates EVM transaction signature handling ethSigner ethtypes.Signer - // zetaConnectorABI is the ABI of the ZetaConnector contract - zetaConnectorABI abi.ABI - - // erc20CustodyABI is the ABI of the ERC20Custody contract - erc20CustodyABI abi.ABI - // zetaConnectorAddress is the address of the ZetaConnector contract zetaConnectorAddress ethcommon.Address // er20CustodyAddress is the address of the ERC20Custody contract er20CustodyAddress ethcommon.Address + + // gatewayAddress is the address of the Gateway contract + gatewayAddress ethcommon.Address } // NewSigner creates a new EVM signer @@ -85,10 +77,9 @@ func NewSigner( ts *metrics.TelemetryServer, logger base.Logger, endpoint string, - zetaConnectorABI string, - erc20CustodyABI string, zetaConnectorAddress ethcommon.Address, erc20CustodyAddress ethcommon.Address, + gatewayAddress ethcommon.Address, ) (*Signer, error) { // create base signer baseSigner := base.NewSigner(chain, tss, ts, logger) @@ -99,25 +90,13 @@ func NewSigner( return nil, errors.Wrap(err, "unable to create EVM client") } - // prepare ABIs - connectorABI, err := abi.JSON(strings.NewReader(zetaConnectorABI)) - if err != nil { - return nil, errors.Wrap(err, "unable to build ZetaConnector ABI") - } - - custodyABI, err := abi.JSON(strings.NewReader(erc20CustodyABI)) - if err != nil { - return nil, errors.Wrap(err, "unable to build ERC20Custody ABI") - } - return &Signer{ Signer: baseSigner, client: client, ethSigner: ethSigner, - zetaConnectorABI: connectorABI, - erc20CustodyABI: custodyABI, zetaConnectorAddress: zetaConnectorAddress, er20CustodyAddress: erc20CustodyAddress, + gatewayAddress: gatewayAddress, }, nil } @@ -157,9 +136,9 @@ func (signer *Signer) GetERC20CustodyAddress() ethcommon.Address { // GetGatewayAddress returns the gateway address func (signer *Signer) GetGatewayAddress() string { - // Note: return empty string for now - // gateway address will be needed in the future contract architecture - return "" + signer.Lock() + defer signer.Unlock() + return signer.gatewayAddress.String() } // Sign given data, and metadata (gas, nonce, etc) @@ -248,196 +227,51 @@ func (signer *Signer) broadcast(ctx context.Context, tx *ethtypes.Transaction) e return signer.client.SendTransaction(ctx, tx) } -// SignOutbound -// function onReceive( -// -// bytes calldata originSenderAddress, -// uint256 originChainId, -// address destinationAddress, -// uint zetaAmount, -// bytes calldata message, -// bytes32 internalSendHash -// -// ) external virtual {} -func (signer *Signer) SignOutbound(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { - var data []byte - var err error - - data, err = signer.zetaConnectorABI.Pack("onReceive", - txData.sender.Bytes(), - txData.srcChainID, - txData.to, - txData.amount, - txData.message, - txData.cctxIndex) - if err != nil { - return nil, fmt.Errorf("onReceive pack error: %w", err) - } - - tx, _, _, err := signer.Sign( - ctx, - data, - signer.zetaConnectorAddress, - zeroValue, - txData.gas, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("sign onReceive error: %w", err) - } - - return tx, nil -} - -// SignRevertTx -// function onRevert( -// address originSenderAddress, -// uint256 originChainId, -// bytes calldata destinationAddress, -// uint256 destinationChainId, -// uint256 zetaAmount, -// bytes calldata message, -// bytes32 internalSendHash -// ) external override whenNotPaused onlyTssAddress -func (signer *Signer) SignRevertTx(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { - data, err := signer.zetaConnectorABI.Pack("onRevert", - txData.sender, - txData.srcChainID, - txData.to.Bytes(), - txData.toChainID, - txData.amount, - txData.message, - txData.cctxIndex, - ) - if err != nil { - return nil, fmt.Errorf("onRevert pack error: %w", err) - } - - tx, _, _, err := signer.Sign( - ctx, - data, - signer.zetaConnectorAddress, - zeroValue, - txData.gas, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("sign onRevert error: %w", err) - } - - return tx, nil -} - -// SignCancelTx signs a transaction from TSS address to itself with a zero amount in order to increment the nonce -func (signer *Signer) SignCancelTx(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { - txData.gas.Limit = evm.EthTransferGasLimit - tx, _, _, err := signer.Sign( - ctx, - nil, - signer.TSS().EVMAddress(), - zeroValue, // zero out the amount to cancel the tx - txData.gas, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("SignCancelTx error: %w", err) - } - - return tx, nil -} - -// SignWithdrawTx signs a withdrawal transaction sent from the TSS address to the destination -func (signer *Signer) SignWithdrawTx(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { - txData.gas.Limit = evm.EthTransferGasLimit - tx, _, _, err := signer.Sign( - ctx, - nil, - txData.to, - txData.amount, - txData.gas, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("SignWithdrawTx error: %w", err) - } - - return tx, nil -} - -// SignCommandTx signs a transaction based on the given command includes: -// -// cmd_whitelist_erc20 -// cmd_migrate_tss_funds -func (signer *Signer) SignCommandTx( - ctx context.Context, - txData *OutboundData, - cmd string, - params string, -) (*ethtypes.Transaction, error) { - switch cmd { - case constant.CmdWhitelistERC20: - return signer.SignWhitelistERC20Cmd(ctx, txData, params) - case constant.CmdMigrateTssFunds: - return signer.SignMigrateTssFundsCmd(ctx, txData) - } - return nil, fmt.Errorf("SignCommandTx: unknown command %s", cmd) -} - // TryProcessOutbound - signer interface implementation // This function will attempt to build and sign an evm transaction using the TSS signer. // It will then broadcast the signed transaction to the outbound chain. // TODO(revamp): simplify function func (signer *Signer) TryProcessOutbound( ctx context.Context, - cctx *types.CrossChainTx, + cctx *crosschaintypes.CrossChainTx, outboundProc *outboundprocessor.Processor, outboundID string, - chainObserver interfaces.ChainObserver, + _ interfaces.ChainObserver, zetacoreClient interfaces.ZetacoreClient, height uint64, ) { + // end outbound process on panic + defer func() { + outboundProc.EndTryProcess(outboundID) + if r := recover(); r != nil { + signer.Logger().Std.Error().Msgf("TryProcessOutbound: %s, caught panic error: %v", cctx.Index, r) + } + }() + + // prepare logger and a few local variables var ( params = cctx.GetCurrentOutboundParam() myID = zetacoreClient.GetKeys().GetOperatorAddress() logger = signer.Logger().Std.With(). - Str("method", "TryProcessOutbound"). - Int64("chain", signer.Chain().ChainId). - Uint64("nonce", params.TssNonce). - Str("cctx.index", cctx.Index). + Str(logs.FieldMethod, "TryProcessOutbound"). + Int64(logs.FieldChain, signer.Chain().ChainId). + Uint64(logs.FieldNonce, params.TssNonce). + Str(logs.FieldCctx, cctx.Index). Str("cctx.receiver", params.Receiver). Str("cctx.amount", params.Amount.String()). Logger() ) - logger.Info().Msgf("TryProcessOutbound") + // retrieve app context app, err := zctx.FromContext(ctx) if err != nil { - signer.Logger().Std.Error().Err(err).Msg("error getting app context") - return - } - - // end outbound process on panic - defer func() { - if r := recover(); r != nil { - logger.Error().Interface("panic", r).Msg("panic in TryProcessOutbound") - } - - outboundProc.EndTryProcess(outboundID) - }() - - evmObserver, ok := chainObserver.(*observer.Observer) - if !ok { - logger.Error().Msg("chain observer is not an EVM observer") + logger.Error().Err(err).Msg("error getting app context") return } // Setup Transaction input - txData, skipTx, err := NewOutboundData(ctx, cctx, evmObserver, height, logger) + txData, skipTx, err := NewOutboundData(ctx, cctx, height, logger) if err != nil { logger.Err(err).Msg("error setting up transaction input fields") return @@ -458,10 +292,44 @@ func (signer *Signer) TryProcessOutbound( return } - // https://github.com/zeta-chain/node/issues/2050 - var tx *ethtypes.Transaction - // compliance check goes first + // sign outbound + tx, err := signer.SignOutboundFromCCTX( + ctx, + logger, + cctx, + txData, + zetacoreClient, + toChain, + ) + if err != nil { + logger.Err(err).Msg("error signing outbound") + return + } + + logger.Info().Msgf( + "Key-sign success: %d => %d, nonce %d", + cctx.InboundParams.SenderChainId, + toChain.ID(), + cctx.GetCurrentOutboundParam().TssNonce, + ) + + // Broadcast Signed Tx + signer.BroadcastOutbound(ctx, tx, cctx, logger, myID, zetacoreClient, txData) +} + +// SignOutboundFromCCTX signs an outbound transaction from a given cctx +// TODO: simplify logic with all if else +// https://github.com/zeta-chain/node/issues/2050 +func (signer *Signer) SignOutboundFromCCTX( + ctx context.Context, + logger zerolog.Logger, + cctx *crosschaintypes.CrossChainTx, + outboundData *OutboundData, + zetacoreClient interfaces.ZetacoreClient, + toChain zctx.Chain, +) (*ethtypes.Transaction, error) { if compliance.IsCctxRestricted(cctx) { + // restricted cctx compliance.PrintComplianceLog( logger, signer.Logger().Compliance, @@ -469,25 +337,20 @@ func (signer *Signer) TryProcessOutbound( signer.Chain().ChainId, cctx.Index, cctx.InboundParams.Sender, - txData.to.Hex(), + outboundData.to.Hex(), cctx.GetCurrentOutboundParam().CoinType.String(), ) - tx, err = signer.SignCancelTx(ctx, txData) // cancel the tx - if err != nil { - logger.Warn().Err(err).Msg(ErrorMsg(cctx)) - return - } - } else if cctx.InboundParams.CoinType == coin.CoinType_Cmd { // admin command + return signer.SignCancel(ctx, outboundData) + } else if cctx.InboundParams.CoinType == coin.CoinType_Cmd { + // admin command to := ethcommon.HexToAddress(cctx.GetCurrentOutboundParam().Receiver) if to == (ethcommon.Address{}) { - logger.Error().Msgf("invalid receiver %s", cctx.GetCurrentOutboundParam().Receiver) - return + return nil, fmt.Errorf("invalid receiver %s", cctx.GetCurrentOutboundParam().Receiver) } msg := strings.Split(cctx.RelayedMessage, ":") if len(msg) != 2 { - logger.Error().Msgf("invalid message %s", msg) - return + return nil, fmt.Errorf("invalid message %s", msg) } // cmd field is used to determine whether to execute ERC20 whitelist or migrate TSS funds given that the coin type // from the cctx is coin.CoinType_Cmd @@ -495,126 +358,100 @@ func (signer *Signer) TryProcessOutbound( // params field is used to pass input parameters for command requests, currently it is used to pass the ERC20 // contract address when a whitelist command is requested params := msg[1] - tx, err = signer.SignCommandTx(ctx, txData, cmd, params) - if err != nil { - logger.Warn().Err(err).Msg(ErrorMsg(cctx)) - return - } + return signer.SignAdminTx(ctx, outboundData, cmd, params) + } else if cctx.ProtocolContractVersion == crosschaintypes.ProtocolContractVersion_V2 { + // call sign outbound from cctx for v2 protocol contracts + return signer.SignOutboundFromCCTXV2(ctx, cctx, outboundData) } else if IsSenderZetaChain(cctx, zetacoreClient) { switch cctx.InboundParams.CoinType { case coin.CoinType_Gas: logger.Info().Msgf( - "SignWithdrawTx: %d => %d, nonce %d, gasPrice %d", + "SignGasWithdraw: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, - txData.gas.Price, + outboundData.gas.Price, ) - tx, err = signer.SignWithdrawTx(ctx, txData) + return signer.SignGasWithdraw(ctx, outboundData) case coin.CoinType_ERC20: logger.Info().Msgf( - "SignERC20WithdrawTx: %d => %d, nonce %d, gasPrice %d", + "SignERC20Withdraw: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, - txData.gas.Price, + outboundData.gas.Price, ) - tx, err = signer.SignERC20WithdrawTx(ctx, txData) + return signer.SignERC20Withdraw(ctx, outboundData) case coin.CoinType_Zeta: logger.Info().Msgf( - "SignOutbound: %d => %d, nonce %d, gasPrice %d", + "SignConnectorOnReceive: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, - txData.gas.Price, + outboundData.gas.Price, ) - tx, err = signer.SignOutbound(ctx, txData) - } - if err != nil { - logger.Warn().Err(err).Msg(ErrorMsg(cctx)) - return + return signer.SignConnectorOnReceive(ctx, outboundData) } - } else if cctx.CctxStatus.Status == types.CctxStatus_PendingRevert && cctx.OutboundParams[0].ReceiverChainId == zetacoreClient.Chain().ChainId { + } else if cctx.CctxStatus.Status == crosschaintypes.CctxStatus_PendingRevert && cctx.OutboundParams[0].ReceiverChainId == zetacoreClient.Chain().ChainId { switch cctx.InboundParams.CoinType { case coin.CoinType_Zeta: logger.Info().Msgf( - "SignRevertTx: %d => %d, nonce %d, gasPrice %d", + "SignConnectorOnRevert: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, - txData.gas.Price, + outboundData.gas.Price, ) - txData.srcChainID = big.NewInt(cctx.OutboundParams[0].ReceiverChainId) - txData.toChainID = big.NewInt(cctx.GetCurrentOutboundParam().ReceiverChainId) - tx, err = signer.SignRevertTx(ctx, txData) + outboundData.srcChainID = big.NewInt(cctx.OutboundParams[0].ReceiverChainId) + outboundData.toChainID = big.NewInt(cctx.GetCurrentOutboundParam().ReceiverChainId) + return signer.SignConnectorOnRevert(ctx, outboundData) case coin.CoinType_Gas: logger.Info().Msgf( - "SignWithdrawTx: %d => %d, nonce %d, gasPrice %d", + "SignGasWithdraw: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, - txData.gas.Price, + outboundData.gas.Price, ) - tx, err = signer.SignWithdrawTx(ctx, txData) + return signer.SignGasWithdraw(ctx, outboundData) case coin.CoinType_ERC20: - logger.Info().Msgf("SignERC20WithdrawTx: %d => %d, nonce %d, gasPrice %d", + logger.Info().Msgf("SignERC20Withdraw: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, - txData.gas.Price, + outboundData.gas.Price, ) - tx, err = signer.SignERC20WithdrawTx(ctx, txData) - } - if err != nil { - logger.Warn().Err(err).Msg(ErrorMsg(cctx)) - return + return signer.SignERC20Withdraw(ctx, outboundData) } - } else if cctx.CctxStatus.Status == types.CctxStatus_PendingRevert { + } else if cctx.CctxStatus.Status == crosschaintypes.CctxStatus_PendingRevert { logger.Info().Msgf( - "SignRevertTx: %d => %d, nonce %d, gasPrice %d", + "SignConnectorOnRevert: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, - txData.gas.Price, + outboundData.gas.Price, ) - txData.srcChainID = big.NewInt(cctx.OutboundParams[0].ReceiverChainId) - txData.toChainID = big.NewInt(cctx.GetCurrentOutboundParam().ReceiverChainId) - - tx, err = signer.SignRevertTx(ctx, txData) - if err != nil { - logger.Warn().Err(err).Msg(ErrorMsg(cctx)) - return - } - } else if cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound { + outboundData.srcChainID = big.NewInt(cctx.OutboundParams[0].ReceiverChainId) + outboundData.toChainID = big.NewInt(cctx.GetCurrentOutboundParam().ReceiverChainId) + return signer.SignConnectorOnRevert(ctx, outboundData) + } else if cctx.CctxStatus.Status == crosschaintypes.CctxStatus_PendingOutbound { logger.Info().Msgf( - "SignOutbound: %d => %d, nonce %d, gasPrice %d", + "SignConnectorOnReceive: %d => %d, nonce %d, gasPrice %d", cctx.InboundParams.SenderChainId, toChain.ID(), cctx.GetCurrentOutboundParam().TssNonce, - txData.gas.Price, + outboundData.gas.Price, ) - tx, err = signer.SignOutbound(ctx, txData) - if err != nil { - logger.Warn().Err(err).Msg(ErrorMsg(cctx)) - return - } + return signer.SignConnectorOnReceive(ctx, outboundData) } - logger.Info().Msgf( - "Key-sign success: %d => %d, nonce %d", - cctx.InboundParams.SenderChainId, - toChain.ID(), - cctx.GetCurrentOutboundParam().TssNonce, - ) - - // Broadcast Signed Tx - signer.BroadcastOutbound(ctx, tx, cctx, logger, myID, zetacoreClient, txData) + return nil, fmt.Errorf("SignOutboundFromCCTX: can't determine how to sign outbound from cctx %s", cctx.String()) } // BroadcastOutbound signed transaction through evm rpc client func (signer *Signer) BroadcastOutbound( ctx context.Context, tx *ethtypes.Transaction, - cctx *types.CrossChainTx, + cctx *crosschaintypes.CrossChainTx, logger zerolog.Logger, myID sdk.AccAddress, zetacoreClient interfaces.ZetacoreClient, @@ -675,34 +512,6 @@ func (signer *Signer) BroadcastOutbound( } } -// SignERC20WithdrawTx -// function withdraw( -// address recipient, -// address asset, -// uint256 amount, -// ) external onlyTssAddress -func (signer *Signer) SignERC20WithdrawTx(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { - data, err := signer.erc20CustodyABI.Pack("withdraw", txData.to, txData.asset, txData.amount) - if err != nil { - return nil, fmt.Errorf("withdraw pack error: %w", err) - } - - tx, _, _, err := signer.Sign( - ctx, - data, - signer.er20CustodyAddress, - zeroValue, - txData.gas, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("sign withdraw error: %w", err) - } - - return tx, nil -} - // EvmClient returns the EVM RPC client func (signer *Signer) EvmClient() interfaces.EVMRPCClient { return signer.client @@ -717,188 +526,22 @@ func (signer *Signer) EvmSigner() ethtypes.Signer { // IsSenderZetaChain checks if the sender chain is ZetaChain // TODO(revamp): move to another package more general for cctx functions func IsSenderZetaChain( - cctx *types.CrossChainTx, + cctx *crosschaintypes.CrossChainTx, zetacoreClient interfaces.ZetacoreClient, ) bool { return cctx.InboundParams.SenderChainId == zetacoreClient.Chain().ChainId && - cctx.CctxStatus.Status == types.CctxStatus_PendingOutbound + cctx.CctxStatus.Status == crosschaintypes.CctxStatus_PendingOutbound } -// ErrorMsg returns a error message for SignOutbound failure with cctx data -func ErrorMsg(cctx *types.CrossChainTx) string { +// ErrorMsg returns a error message for SignConnectorOnReceive failure with cctx data +func ErrorMsg(cctx *crosschaintypes.CrossChainTx) string { return fmt.Sprintf( - "signer SignOutbound error: nonce %d chain %d", + "signer SignConnectorOnReceive error: nonce %d chain %d", cctx.GetCurrentOutboundParam().TssNonce, cctx.GetCurrentOutboundParam().ReceiverChainId, ) } -// SignWhitelistERC20Cmd signs a whitelist command for ERC20 token -// TODO(revamp): move the cmd in a specific file -func (signer *Signer) SignWhitelistERC20Cmd( - ctx context.Context, - txData *OutboundData, - params string, -) (*ethtypes.Transaction, error) { - outboundParams := txData.outboundParams - erc20 := ethcommon.HexToAddress(params) - if erc20 == (ethcommon.Address{}) { - return nil, fmt.Errorf("SignCommandTx: invalid erc20 address %s", params) - } - - custodyAbi, err := erc20custody.ERC20CustodyMetaData.GetAbi() - if err != nil { - return nil, err - } - - data, err := custodyAbi.Pack("whitelist", erc20) - if err != nil { - return nil, fmt.Errorf("whitelist pack error: %w", err) - } - - tx, _, _, err := signer.Sign( - ctx, - data, - txData.to, - zeroValue, - txData.gas, - outboundParams.TssNonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("sign whitelist error: %w", err) - } - - return tx, nil -} - -// SignMigrateTssFundsCmd signs a migrate TSS funds command -// TODO(revamp): move the cmd in a specific file -func (signer *Signer) SignMigrateTssFundsCmd(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { - tx, _, _, err := signer.Sign( - ctx, - nil, - txData.to, - txData.amount, - txData.gas, - txData.nonce, - txData.height, - ) - if err != nil { - return nil, fmt.Errorf("SignMigrateTssFundsCmd error: %w", err) - } - return tx, nil -} - -// reportToOutboundTracker reports outboundHash to tracker only when tx receipt is available -// TODO(revamp): move outbound tracker function to a outbound tracker file -func (signer *Signer) reportToOutboundTracker( - ctx context.Context, - zetacoreClient interfaces.ZetacoreClient, - chainID int64, - nonce uint64, - outboundHash string, - logger zerolog.Logger, -) { - // set being reported flag to avoid duplicate reporting - alreadySet := signer.Signer.SetBeingReportedFlag(outboundHash) - if alreadySet { - logger.Info(). - Msgf("reportToOutboundTracker: outboundHash %s for chain %d nonce %d is being reported", outboundHash, chainID, nonce) - return - } - - // report to outbound tracker with goroutine - go func() { - defer func() { - signer.Signer.ClearBeingReportedFlag(outboundHash) - }() - - // try monitoring tx inclusion status for 10 minutes - var err error - report := false - isPending := false - blockNumber := uint64(0) - tStart := time.Now() - for { - // give up after 10 minutes of monitoring - time.Sleep(10 * time.Second) - - if time.Since(tStart) > evm.OutboundInclusionTimeout { - // if tx is still pending after timeout, report to outboundTracker anyway as we cannot monitor forever - if isPending { - report = true // probably will be included later - } - logger.Info(). - Msgf("reportToOutboundTracker: timeout waiting tx inclusion for chain %d nonce %d outboundHash %s report %v", chainID, nonce, outboundHash, report) - break - } - // try getting the tx - _, isPending, err = signer.client.TransactionByHash(ctx, ethcommon.HexToHash(outboundHash)) - if err != nil { - logger.Info(). - Err(err). - Msgf("reportToOutboundTracker: error getting tx for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - continue - } - // if tx is include in a block, try getting receipt - if !isPending { - report = true // included - receipt, err := signer.client.TransactionReceipt(ctx, ethcommon.HexToHash(outboundHash)) - if err != nil { - logger.Info(). - Err(err). - Msgf("reportToOutboundTracker: error getting receipt for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - } - if receipt != nil { - blockNumber = receipt.BlockNumber.Uint64() - } - break - } - // keep monitoring pending tx - logger.Info(). - Msgf("reportToOutboundTracker: tx has not been included yet for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - } - - // try adding to outbound tracker for 10 minutes - if report { - tStart := time.Now() - for { - // give up after 10 minutes of retrying - if time.Since(tStart) > evm.OutboundTrackerReportTimeout { - logger.Info(). - Msgf("reportToOutboundTracker: timeout adding outbound tracker for chain %d nonce %d outboundHash %s, please add manually", chainID, nonce, outboundHash) - break - } - // stop if the cctx is already finalized - cctx, err := zetacoreClient.GetCctxByNonce(ctx, chainID, nonce) - if err != nil { - logger.Err(err). - Msgf("reportToOutboundTracker: error getting cctx for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - } else if !crosschainkeeper.IsPending(cctx) { - logger.Info().Msgf("reportToOutboundTracker: cctx already finalized for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - break - } - // report to outbound tracker - zetaHash, err := zetacoreClient.AddOutboundTracker(ctx, chainID, nonce, outboundHash, nil, "", -1) - if err != nil { - logger.Err(err). - Msgf("reportToOutboundTracker: error adding to outbound tracker for chain %d nonce %d outboundHash %s", chainID, nonce, outboundHash) - } else if zetaHash != "" { - logger.Info().Msgf("reportToOutboundTracker: added outboundHash to core successful %s, chain %d nonce %d outboundHash %s block %d", - zetaHash, chainID, nonce, outboundHash, blockNumber) - } else { - // stop if the tracker contains the outboundHash - logger.Info().Msgf("reportToOutboundTracker: outbound tracker contains outboundHash %s for chain %d nonce %d", outboundHash, chainID, nonce) - break - } - // retry otherwise - time.Sleep(evm.ZetaBlockTime * 3) - } - } - }() -} - // getEVMRPC is a helper function to set up the client and signer, also initializes a mock client for unit tests func getEVMRPC(ctx context.Context, endpoint string) (interfaces.EVMRPCClient, ethtypes.Signer, error) { if endpoint == mocks.EVMRPCEnabled { diff --git a/zetaclient/chains/evm/signer/signer_admin.go b/zetaclient/chains/evm/signer/signer_admin.go new file mode 100644 index 0000000000..6c6d8db2d2 --- /dev/null +++ b/zetaclient/chains/evm/signer/signer_admin.go @@ -0,0 +1,170 @@ +package signer + +import ( + "context" + "fmt" + "math/big" + "strings" + + ethcommon "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + + "github.com/zeta-chain/zetacore/pkg/constant" +) + +// SignAdminTx signs a admin cmd transaction based on the given command +func (signer *Signer) SignAdminTx( + ctx context.Context, + txData *OutboundData, + cmd string, + params string, +) (*ethtypes.Transaction, error) { + switch cmd { + case constant.CmdWhitelistERC20: + return signer.signWhitelistERC20Cmd(ctx, txData, params) + case constant.CmdMigrateERC20CustodyFunds: + return signer.signMigrateERC20CustodyFundsCmd(ctx, txData, params) + case constant.CmdUpdateERC20CustodyPauseStatus: + return signer.signUpdateERC20CustodyPauseStatusCmd(ctx, txData, params) + case constant.CmdMigrateTssFunds: + return signer.signMigrateTssFundsCmd(ctx, txData) + } + return nil, fmt.Errorf("SignAdminTx: unknown command %s", cmd) +} + +// signWhitelistERC20Cmd signs a whitelist command for ERC20 token +func (signer *Signer) signWhitelistERC20Cmd( + ctx context.Context, + txData *OutboundData, + params string, +) (*ethtypes.Transaction, error) { + erc20 := ethcommon.HexToAddress(params) + if erc20 == (ethcommon.Address{}) { + return nil, fmt.Errorf("SignAdminTx: invalid erc20 address %s", params) + } + custodyAbi, err := erc20custody.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, err + } + data, err := custodyAbi.Pack("whitelist", erc20) + if err != nil { + return nil, fmt.Errorf("whitelist pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + txData.to, + zeroValue, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign whitelist error: %w", err) + } + return tx, nil +} + +// signMigrateERC20CustodyFundsCmd signs a migrate ERC20 custody funds command +func (signer *Signer) signMigrateERC20CustodyFundsCmd( + ctx context.Context, + txData *OutboundData, + params string, +) (*ethtypes.Transaction, error) { + paramsArray := strings.Split(params, ",") + if len(paramsArray) != 3 { + return nil, fmt.Errorf("signMigrateERC20CustodyFundsCmd: invalid params %s", params) + } + newCustody := ethcommon.HexToAddress(paramsArray[0]) + erc20 := ethcommon.HexToAddress(paramsArray[1]) + amount, ok := new(big.Int).SetString(paramsArray[2], 10) + if !ok { + return nil, fmt.Errorf("signMigrateERC20CustodyFundsCmd: invalid amount %s", paramsArray[2]) + } + + custodyAbi, err := erc20custody.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, err + } + data, err := custodyAbi.Pack("withdraw", newCustody, erc20, amount) + if err != nil { + return nil, fmt.Errorf("withdraw pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + txData.to, + zeroValue, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("signMigrateERC20CustodyFundsCmd error: %w", err) + } + return tx, nil +} + +// signUpdateERC20CustodyPauseStatusCmd signs a update ERC20 custody pause status command +func (signer *Signer) signUpdateERC20CustodyPauseStatusCmd( + ctx context.Context, + txData *OutboundData, + params string, +) (*ethtypes.Transaction, error) { + custodyAbi, err := erc20custody.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, err + } + + // select the action + // NOTE: we could directly do Pack(params) + // having this logic here is more explicit and restrict the possible values + var functionName string + switch params { + case constant.OptionPause: + functionName = "pause" + case constant.OptionUnpause: + functionName = "unpause" + default: + return nil, fmt.Errorf("signUpdateERC20CustodyPauseStatusCmd: invalid params %s", params) + } + + data, err := custodyAbi.Pack(functionName) + if err != nil { + return nil, fmt.Errorf("pause/unpause pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + txData.to, + zeroValue, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("signUpdateERC20CustodyPauseStatusCmd error: %w", err) + } + return tx, nil +} + +// signMigrateTssFundsCmd signs a migrate TSS funds command +func (signer *Signer) signMigrateTssFundsCmd(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + tx, _, _, err := signer.Sign( + ctx, + nil, + txData.to, + txData.amount, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("signMigrateTssFundsCmd error: %w", err) + } + return tx, nil +} diff --git a/zetaclient/chains/evm/signer/signer_admin_test.go b/zetaclient/chains/evm/signer/signer_admin_test.go new file mode 100644 index 0000000000..e1f45d168a --- /dev/null +++ b/zetaclient/chains/evm/signer/signer_admin_test.go @@ -0,0 +1,331 @@ +package signer + +import ( + "fmt" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/constant" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" + "math/big" + "testing" +) + +func TestSigner_SignAdminTx(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + evmSigner, err := getNewEvmSigner(nil) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + + require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + + require.False(t, skip) + require.NoError(t, err) + + t.Run("SignAdminTx CmdWhitelistERC20", func(t *testing.T) { + cmd := constant.CmdWhitelistERC20 + params := ConnectorAddress.Hex() + // Call SignAdminTx + tx, err := evmSigner.SignAdminTx(ctx, txData, cmd, params) + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + // Note: Revert tx calls erc20 custody contract with 0 gas token + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, big.NewInt(0)) + }) + + t.Run("SignAdminTx CmdMigrateERC20CustodyFunds", func(t *testing.T) { + cmd := constant.CmdMigrateERC20CustodyFunds + params := fmt.Sprintf( + "%s,%s,%s", + sample.EthAddress().Hex(), + sample.EthAddress().Hex(), + big.NewInt(100).String(), + ) + // Call SignAdminTx + tx, err := evmSigner.SignAdminTx(ctx, txData, cmd, params) + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + // Note: Revert tx calls erc20 custody contract with 0 gas token + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, big.NewInt(0)) + }) + + t.Run("SignAdminTx CmdUpdateERC20CustodyPauseStatus", func(t *testing.T) { + cmd := constant.CmdUpdateERC20CustodyPauseStatus + params := constant.OptionPause + // Call SignAdminTx + tx, err := evmSigner.SignAdminTx(ctx, txData, cmd, params) + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + // Note: Revert tx calls erc20 custody contract with 0 gas token + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, big.NewInt(0)) + }) + + t.Run("SignAdminTx CmdMigrateTssFunds", func(t *testing.T) { + cmd := constant.CmdMigrateTssFunds + // Call SignAdminTx + tx, err := evmSigner.SignAdminTx(ctx, txData, cmd, "") + require.NoError(t, err) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) + }) +} + +func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + + require.NoError(t, err) + + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + + require.NoError(t, err) + require.False(t, skip) + + t.Run("signWhitelistERC20Cmd - should successfully sign", func(t *testing.T) { + // Call signWhitelistERC20Cmd + tx, err := evmSigner.signWhitelistERC20Cmd(ctx, txData, sample.EthAddress().Hex()) + require.NoError(t, err) + require.NotNil(t, tx) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) + }) + + t.Run("signWhitelistERC20Cmd - should fail on invalid erc20 address", func(t *testing.T) { + tx, err := evmSigner.signWhitelistERC20Cmd(ctx, txData, "") + require.Nil(t, tx) + require.ErrorContains(t, err, "invalid erc20 address") + }) + + t.Run("signWhitelistERC20Cmd - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + // Call signWhitelistERC20Cmd + tx, err := evmSigner.signWhitelistERC20Cmd(ctx, txData, sample.EthAddress().Hex()) + require.ErrorContains(t, err, "sign whitelist error") + require.Nil(t, tx) + }) +} + +func TestSigner_SignMigrateERC20CustodyFundsCmd(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + + require.NoError(t, err) + + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + + require.NoError(t, err) + require.False(t, skip) + + t.Run("signMigrateERC20CustodyFundsCmd - should successfully sign", func(t *testing.T) { + // Call signWhitelistERC20Cmd + + params := fmt.Sprintf( + "%s,%s,%s", + sample.EthAddress().Hex(), + sample.EthAddress().Hex(), + big.NewInt(100).String(), + ) + + tx, err := evmSigner.signMigrateERC20CustodyFundsCmd(ctx, txData, params) + require.NoError(t, err) + require.NotNil(t, tx) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) + }) + + t.Run("signMigrateERC20CustodyFundsCmd - should fail on invalid params", func(t *testing.T) { + + params := fmt.Sprintf("%s,%s", sample.EthAddress().Hex(), sample.EthAddress().Hex()) + + _, err := evmSigner.signMigrateERC20CustodyFundsCmd(ctx, txData, params) + require.ErrorContains(t, err, "invalid params") + }) + + t.Run("signMigrateERC20CustodyFundsCmd - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + params := fmt.Sprintf( + "%s,%s,%s", + sample.EthAddress().Hex(), + sample.EthAddress().Hex(), + big.NewInt(100).String(), + ) + + // Call signWhitelistERC20Cmd + tx, err := evmSigner.signMigrateERC20CustodyFundsCmd(ctx, txData, params) + require.ErrorContains(t, err, "tss is paused") + require.Nil(t, tx) + }) +} + +func TestSigner_SignUpdateERC20CustodyPauseStatusCmd(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + + require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + + require.False(t, skip) + require.NoError(t, err) + + t.Run("signUpdateERC20CustodyPauseStatusCmd - should successfully sign for pause", func(t *testing.T) { + // Call signWhitelistERC20Cmd + + params := constant.OptionPause + + tx, err := evmSigner.signUpdateERC20CustodyPauseStatusCmd(ctx, txData, params) + require.NoError(t, err) + require.NotNil(t, tx) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) + }) + + t.Run("signUpdateERC20CustodyPauseStatusCmd - should successfully sign for unpause", func(t *testing.T) { + // Call signWhitelistERC20Cmd + + params := constant.OptionUnpause + + tx, err := evmSigner.signUpdateERC20CustodyPauseStatusCmd(ctx, txData, params) + require.NoError(t, err) + require.NotNil(t, tx) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) + }) + + t.Run("signUpdateERC20CustodyPauseStatusCmd - should fail on invalid params", func(t *testing.T) { + params := "invalid" + + _, err := evmSigner.signUpdateERC20CustodyPauseStatusCmd(ctx, txData, params) + require.ErrorContains(t, err, "invalid params") + }) + + t.Run("signUpdateERC20CustodyPauseStatusCmd - should fail on empty params", func(t *testing.T) { + params := "" + + _, err := evmSigner.signUpdateERC20CustodyPauseStatusCmd(ctx, txData, params) + require.ErrorContains(t, err, "invalid params") + }) + + t.Run("signUpdateERC20CustodyPauseStatusCmd - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + params := constant.OptionPause + + // Call signWhitelistERC20Cmd + tx, err := evmSigner.signUpdateERC20CustodyPauseStatusCmd(ctx, txData, params) + require.ErrorContains(t, err, "tss is paused") + require.Nil(t, tx) + }) +} + +func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { + ctx := makeCtx(t) + + // Setup evm signer + tss := mocks.NewTSSMainnet() + evmSigner, err := getNewEvmSigner(tss) + require.NoError(t, err) + + // Setup txData struct + cctx := getCCTX(t) + + require.NoError(t, err) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) + + require.False(t, skip) + require.NoError(t, err) + + t.Run("signMigrateTssFundsCmd - should successfully sign", func(t *testing.T) { + // Call signMigrateTssFundsCmd + tx, err := evmSigner.signMigrateTssFundsCmd(ctx, txData) + require.NoError(t, err) + require.NotNil(t, tx) + + // Verify tx signature + tss := mocks.NewTSSMainnet() + verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) + + // Verify tx body basics + verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) + }) + + t.Run("signMigrateTssFundsCmd - should fail if keysign fails", func(t *testing.T) { + // Pause tss to make keysign fail + tss.Pause() + + // Call signMigrateTssFundsCmd + tx, err := evmSigner.signMigrateTssFundsCmd(ctx, txData) + require.ErrorContains(t, err, "signMigrateTssFundsCmd error") + require.Nil(t, tx) + }) +} diff --git a/zetaclient/chains/evm/signer/signer_test.go b/zetaclient/chains/evm/signer/signer_test.go index 9233dfcbe2..3297a301f3 100644 --- a/zetaclient/chains/evm/signer/signer_test.go +++ b/zetaclient/chains/evm/signer/signer_test.go @@ -10,7 +10,6 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" observertypes "github.com/zeta-chain/zetacore/x/observer/types" zctx "github.com/zeta-chain/zetacore/zetaclient/context" @@ -18,7 +17,6 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/keys" "github.com/zeta-chain/zetacore/pkg/chains" - "github.com/zeta-chain/zetacore/pkg/constant" "github.com/zeta-chain/zetacore/testutil/sample" crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" @@ -46,7 +44,7 @@ func getNewEvmSigner(tss interfaces.TSSSigner) (*Signer, error) { tss = mocks.NewTSSMainnet() } - mpiAddress := ConnectorAddress + connectorAddress := ConnectorAddress erc20CustodyAddress := ERC20CustodyAddress logger := base.Logger{} @@ -57,10 +55,9 @@ func getNewEvmSigner(tss interfaces.TSSSigner) (*Signer, error) { nil, logger, mocks.EVMRPCEnabled, - config.GetConnectorABI(), - config.GetERC20CustodyABI(), - mpiAddress, + connectorAddress, erc20CustodyAddress, + sample.EthAddress(), ) } @@ -185,288 +182,6 @@ func TestSigner_TryProcessOutbound(t *testing.T) { require.Len(t, *list, 1) } -func TestSigner_SignOutbound(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - tss := mocks.NewTSSMainnet() - evmSigner, err := getNewEvmSigner(tss) - require.NoError(t, err) - - // Setup txData struct - - cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, makeLogger(t)) - require.False(t, skip) - require.NoError(t, err) - - t.Run("SignOutbound - should successfully sign LegacyTx", func(t *testing.T) { - // Call SignOutbound - tx, err := evmSigner.SignOutbound(ctx, txData) - require.NoError(t, err) - - // Verify Signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // check that by default tx type is legacy tx - assert.Equal(t, ethtypes.LegacyTxType, int(tx.Type())) - }) - t.Run("SignOutbound - should fail if keysign fails", func(t *testing.T) { - // Pause tss to make keysign fail - tss.Pause() - - // Call SignOutbound - tx, err := evmSigner.SignOutbound(ctx, txData) - require.ErrorContains(t, err, "sign onReceive error") - require.Nil(t, tx) - }) - - t.Run("SignOutbound - should successfully sign DynamicFeeTx", func(t *testing.T) { - // ARRANGE - const ( - gwei = 1_000_000_000 - priorityFee = 1 * gwei - gasPrice = 3 * gwei - ) - - // Given a CCTX with gas price and priority fee - cctx := getCCTX(t) - cctx.OutboundParams[0].GasPrice = big.NewInt(gasPrice).String() - cctx.OutboundParams[0].GasPriorityFee = big.NewInt(priorityFee).String() - - // Given outbound data - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, makeLogger(t)) - require.False(t, skip) - require.NoError(t, err) - - // Given a working TSS - tss.Unpause() - - // ACT - tx, err := evmSigner.SignOutbound(ctx, txData) - require.NoError(t, err) - - // ASSERT - verifyTxSignature(t, tx, mocks.NewTSSMainnet().Pubkey(), evmSigner.EvmSigner()) - - // check that by default tx type is a dynamic fee tx - assert.Equal(t, ethtypes.DynamicFeeTxType, int(tx.Type())) - - // check that the gasPrice & priorityFee are set correctly - assert.Equal(t, int64(gasPrice), tx.GasFeeCap().Int64()) - assert.Equal(t, int64(priorityFee), tx.GasTipCap().Int64()) - }) -} - -func TestSigner_SignRevertTx(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - tss := mocks.NewTSSMainnet() - evmSigner, err := getNewEvmSigner(tss) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.False(t, skip) - require.NoError(t, err) - - t.Run("SignRevertTx - should successfully sign", func(t *testing.T) { - // Call SignRevertTx - tx, err := evmSigner.SignRevertTx(ctx, txData) - require.NoError(t, err) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - // Note: Revert tx calls connector contract with 0 gas token - verifyTxBodyBasics(t, tx, evmSigner.zetaConnectorAddress, txData.nonce, big.NewInt(0)) - }) - t.Run("SignRevertTx - should fail if keysign fails", func(t *testing.T) { - // Pause tss to make keysign fail - tss.Pause() - - // Call SignRevertTx - tx, err := evmSigner.SignRevertTx(ctx, txData) - require.ErrorContains(t, err, "sign onRevert error") - require.Nil(t, tx) - }) -} - -func TestSigner_SignCancelTx(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - tss := mocks.NewTSSMainnet() - evmSigner, err := getNewEvmSigner(tss) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.False(t, skip) - require.NoError(t, err) - - t.Run("SignCancelTx - should successfully sign", func(t *testing.T) { - // Call SignRevertTx - tx, err := evmSigner.SignCancelTx(ctx, txData) - require.NoError(t, err) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - // Note: Cancel tx sends 0 gas token to TSS self address - verifyTxBodyBasics(t, tx, evmSigner.TSS().EVMAddress(), txData.nonce, big.NewInt(0)) - }) - t.Run("SignCancelTx - should fail if keysign fails", func(t *testing.T) { - // Pause tss to make keysign fail - tss.Pause() - - // Call SignCancelTx - tx, err := evmSigner.SignCancelTx(ctx, txData) - require.ErrorContains(t, err, "SignCancelTx error") - require.Nil(t, tx) - }) -} - -func TestSigner_SignWithdrawTx(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - tss := mocks.NewTSSMainnet() - evmSigner, err := getNewEvmSigner(tss) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.False(t, skip) - require.NoError(t, err) - - t.Run("SignWithdrawTx - should successfully sign", func(t *testing.T) { - // Call SignWithdrawTx - tx, err := evmSigner.SignWithdrawTx(ctx, txData) - require.NoError(t, err) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) - }) - t.Run("SignWithdrawTx - should fail if keysign fails", func(t *testing.T) { - // Pause tss to make keysign fail - tss.Pause() - - // Call SignWithdrawTx - tx, err := evmSigner.SignWithdrawTx(ctx, txData) - require.ErrorContains(t, err, "SignWithdrawTx error") - require.Nil(t, tx) - }) -} - -func TestSigner_SignCommandTx(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - evmSigner, err := getNewEvmSigner(nil) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, nil) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.False(t, skip) - require.NoError(t, err) - - t.Run("SignCommandTx CmdWhitelistERC20", func(t *testing.T) { - cmd := constant.CmdWhitelistERC20 - params := ConnectorAddress.Hex() - // Call SignCommandTx - tx, err := evmSigner.SignCommandTx(ctx, txData, cmd, params) - require.NoError(t, err) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - // Note: Revert tx calls erc20 custody contract with 0 gas token - verifyTxBodyBasics(t, tx, txData.to, txData.nonce, big.NewInt(0)) - }) - - t.Run("SignCommandTx CmdMigrateTssFunds", func(t *testing.T) { - cmd := constant.CmdMigrateTssFunds - // Call SignCommandTx - tx, err := evmSigner.SignCommandTx(ctx, txData, cmd, "") - require.NoError(t, err) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) - }) -} - -func TestSigner_SignERC20WithdrawTx(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - tss := mocks.NewTSSMainnet() - evmSigner, err := getNewEvmSigner(tss) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.False(t, skip) - require.NoError(t, err) - - t.Run("SignERC20WithdrawTx - should successfully sign", func(t *testing.T) { - // Call SignERC20WithdrawTx - tx, err := evmSigner.SignERC20WithdrawTx(ctx, txData) - require.NoError(t, err) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - // Note: Withdraw tx calls erc20 custody contract with 0 gas token - verifyTxBodyBasics(t, tx, evmSigner.er20CustodyAddress, txData.nonce, big.NewInt(0)) - }) - - t.Run("SignERC20WithdrawTx - should fail if keysign fails", func(t *testing.T) { - // pause tss to make keysign fail - tss.Pause() - - // Call SignERC20WithdrawTx - tx, err := evmSigner.SignERC20WithdrawTx(ctx, txData) - require.ErrorContains(t, err, "sign withdraw error") - require.Nil(t, tx) - }) -} - func TestSigner_BroadcastOutbound(t *testing.T) { ctx := makeCtx(t) @@ -476,16 +191,13 @@ func TestSigner_BroadcastOutbound(t *testing.T) { // Setup txData struct cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, nil) - require.NoError(t, err) - - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) + txData, skip, err := NewOutboundData(ctx, cctx, 123, zerolog.Logger{}) require.NoError(t, err) require.False(t, skip) t.Run("BroadcastOutbound - should successfully broadcast", func(t *testing.T) { - // Call SignERC20WithdrawTx - tx, err := evmSigner.SignERC20WithdrawTx(ctx, txData) + // Call SignERC20Withdraw + tx, err := evmSigner.SignERC20Withdraw(ctx, txData) require.NoError(t, err) evmSigner.BroadcastOutbound( @@ -522,95 +234,8 @@ func TestSigner_SignerErrorMsg(t *testing.T) { require.Contains(t, msg, "nonce 68270 chain 56") } -func TestSigner_SignWhitelistERC20Cmd(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - tss := mocks.NewTSSMainnet() - evmSigner, err := getNewEvmSigner(tss) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.NoError(t, err) - require.False(t, skip) - - t.Run("SignWhitelistERC20Cmd - should successfully sign", func(t *testing.T) { - // Call SignWhitelistERC20Cmd - tx, err := evmSigner.SignWhitelistERC20Cmd(ctx, txData, sample.EthAddress().Hex()) - require.NoError(t, err) - require.NotNil(t, tx) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - verifyTxBodyBasics(t, tx, txData.to, txData.nonce, zeroValue) - }) - t.Run("SignWhitelistERC20Cmd - should fail on invalid erc20 address", func(t *testing.T) { - tx, err := evmSigner.SignWhitelistERC20Cmd(ctx, txData, "") - require.Nil(t, tx) - require.ErrorContains(t, err, "invalid erc20 address") - }) - t.Run("SignWhitelistERC20Cmd - should fail if keysign fails", func(t *testing.T) { - // Pause tss to make keysign fail - tss.Pause() - - // Call SignWhitelistERC20Cmd - tx, err := evmSigner.SignWhitelistERC20Cmd(ctx, txData, sample.EthAddress().Hex()) - require.ErrorContains(t, err, "sign whitelist error") - require.Nil(t, tx) - }) -} - -func TestSigner_SignMigrateTssFundsCmd(t *testing.T) { - ctx := makeCtx(t) - - // Setup evm signer - tss := mocks.NewTSSMainnet() - evmSigner, err := getNewEvmSigner(tss) - require.NoError(t, err) - - // Setup txData struct - cctx := getCCTX(t) - mockObserver, err := getNewEvmChainObserver(t, tss) - require.NoError(t, err) - txData, skip, err := NewOutboundData(ctx, cctx, mockObserver, 123, zerolog.Logger{}) - require.False(t, skip) - require.NoError(t, err) - - t.Run("SignMigrateTssFundsCmd - should successfully sign", func(t *testing.T) { - // Call SignMigrateTssFundsCmd - tx, err := evmSigner.SignMigrateTssFundsCmd(ctx, txData) - require.NoError(t, err) - require.NotNil(t, tx) - - // Verify tx signature - tss := mocks.NewTSSMainnet() - verifyTxSignature(t, tx, tss.Pubkey(), evmSigner.EvmSigner()) - - // Verify tx body basics - verifyTxBodyBasics(t, tx, txData.to, txData.nonce, txData.amount) - }) - - t.Run("SignMigrateTssFundsCmd - should fail if keysign fails", func(t *testing.T) { - // Pause tss to make keysign fail - tss.Pause() - - // Call SignMigrateTssFundsCmd - tx, err := evmSigner.SignMigrateTssFundsCmd(ctx, txData) - require.ErrorContains(t, err, "SignMigrateTssFundsCmd error") - require.Nil(t, tx) - }) -} func makeCtx(t *testing.T) context.Context { - app := zctx.New(config.New(false), zerolog.Nop()) + app := zctx.New(config.New(false), nil, zerolog.Nop()) bscParams := mocks.MockChainParams(chains.BscMainnet.ChainId, 10) diff --git a/zetaclient/chains/evm/signer/v2_sign.go b/zetaclient/chains/evm/signer/v2_sign.go new file mode 100644 index 0000000000..9a3bf3ba89 --- /dev/null +++ b/zetaclient/chains/evm/signer/v2_sign.go @@ -0,0 +1,205 @@ +package signer + +import ( + "context" + "fmt" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/pkg/errors" + erc20custodyv2 "github.com/zeta-chain/protocol-contracts/v2/pkg/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/gatewayevm.sol" + "github.com/zeta-chain/protocol-contracts/v2/pkg/revert.sol" +) + +// signGatewayExecute signs a gateway execute +// used for gas withdrawal and call transaction +// function execute +// address destination, +// bytes calldata data +func (signer *Signer) signGatewayExecute(ctx context.Context, txData *OutboundData) (*ethtypes.Transaction, error) { + gatewayABI, err := gatewayevm.GatewayEVMMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get GatewayEVMMetaData ABI") + } + + data, err := gatewayABI.Pack("execute", txData.to, txData.message) + if err != nil { + return nil, fmt.Errorf("execute pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.gatewayAddress, + txData.amount, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign execute error: %w", err) + } + + return tx, nil +} + +// signGatewayExecuteRevert signs a gateway execute revert +// function executeRevert +// address destination, +// bytes calldata data +func (signer *Signer) signGatewayExecuteRevert( + ctx context.Context, + txData *OutboundData, +) (*ethtypes.Transaction, error) { + gatewayABI, err := gatewayevm.GatewayEVMMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get GatewayEVMMetaData ABI") + } + + data, err := gatewayABI.Pack( + "executeRevert", + txData.to, + txData.message, + revert.RevertContext{ + Asset: txData.asset, + Amount: txData.amount.Uint64(), + RevertMessage: txData.revertOptions.RevertMessage, + }, + ) + if err != nil { + return nil, fmt.Errorf("executeRevert pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.gatewayAddress, + txData.amount, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign executeRevert error: %w", err) + } + + return tx, nil +} + +// signERC20CustodyWithdraw signs a erc20 withdrawal transaction +// function withdrawAndCall +// address to, +// address token, +// uint256 amount, +func (signer *Signer) signERC20CustodyWithdraw( + ctx context.Context, + txData *OutboundData, +) (*ethtypes.Transaction, error) { + erc20CustodyV2ABI, err := erc20custodyv2.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get ERC20CustodyMetaData ABI") + } + + data, err := erc20CustodyV2ABI.Pack("withdraw", txData.to, txData.asset, txData.amount) + if err != nil { + return nil, fmt.Errorf("withdraw pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.er20CustodyAddress, + zeroValue, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign withdraw error: %w", err) + } + + return tx, nil +} + +// signERC20CustodyWithdrawAndCall signs a erc20 withdrawal and call transaction +// function withdrawAndCall +// address token, +// address to, +// uint256 amount, +// bytes calldata data +func (signer *Signer) signERC20CustodyWithdrawAndCall( + ctx context.Context, + txData *OutboundData, +) (*ethtypes.Transaction, error) { + erc20CustodyV2ABI, err := erc20custodyv2.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get ERC20CustodyMetaData ABI") + } + + data, err := erc20CustodyV2ABI.Pack("withdrawAndCall", txData.to, txData.asset, txData.amount, txData.message) + if err != nil { + return nil, fmt.Errorf("withdraw pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.er20CustodyAddress, + zeroValue, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign withdrawAndCall error: %w", err) + } + + return tx, nil +} + +// signERC20CustodyWithdrawRevert signs a erc20 withdrawal revert transaction +// function withdrawAndRevert +// address token, +// address to, +// uint256 amount, +// bytes calldata data +func (signer *Signer) signERC20CustodyWithdrawRevert( + ctx context.Context, + txData *OutboundData, +) (*ethtypes.Transaction, error) { + erc20CustodyV2ABI, err := erc20custodyv2.ERC20CustodyMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "unable to get ERC20CustodyMetaData ABI") + } + + data, err := erc20CustodyV2ABI.Pack( + "withdrawAndRevert", + txData.to, + txData.asset, + txData.amount, + txData.message, + revert.RevertContext{ + Asset: txData.asset, + Amount: txData.amount.Uint64(), + RevertMessage: txData.revertOptions.RevertMessage, + }, + ) + if err != nil { + return nil, fmt.Errorf("withdraw pack error: %w", err) + } + + tx, _, _, err := signer.Sign( + ctx, + data, + signer.er20CustodyAddress, + zeroValue, + txData.gas, + txData.nonce, + txData.height, + ) + if err != nil { + return nil, fmt.Errorf("sign withdrawAndRevert error: %w", err) + } + + return tx, nil +} diff --git a/zetaclient/chains/evm/signer/v2_signer.go b/zetaclient/chains/evm/signer/v2_signer.go new file mode 100644 index 0000000000..00f9c2197d --- /dev/null +++ b/zetaclient/chains/evm/signer/v2_signer.go @@ -0,0 +1,37 @@ +package signer + +import ( + "context" + "fmt" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/zeta-chain/zetacore/x/crosschain/types" + "github.com/zeta-chain/zetacore/zetaclient/chains/evm" +) + +// SignOutboundFromCCTXV2 signs an outbound transaction from a CCTX with protocol contract v2 +func (signer *Signer) SignOutboundFromCCTXV2( + ctx context.Context, + cctx *types.CrossChainTx, + outboundData *OutboundData, +) (*ethtypes.Transaction, error) { + outboundType := evm.ParseOutboundTypeFromCCTX(*cctx) + switch outboundType { + case evm.OutboundTypeGasWithdraw, evm.OutboundTypeGasWithdrawRevert: + return signer.SignGasWithdraw(ctx, outboundData) + case evm.OutboundTypeERC20Withdraw, evm.OutboundTypeERC20WithdrawRevert: + return signer.signERC20CustodyWithdraw(ctx, outboundData) + case evm.OutboundTypeERC20WithdrawAndCall: + return signer.signERC20CustodyWithdrawAndCall(ctx, outboundData) + case evm.OutboundTypeGasWithdrawAndCall, evm.OutboundTypeCall: + // both gas withdraw and call and no-asset call uses gateway execute + // no-asset call simply hash msg.value == 0 + return signer.signGatewayExecute(ctx, outboundData) + case evm.OutboundTypeGasWithdrawRevertAndCallOnRevert: + return signer.signGatewayExecuteRevert(ctx, outboundData) + case evm.OutboundTypeERC20WithdrawRevertAndCallOnRevert: + return signer.signERC20CustodyWithdrawRevert(ctx, outboundData) + } + return nil, fmt.Errorf("unsupported outbound type %d", outboundType) +} diff --git a/zetaclient/chains/evm/validation.go b/zetaclient/chains/evm/validation.go index 8c7f9249c5..0f043c9966 100644 --- a/zetaclient/chains/evm/validation.go +++ b/zetaclient/chains/evm/validation.go @@ -14,7 +14,7 @@ import ( // ValidateEvmTxLog checks the basics of an EVM tx log func ValidateEvmTxLog(vLog *ethtypes.Log, wantAddress ethcommon.Address, wantHash string, wantTopics int) error { if vLog.Removed { - return fmt.Errorf("log is removed, chain reorg?") + return fmt.Errorf("log is removed, it might be related to a chain reorganization") } if vLog.Address != wantAddress { return fmt.Errorf("log emitter address mismatch: want %s got %s", wantAddress.Hex(), vLog.Address.Hex()) diff --git a/zetaclient/chains/interfaces/interfaces.go b/zetaclient/chains/interfaces/interfaces.go index a11af2fc73..5e1148f81b 100644 --- a/zetaclient/chains/interfaces/interfaces.go +++ b/zetaclient/chains/interfaces/interfaces.go @@ -195,6 +195,11 @@ type SolanaRPCClient interface { GetHealth(ctx context.Context) (string, error) GetSlot(ctx context.Context, commitment solrpc.CommitmentType) (uint64, error) GetAccountInfo(ctx context.Context, account solana.PublicKey) (*solrpc.GetAccountInfoResult, error) + GetBalance( + ctx context.Context, + account solana.PublicKey, + commitment solrpc.CommitmentType, + ) (*solrpc.GetBalanceResult, error) GetRecentBlockhash(ctx context.Context, commitment solrpc.CommitmentType) (*solrpc.GetRecentBlockhashResult, error) GetRecentPrioritizationFees( ctx context.Context, diff --git a/zetaclient/chains/solana/observer/outbound.go b/zetaclient/chains/solana/observer/outbound.go index 6eca0b437e..396e49df1c 100644 --- a/zetaclient/chains/solana/observer/outbound.go +++ b/zetaclient/chains/solana/observer/outbound.go @@ -17,6 +17,7 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" zctx "github.com/zeta-chain/zetacore/zetaclient/context" + "github.com/zeta-chain/zetacore/zetaclient/logs" clienttypes "github.com/zeta-chain/zetacore/zetaclient/types" "github.com/zeta-chain/zetacore/zetaclient/zetacore" ) @@ -254,15 +255,15 @@ func (ob *Observer) CheckFinalizedTx( // prepare logger fields chainID := ob.Chain().ChainId logger := ob.Logger().Outbound.With(). - Str("method", "checkFinalizedTx"). - Int64("chain", chainID). - Uint64("nonce", nonce). - Str("tx", txHash).Logger() + Str(logs.FieldMethod, "CheckFinalizedTx"). + Int64(logs.FieldChain, chainID). + Uint64(logs.FieldNonce, nonce). + Str(logs.FieldTx, txHash).Logger() // convert txHash to signature sig, err := solana.SignatureFromBase58(txHash) if err != nil { - logger.Error().Err(err).Msgf("SignatureFromBase58 err for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msg("SignatureFromBase58 error") return nil, false } @@ -271,20 +272,20 @@ func (ob *Observer) CheckFinalizedTx( Commitment: rpc.CommitmentFinalized, }) if err != nil { - logger.Error().Err(err).Msgf("GetTransaction err for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msg("GetTransaction error") return nil, false } // the tx must be successful in order to effectively increment the nonce if txResult.Meta.Err != nil { - logger.Error().Any("Err", txResult.Meta.Err).Msgf("tx is not successful for chain %d nonce %d", chainID, nonce) + logger.Error().Any("Err", txResult.Meta.Err).Msg("tx is not successful") return nil, false } // parse gateway instruction from tx result inst, err := ParseGatewayInstruction(txResult, ob.gatewayID, coinType) if err != nil { - logger.Error().Err(err).Msgf("ParseGatewayInstruction err for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msg("ParseGatewayInstruction error") return nil, false } txNonce := inst.GatewayNonce() @@ -292,19 +293,19 @@ func (ob *Observer) CheckFinalizedTx( // recover ECDSA signer from instruction signerECDSA, err := inst.Signer() if err != nil { - logger.Error().Err(err).Msgf("cannot get instruction signer for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msg("cannot get instruction signer") return nil, false } // check tx authorization if signerECDSA != ob.TSS().EVMAddress() { - logger.Error().Msgf("tx signer %s is not matching TSS, chain %d nonce %d", signerECDSA, chainID, nonce) + logger.Error().Msgf("tx signer %s is not matching current TSS address %s", signerECDSA, ob.TSS().EVMAddress()) return nil, false } // check tx nonce if txNonce != nonce { - logger.Error().Msgf("tx nonce %d is not matching cctx, chain %d nonce %d", txNonce, chainID, nonce) + logger.Error().Msgf("tx nonce %d is not matching tracker nonce", txNonce) return nil, false } diff --git a/zetaclient/chains/solana/signer/outbound_tracker_reporter.go b/zetaclient/chains/solana/signer/outbound_tracker_reporter.go index 6462060beb..0a5fb6432e 100644 --- a/zetaclient/chains/solana/signer/outbound_tracker_reporter.go +++ b/zetaclient/chains/solana/signer/outbound_tracker_reporter.go @@ -8,7 +8,9 @@ import ( "github.com/gagliardetto/solana-go/rpc" "github.com/rs/zerolog" + "github.com/zeta-chain/zetacore/pkg/bg" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/logs" ) const ( @@ -27,16 +29,23 @@ func (signer *Signer) reportToOutboundTracker( txSig solana.Signature, logger zerolog.Logger, ) { + // prepare logger + logger = logger.With(). + Str(logs.FieldMethod, "reportToOutboundTracker"). + Int64(logs.FieldChain, chainID). + Uint64(logs.FieldNonce, nonce). + Str(logs.FieldTx, txSig.String()). + Logger() + // set being reported flag to avoid duplicate reporting alreadySet := signer.Signer.SetBeingReportedFlag(txSig.String()) if alreadySet { - logger.Info(). - Msgf("reportToOutboundTracker: outbound %s for chain %d nonce %d is being reported", txSig, chainID, nonce) + logger.Info().Msg("outbound is being reported to tracker") return } // launch a goroutine to monitor tx confirmation status - go func() { + bg.Work(ctx, func(ctx context.Context) error { defer func() { signer.Signer.ClearBeingReportedFlag(txSig.String()) }() @@ -48,9 +57,8 @@ func (signer *Signer) reportToOutboundTracker( // give up if we know the tx is too old and already expired if time.Since(start) > SolanaTransactionTimeout { - logger.Info(). - Msgf("reportToOutboundTracker: outbound %s expired for chain %d nonce %d", txSig, chainID, nonce) - return + logger.Info().Msg("outbound is expired") + return nil } // query tx using optimistic commitment level "confirmed" @@ -68,24 +76,21 @@ func (signer *Signer) reportToOutboundTracker( // unlike Ethereum, Solana doesn't have protocol-level nonce; the nonce is enforced by the gateway program. // a failed outbound (e.g. signature err, balance err) will never be able to increment the gateway program nonce. // a good/valid candidate of outbound tracker hash must come with a successful tx. - logger.Warn(). - Any("Err", tx.Meta.Err). - Msgf("reportToOutboundTracker: outbound %s failed for chain %d nonce %d", txSig, chainID, nonce) - return + logger.Warn().Any("Err", tx.Meta.Err).Msg("outbound is failed") + return nil } // report outbound hash to zetacore zetaHash, err := zetacoreClient.AddOutboundTracker(ctx, chainID, nonce, txSig.String(), nil, "", -1) if err != nil { - logger.Err(err). - Msgf("reportToOutboundTracker: error adding outbound %s for chain %d nonce %d", txSig, chainID, nonce) + logger.Err(err).Msg("error adding outbound to tracker") } else if zetaHash != "" { - logger.Info().Msgf("reportToOutboundTracker: added outbound %s for chain %d nonce %d; zeta txhash %s", txSig, chainID, nonce, zetaHash) + logger.Info().Msgf("added outbound to tracker; zeta txhash %s", zetaHash) } else { - // exit goroutine if the tracker already contains the hash (reported by other signer) - logger.Info().Msgf("reportToOutboundTracker: outbound %s already in tracker for chain %d nonce %d", txSig, chainID, nonce) - return + // exit goroutine until the tracker contains the hash (reported by either this or other signers) + logger.Info().Msg("outbound now exists in tracker") + return nil } } - }() + }, bg.WithName("TrackerReporterSolana"), bg.WithLogger(logger)) } diff --git a/zetaclient/chains/solana/signer/signer.go b/zetaclient/chains/solana/signer/signer.go index 3fb7512512..41c4c3efdd 100644 --- a/zetaclient/chains/solana/signer/signer.go +++ b/zetaclient/chains/solana/signer/signer.go @@ -11,10 +11,12 @@ import ( "github.com/zeta-chain/zetacore/pkg/chains" "github.com/zeta-chain/zetacore/pkg/coin" contracts "github.com/zeta-chain/zetacore/pkg/contracts/solana" + "github.com/zeta-chain/zetacore/pkg/crypto" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/keys" "github.com/zeta-chain/zetacore/zetaclient/metrics" "github.com/zeta-chain/zetacore/zetaclient/outboundprocessor" ) @@ -28,8 +30,9 @@ type Signer struct { // client is the Solana RPC client that interacts with the Solana chain client interfaces.SolanaRPCClient - // solanaFeePayerKey is the private key of the fee payer account on Solana chain - solanaFeePayerKey solana.PrivateKey + // relayerKey is the private key of the relayer account for Solana chain + // relayerKey is optional, the signer will not relay transactions if it is not set + relayerKey *solana.PrivateKey // gatewayID is the program ID of gateway program on Solana chain gatewayID solana.PublicKey @@ -44,7 +47,7 @@ func NewSigner( chainParams observertypes.ChainParams, solClient interfaces.SolanaRPCClient, tss interfaces.TSSSigner, - solanaKey solana.PrivateKey, + relayerKey *keys.RelayerKey, ts *metrics.TelemetryServer, logger base.Logger, ) (*Signer, error) { @@ -56,16 +59,32 @@ func NewSigner( if err != nil { return nil, errors.Wrapf(err, "cannot parse gateway address %s", chainParams.GatewayAddress) } - logger.Std.Info().Msgf("Solana fee payer address: %s", solanaKey.PublicKey()) - - // create solana observer - return &Signer{ - Signer: baseSigner, - client: solClient, - solanaFeePayerKey: solanaKey, - gatewayID: gatewayID, - pda: pda, - }, nil + + // create Solana signer + signer := &Signer{ + Signer: baseSigner, + client: solClient, + gatewayID: gatewayID, + pda: pda, + } + + // construct Solana private key if present + if relayerKey != nil { + signer.relayerKey, err = crypto.SolanaPrivateKeyFromString(relayerKey.PrivateKey) + if err != nil { + return nil, errors.Wrap(err, "unable to construct solana private key") + } + logger.Std.Info().Msgf("Solana relayer address: %s", signer.relayerKey.PublicKey()) + } else { + logger.Std.Info().Msg("Solana relayer key is not provided") + } + + return signer, nil +} + +// HasRelayerKey returns true if the signer has a relayer key +func (signer *Signer) HasRelayerKey() bool { + return signer.relayerKey != nil } // TryProcessOutbound - signer interface implementation @@ -114,10 +133,18 @@ func (signer *Signer) TryProcessOutbound( return } - // sign the withdraw transaction by fee payer + // skip relaying the transaction if this signer hasn't set the relayer key + if !signer.HasRelayerKey() { + return + } + + // set relayer balance metrics + signer.SetRelayerBalanceMetrics(ctx) + + // sign the withdraw transaction by relayer key tx, err := signer.SignWithdrawTx(ctx, *msg) if err != nil { - logger.Error().Err(err).Msgf("TryProcessOutbound: SignWithdrawTx error for chain %d nonce %d", chainID, nonce) + logger.Error().Err(err).Msgf("TryProcessOutbound: SignGasWithdraw error for chain %d nonce %d", chainID, nonce) return } @@ -167,6 +194,21 @@ func (signer *Signer) GetGatewayAddress() string { return signer.gatewayID.String() } +// SetRelayerBalanceMetrics sets the relayer balance metrics +func (signer *Signer) SetRelayerBalanceMetrics(ctx context.Context) { + if !signer.HasRelayerKey() { + return + } + + result, err := signer.client.GetBalance(ctx, signer.relayerKey.PublicKey(), rpc.CommitmentFinalized) + if err != nil { + signer.Logger().Std.Error().Err(err).Msg("GetBalance error") + return + } + solBalance := float64(result.Value) / float64(solana.LAMPORTS_PER_SOL) + metrics.RelayerKeyBalance.WithLabelValues(signer.Chain().Name).Set(solBalance) +} + // TODO: get rid of below four functions for Solana and Bitcoin // https://github.com/zeta-chain/node/issues/2532 func (signer *Signer) SetZetaConnectorAddress(_ ethcommon.Address) { diff --git a/zetaclient/chains/solana/signer/signer_test.go b/zetaclient/chains/solana/signer/signer_test.go new file mode 100644 index 0000000000..f3e1799d61 --- /dev/null +++ b/zetaclient/chains/solana/signer/signer_test.go @@ -0,0 +1,142 @@ +package signer_test + +import ( + "context" + "errors" + "testing" + + "github.com/gagliardetto/solana-go/rpc" + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/testutil/sample" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + "github.com/zeta-chain/zetacore/zetaclient/chains/base" + "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" + "github.com/zeta-chain/zetacore/zetaclient/chains/solana/signer" + "github.com/zeta-chain/zetacore/zetaclient/keys" + "github.com/zeta-chain/zetacore/zetaclient/metrics" + "github.com/zeta-chain/zetacore/zetaclient/testutils" + "github.com/zeta-chain/zetacore/zetaclient/testutils/mocks" +) + +func Test_NewSigner(t *testing.T) { + // test parameters + chain := chains.SolanaDevnet + chainParams := sample.ChainParams(chain.ChainId) + chainParams.GatewayAddress = testutils.GatewayAddresses[chain.ChainId] + + tests := []struct { + name string + chain chains.Chain + chainParams observertypes.ChainParams + solClient interfaces.SolanaRPCClient + tss interfaces.TSSSigner + relayerKey *keys.RelayerKey + ts *metrics.TelemetryServer + logger base.Logger + errMessage string + }{ + { + name: "should create solana signer successfully with relayer key", + chain: chain, + chainParams: *chainParams, + solClient: nil, + tss: nil, + relayerKey: &keys.RelayerKey{ + PrivateKey: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ", + }, + ts: nil, + logger: base.DefaultLogger(), + }, + { + name: "should create solana signer successfully without relayer key", + chainParams: *chainParams, + solClient: nil, + tss: nil, + relayerKey: nil, + ts: nil, + logger: base.DefaultLogger(), + }, + { + name: "should fail to create solana signer with invalid gateway address", + chainParams: func() observertypes.ChainParams { + cp := *chainParams + cp.GatewayAddress = "invalid" + return cp + }(), + solClient: nil, + tss: nil, + relayerKey: nil, + ts: nil, + logger: base.DefaultLogger(), + errMessage: "cannot parse gateway address", + }, + { + name: "should fail to create solana signer with invalid relayer key", + chainParams: *chainParams, + solClient: nil, + tss: nil, + relayerKey: &keys.RelayerKey{ + PrivateKey: "3EMjCcCJg53fMEGVj13", // too short + }, + ts: nil, + logger: base.DefaultLogger(), + errMessage: "unable to construct solana private key", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, err := signer.NewSigner(tt.chain, tt.chainParams, tt.solClient, tt.tss, tt.relayerKey, tt.ts, tt.logger) + if tt.errMessage != "" { + require.ErrorContains(t, err, tt.errMessage) + require.Nil(t, s) + return + } + + require.NoError(t, err) + require.NotNil(t, s) + }) + } +} + +func Test_SetRelayerBalanceMetrics(t *testing.T) { + // test parameters + chain := chains.SolanaDevnet + chainParams := sample.ChainParams(chain.ChainId) + chainParams.GatewayAddress = testutils.GatewayAddresses[chain.ChainId] + relayerKey := &keys.RelayerKey{ + PrivateKey: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ", + } + ctx := context.Background() + + // mock solana client with RPC error + mckClient := mocks.NewSolanaRPCClient(t) + mckClient.On("GetBalance", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("rpc error")) + + // create signer and set relayer balance metrics + s, err := signer.NewSigner(chain, *chainParams, mckClient, nil, relayerKey, nil, base.DefaultLogger()) + require.NoError(t, err) + s.SetRelayerBalanceMetrics(ctx) + + // assert that relayer key balance metrics is not set (due to RPC error) + balance := testutil.ToFloat64(metrics.RelayerKeyBalance.WithLabelValues(chain.Name)) + require.Equal(t, 0.0, balance) + + // mock solana client with balance + mckClient = mocks.NewSolanaRPCClient(t) + mckClient.On("GetBalance", mock.Anything, mock.Anything, mock.Anything).Return(&rpc.GetBalanceResult{ + Value: 123400000, + }, nil) + + // create signer and set relayer balance metrics again + s, err = signer.NewSigner(chain, *chainParams, mckClient, nil, relayerKey, nil, base.DefaultLogger()) + require.NoError(t, err) + s.SetRelayerBalanceMetrics(ctx) + + // assert that relayer key balance metrics is set correctly + balance = testutil.ToFloat64(metrics.RelayerKeyBalance.WithLabelValues(chain.Name)) + require.Equal(t, 0.1234, balance) +} diff --git a/zetaclient/chains/solana/signer/withdraw.go b/zetaclient/chains/solana/signer/withdraw.go index 383d1c908a..f44dc3fc30 100644 --- a/zetaclient/chains/solana/signer/withdraw.go +++ b/zetaclient/chains/solana/signer/withdraw.go @@ -47,7 +47,7 @@ func (signer *Signer) SignMsgWithdraw( return msg.SetSignature(signature), nil } -// SignWithdrawTx wraps the withdraw 'msg' into a Solana transaction and signs it with the fee payer key. +// SignWithdrawTx wraps the withdraw 'msg' into a Solana transaction and signs it with the relayer key. func (signer *Signer) SignWithdrawTx(ctx context.Context, msg contracts.MsgWithdraw) (*solana.Transaction, error) { // create withdraw instruction with program call data var err error @@ -65,7 +65,7 @@ func (signer *Signer) SignWithdrawTx(ctx context.Context, msg contracts.MsgWithd } // attach required accounts to the instruction - privkey := signer.solanaFeePayerKey + privkey := signer.relayerKey attachWithdrawAccounts(&inst, privkey.PublicKey(), signer.pda, msg.To(), signer.gatewayID) // get a recent blockhash @@ -89,10 +89,10 @@ func (signer *Signer) SignWithdrawTx(ctx context.Context, msg contracts.MsgWithd return nil, errors.Wrap(err, "NewTransaction error") } - // fee payer signs the transaction + // relayer signs the transaction _, err = tx.Sign(func(key solana.PublicKey) *solana.PrivateKey { if key.Equals(privkey.PublicKey()) { - return &privkey + return privkey } return nil }) diff --git a/zetaclient/config/config_chain.go b/zetaclient/config/config_chain.go index 342574764f..826253d71e 100644 --- a/zetaclient/config/config_chain.go +++ b/zetaclient/config/config_chain.go @@ -10,28 +10,6 @@ const ( MaxBlocksPerPeriod = 100 ) -const ( - // connectorAbiString is the ABI of the connector contract - // TODO(revamp): we should be able to use info from Go binding - connectorAbiString = ` -[{"inputs":[{"internalType":"address","name":"_zetaTokenAddress","type":"address"},{"internalType":"address","name":"_tssAddress","type":"address"},{"internalType":"address","name":"_tssAddressUpdater","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"originSenderAddress","type":"bytes"},{"indexed":true,"internalType":"uint256","name":"originChainId","type":"uint256"},{"indexed":true,"internalType":"address","name":"destinationAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"zetaAmount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"message","type":"bytes"},{"indexed":true,"internalType":"bytes32","name":"internalSendHash","type":"bytes32"}],"name":"ZetaReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"originSenderAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"originChainId","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"indexed":true,"internalType":"bytes","name":"destinationAddress","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"zetaAmount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"message","type":"bytes"},{"indexed":true,"internalType":"bytes32","name":"internalSendHash","type":"bytes32"}],"name":"ZetaReverted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"originSenderAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"destinationAddress","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"zetaAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasLimit","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"message","type":"bytes"},{"indexed":false,"internalType":"bytes","name":"zetaParams","type":"bytes"}],"name":"ZetaSent","type":"event"},{"inputs":[],"name":"getLockedAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"originSenderAddress","type":"bytes"},{"internalType":"uint256","name":"originChainId","type":"uint256"},{"internalType":"address","name":"destinationAddress","type":"address"},{"internalType":"uint256","name":"zetaAmount","type":"uint256"},{"internalType":"bytes","name":"message","type":"bytes"},{"internalType":"bytes32","name":"internalSendHash","type":"bytes32"}],"name":"onReceive","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"originSenderAddress","type":"address"},{"internalType":"uint256","name":"originChainId","type":"uint256"},{"internalType":"bytes","name":"destinationAddress","type":"bytes"},{"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"internalType":"uint256","name":"zetaAmount","type":"uint256"},{"internalType":"bytes","name":"message","type":"bytes"},{"internalType":"bytes32","name":"internalSendHash","type":"bytes32"}],"name":"onRevert","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceTssAddressUpdater","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"internalType":"bytes","name":"destinationAddress","type":"bytes"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"bytes","name":"message","type":"bytes"},{"internalType":"uint256","name":"zetaAmount","type":"uint256"},{"internalType":"bytes","name":"zetaParams","type":"bytes"}],"internalType":"struct ZetaInterfaces.SendInput","name":"input","type":"tuple"}],"name":"send","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tssAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tssAddressUpdater","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_tssAddress","type":"address"}],"name":"updateTssAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"zetaToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]` - - // erc20CustodyAbiString is the ABI of the erc20 custodu contract - // TODO(revamp): we should be able to use info from Go binding - erc20CustodyAbiString = ` -[{"inputs":[{"internalType":"address","name":"_TSSAddress","type":"address"},{"internalType":"address","name":"_TSSAddressUpdater","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidSender","type":"error"},{"inputs":[],"name":"InvalidTSSUpdater","type":"error"},{"inputs":[],"name":"IsPaused","type":"error"},{"inputs":[],"name":"NotPaused","type":"error"},{"inputs":[],"name":"NotWhitelisted","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"recipient","type":"bytes"},{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"message","type":"bytes"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"asset","type":"address"}],"name":"Unwhitelisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"asset","type":"address"}],"name":"Whitelisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"address","name":"asset","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawn","type":"event"},{"inputs":[],"name":"TSSAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TSSAddressUpdater","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"recipient","type":"bytes"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"message","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceTSSAddressUpdater","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"unwhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"updateTSSAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"whitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"asset","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]` -) - -// GetConnectorABI returns the ABI of the connector contract -func GetConnectorABI() string { - return connectorAbiString -} - -// GetERC20CustodyABI returns the ABI of the erc20 custody contract -func GetERC20CustodyABI() string { - return erc20CustodyAbiString -} - // New constructs Config optionally with default values. func New(setDefaults bool) Config { cfg := Config{ diff --git a/zetaclient/config/types.go b/zetaclient/config/types.go index 31298b00a3..b43043e30e 100644 --- a/zetaclient/config/types.go +++ b/zetaclient/config/types.go @@ -2,15 +2,9 @@ package config import ( "encoding/json" - "fmt" - "os" - "path" "strings" "sync" - "cosmossdk.io/errors" - "github.com/gagliardetto/solana-go" - "github.com/zeta-chain/zetacore/pkg/chains" ) @@ -85,9 +79,9 @@ type Config struct { TssPath string `json:"TssPath"` TestTssKeysign bool `json:"TestTssKeysign"` KeyringBackend KeyringBackend `json:"KeyringBackend"` + RelayerKeyPath string `json:"RelayerKeyPath"` HsmMode bool `json:"HsmMode"` HsmHotKey string `json:"HsmHotKey"` - SolanaKeyFile string `json:"SolanaKeyFile"` // chain configs EVMChainConfigs map[int64]EVMConfig `json:"EVMChainConfigs"` @@ -165,34 +159,11 @@ func (c Config) GetKeyringBackend() KeyringBackend { return c.KeyringBackend } -// LoadSolanaPrivateKey loads the Solana private key from the key file -func (c Config) LoadSolanaPrivateKey() (solana.PrivateKey, error) { - // key file path - fileName := path.Join(c.ZetaCoreHome, c.SolanaKeyFile) - - // load the gateway keypair from a JSON file - // #nosec G304 -- user is allowed to specify the key file - fileContent, err := os.ReadFile(fileName) - if err != nil { - return solana.PrivateKey{}, errors.Wrapf(err, "unable to read Solana key file: %s", fileName) - } - - // unmarshal the JSON content into a slice of bytes - var keyBytes []byte - err = json.Unmarshal(fileContent, &keyBytes) - if err != nil { - return solana.PrivateKey{}, errors.Wrap(err, "unable to unmarshal Solana key bytes") - } - - // ensure the key length is 64 bytes - if len(keyBytes) != 64 { - return solana.PrivateKey{}, fmt.Errorf("invalid Solana key length: %d", len(keyBytes)) - } - - // create private key from the key bytes - privKey := solana.PrivateKey(keyBytes) - - return privKey, nil +// GetRelayerKeyPath returns the relayer key path +func (c Config) GetRelayerKeyPath() string { + c.mu.RLock() + defer c.mu.RUnlock() + return c.RelayerKeyPath } func (c EVMConfig) Empty() bool { diff --git a/zetaclient/context/app.go b/zetaclient/context/app.go index 032d0b759c..e3a219f00b 100644 --- a/zetaclient/context/app.go +++ b/zetaclient/context/app.go @@ -19,25 +19,34 @@ import ( // AppContext represents application (zetaclient) context. type AppContext struct { + // config is the config of the app config config.Config + + // logger is the logger of the app logger zerolog.Logger + // chainRegistry is a registry of supported chains chainRegistry *ChainRegistry + // currentTssPubKey is the current tss pubKey currentTssPubKey string - crosschainFlags observertypes.CrosschainFlags - keygen observertypes.Keygen + + // crosschainFlags is the current crosschain flags state + crosschainFlags observertypes.CrosschainFlags + + // keygen is the current tss keygen state + keygen observertypes.Keygen mu sync.RWMutex } // New creates and returns new empty AppContext -func New(cfg config.Config, logger zerolog.Logger) *AppContext { +func New(cfg config.Config, relayerKeyPasswords map[string]string, logger zerolog.Logger) *AppContext { return &AppContext{ config: cfg, logger: logger.With().Str("module", "appcontext").Logger(), - chainRegistry: NewChainRegistry(), + chainRegistry: NewChainRegistry(relayerKeyPasswords), crosschainFlags: observertypes.CrosschainFlags{}, currentTssPubKey: "", diff --git a/zetaclient/context/app_test.go b/zetaclient/context/app_test.go index 0dd8e2daed..d3bba4f041 100644 --- a/zetaclient/context/app_test.go +++ b/zetaclient/context/app_test.go @@ -57,7 +57,7 @@ func TestAppContext(t *testing.T) { t.Run("Update", func(t *testing.T) { // Given AppContext - appContext := New(testCfg, logger) + appContext := New(testCfg, nil, logger) // With expected default behavior _, err := appContext.GetChain(123) diff --git a/zetaclient/context/chain.go b/zetaclient/context/chain.go index a5c17768f1..4271fdb46e 100644 --- a/zetaclient/context/chain.go +++ b/zetaclient/context/chain.go @@ -20,6 +20,9 @@ type ChainRegistry struct { // chain IDs. It's stored in the protocol to dynamically support new chains without doing an upgrade additionalChains []chains.Chain + // relayerKeyPasswords maps network name to relayer key password + relayerKeyPasswords map[string]string + mu sync.Mutex } @@ -39,11 +42,12 @@ var ( ) // NewChainRegistry constructs a new ChainRegistry -func NewChainRegistry() *ChainRegistry { +func NewChainRegistry(relayerKeyPasswords map[string]string) *ChainRegistry { return &ChainRegistry{ - chains: make(map[int64]Chain), - additionalChains: []chains.Chain{}, - mu: sync.Mutex{}, + chains: make(map[int64]Chain), + additionalChains: []chains.Chain{}, + relayerKeyPasswords: relayerKeyPasswords, + mu: sync.Mutex{}, } } @@ -161,6 +165,13 @@ func (c Chain) IsSolana() bool { return chains.IsSolanaChain(c.ID(), c.registry.additionalChains) } +// RelayerKeyPassword returns the relayer key password for the chain +func (c Chain) RelayerKeyPassword() string { + network := c.RawChain().Network + + return c.registry.relayerKeyPasswords[network.String()] +} + func validateNewChain(chainID int64, chain *chains.Chain, params *observer.ChainParams) error { switch { case chainID < 1: diff --git a/zetaclient/context/chain_test.go b/zetaclient/context/chain_test.go index a679ed020a..29d1ecef7c 100644 --- a/zetaclient/context/chain_test.go +++ b/zetaclient/context/chain_test.go @@ -35,7 +35,7 @@ func TestChainRegistry(t *testing.T) { t.Run("Sample Flow", func(t *testing.T) { // Given registry - r := NewChainRegistry() + r := NewChainRegistry(nil) // With some chains added require.NoError(t, r.Set(btc.ChainId, btc, btcParams)) diff --git a/zetaclient/context/context_test.go b/zetaclient/context/context_test.go index be9dab83a4..d0f623a86f 100644 --- a/zetaclient/context/context_test.go +++ b/zetaclient/context/context_test.go @@ -24,7 +24,7 @@ func TestFromContext(t *testing.T) { // ARRANGE #2 // Given basic app - app := context.New(config.New(false), zerolog.Nop()) + app := context.New(config.New(false), nil, zerolog.Nop()) // That is included in the ctx ctx = context.WithAppContext(ctx, app) @@ -42,7 +42,7 @@ func TestFromContext(t *testing.T) { func TestCopy(t *testing.T) { // ARRANGE var ( - app = context.New(config.New(false), zerolog.Nop()) + app = context.New(config.New(false), nil, zerolog.Nop()) ctx1 = context.WithAppContext(goctx.Background(), app) ) diff --git a/zetaclient/keys/relayer_key.go b/zetaclient/keys/relayer_key.go new file mode 100644 index 0000000000..935d11ac84 --- /dev/null +++ b/zetaclient/keys/relayer_key.go @@ -0,0 +1,155 @@ +package keys + +import ( + "encoding/json" + "os" + "path/filepath" + + "github.com/pkg/errors" + + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/crypto" + zetaos "github.com/zeta-chain/zetacore/pkg/os" +) + +// RelayerKey is the structure that holds the relayer private key +type RelayerKey struct { + PrivateKey string `json:"private_key"` +} + +// ResolveAddress returns the network name and address of the relayer key +func (rk RelayerKey) ResolveAddress(network chains.Network) (string, string, error) { + var address string + + switch network { + case chains.Network_solana: + privKey, err := crypto.SolanaPrivateKeyFromString(rk.PrivateKey) + if err != nil { + return "", "", errors.Wrap(err, "unable to construct solana private key") + } + address = privKey.PublicKey().String() + default: + return "", "", errors.Errorf("unsupported network %d: unable to derive relayer address", network) + } + + // return network name and address + return network.String(), address, nil +} + +// LoadRelayerKey loads the relayer key for given network and password +func LoadRelayerKey(relayerKeyPath string, network chains.Network, password string) (*RelayerKey, error) { + // resolve the relayer key file name + fileName, err := ResolveRelayerKeyFile(relayerKeyPath, network) + if err != nil { + return nil, errors.Wrap(err, "failed to resolve relayer key file name") + } + + // load the relayer key if it is present + if zetaos.FileExists(fileName) { + // read the relayer key file + relayerKey, err := ReadRelayerKeyFromFile(fileName) + if err != nil { + return nil, errors.Wrapf(err, "failed to read relayer key file: %s", fileName) + } + + // password must be set by operator + if password == "" { + return nil, errors.New("password is required to decrypt the private key") + } + + // decrypt the private key + privateKey, err := crypto.DecryptAES256GCMBase64(relayerKey.PrivateKey, password) + if err != nil { + return nil, errors.Wrap(err, "failed to decrypt private key") + } + + relayerKey.PrivateKey = privateKey + return relayerKey, nil + } + + // relayer key is optional, so it's okay if the relayer key is not provided + return nil, nil +} + +// ResolveRelayerKeyFile is a helper function to resolve the relayer key file with full path +func ResolveRelayerKeyFile(relayerKeyPath string, network chains.Network) (string, error) { + // resolve relayer key path if it contains a tilde + keyPath, err := zetaos.ExpandHomeDir(relayerKeyPath) + if err != nil { + return "", errors.Wrap(err, "failed to resolve relayer key path") + } + + // get relayer key file name by network + name, err := relayerKeyFileByNetwork(network) + if err != nil { + return "", errors.Wrap(err, "failed to get relayer key file name") + } + + return filepath.Join(keyPath, name), nil +} + +// WriteRelayerKeyToFile writes the relayer key to a file +func WriteRelayerKeyToFile(fileName string, relayerKey RelayerKey) error { + keyData, err := json.Marshal(relayerKey) + if err != nil { + return errors.Wrap(err, "failed to marshal relayer key") + } + + // create relay key file (owner `rw` permissions) + return os.WriteFile(fileName, keyData, 0o600) +} + +// ReadRelayerKeyFromFile reads the relayer key file and returns the key +func ReadRelayerKeyFromFile(fileName string) (*RelayerKey, error) { + // expand home directory in the file path if it exists + fileNameFull, err := zetaos.ExpandHomeDir(fileName) + if err != nil { + return nil, errors.Wrapf(err, "ExpandHome failed for file: %s", fileName) + } + + // read the file contents + // #nosec G304 -- relayer key file is controlled by the operator + fileData, err := os.ReadFile(fileNameFull) + if err != nil { + return nil, errors.Wrapf(err, "unable to read relayer key data: %s", fileNameFull) + } + + // unmarshal the JSON data into the struct + var key RelayerKey + err = json.Unmarshal(fileData, &key) + if err != nil { + return nil, errors.Wrap(err, "unable to unmarshal relayer key") + } + + return &key, nil +} + +// IsRelayerPrivateKeyValid checks if the given private key is valid for the given network +func IsRelayerPrivateKeyValid(privateKey string, network chains.Network) bool { + switch network { + case chains.Network_solana: + _, err := crypto.SolanaPrivateKeyFromString(privateKey) + if err != nil { + return false + } + default: + // unsupported network + return false + } + return true +} + +// relayerKeyFileByNetwork returns the relayer key JSON file name based on network +func relayerKeyFileByNetwork(network chains.Network) (string, error) { + // JSONFileSuffix is the suffix for the relayer key file + const JSONFileSuffix = ".json" + + // return file name for supported networks only + switch network { + case chains.Network_solana: + // return network name + '.json' + return network.String() + JSONFileSuffix, nil + default: + return "", errors.Errorf("network %d does not support relayer key", network) + } +} diff --git a/zetaclient/keys/relayer_key_test.go b/zetaclient/keys/relayer_key_test.go new file mode 100644 index 0000000000..08e82863d1 --- /dev/null +++ b/zetaclient/keys/relayer_key_test.go @@ -0,0 +1,293 @@ +package keys_test + +import ( + "os" + "os/user" + "path" + "testing" + + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/pkg/chains" + "github.com/zeta-chain/zetacore/pkg/crypto" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/zetaclient/keys" +) + +// createRelayerKeyFile creates a relayer key file for testing +func createRelayerKeyFile(t *testing.T, fileName, privKey, password string) { + // encrypt the private key + ciphertext, err := crypto.EncryptAES256GCMBase64(privKey, password) + require.NoError(t, err) + + // create relayer key file + err = keys.WriteRelayerKeyToFile(fileName, keys.RelayerKey{PrivateKey: ciphertext}) + require.NoError(t, err) +} + +// createBadRelayerKeyFile creates a bad relayer key file for testing +func createBadRelayerKeyFile(t *testing.T, fileName string) { + err := os.WriteFile(fileName, []byte("arbitrary data"), 0o600) + require.NoError(t, err) +} + +func Test_ResolveAddress(t *testing.T) { + // sample test keys + solanaPrivKey := sample.SolanaPrivateKey(t) + + tests := []struct { + name string + network chains.Network + relayerKey keys.RelayerKey + expectedNetworkName string + expectedAddress string + expectedError string + }{ + { + name: "should resolve solana address", + network: chains.Network_solana, + relayerKey: keys.RelayerKey{ + PrivateKey: solanaPrivKey.String(), + }, + expectedNetworkName: "solana", + expectedAddress: solanaPrivKey.PublicKey().String(), + }, + { + name: "should return error if private key is invalid", + network: chains.Network_solana, + relayerKey: keys.RelayerKey{ + PrivateKey: "invalid", + }, + expectedError: "unable to construct solana private key", + }, + { + name: "should return error if network is unsupported", + network: chains.Network_eth, + relayerKey: keys.RelayerKey{ + PrivateKey: solanaPrivKey.String(), + }, + expectedError: "unsupported network", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + networkName, address, err := tt.relayerKey.ResolveAddress(tt.network) + if tt.expectedError != "" { + require.Empty(t, networkName) + require.Empty(t, address) + require.ErrorContains(t, err, tt.expectedError) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expectedNetworkName, networkName) + require.Equal(t, tt.expectedAddress, address) + }) + } +} + +func Test_LoadRelayerKey(t *testing.T) { + // sample test key and temp path + solanaPrivKey := sample.SolanaPrivateKey(t) + keyPath := sample.CreateTempDir(t) + fileName := path.Join(keyPath, "solana.json") + + // create relayer key file + createRelayerKeyFile(t, fileName, solanaPrivKey.String(), "password") + + // create a bad relayer key file + keyPath2 := sample.CreateTempDir(t) + badKeyFile := path.Join(keyPath2, "solana.json") + createBadRelayerKeyFile(t, badKeyFile) + + // test cases + tests := []struct { + name string + keyPath string + network chains.Network + password string + expectedKey *keys.RelayerKey + expectError string + }{ + { + name: "should load relayer key successfully", + keyPath: keyPath, + network: chains.Network_solana, + password: "password", + expectedKey: &keys.RelayerKey{PrivateKey: solanaPrivKey.String()}, + }, + { + name: "it's okay if relayer key is not provided", + keyPath: sample.CreateTempDir(t), // create a empty directory + network: chains.Network_solana, + password: "", + expectedKey: nil, + expectError: "", + }, + { + name: "should return error if network is unsupported", + keyPath: keyPath, + network: chains.Network_eth, + password: "", + expectedKey: nil, + expectError: "failed to resolve relayer key file name", + }, + { + name: "should return error if unable to read relayer key file", + keyPath: keyPath2, + network: chains.Network_solana, + password: "", + expectedKey: nil, + expectError: "failed to read relayer key file", + }, + { + name: "should return error if password is missing", + keyPath: keyPath, + network: chains.Network_solana, + password: "", + expectedKey: nil, + expectError: "password is required to decrypt the private key", + }, + { + name: "should return error if password is incorrect", + keyPath: keyPath, + network: chains.Network_solana, + password: "incorrect", + expectedKey: nil, + expectError: "failed to decrypt private key", + }, + } + + // Iterate over the test cases and run them + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + relayerKey, err := keys.LoadRelayerKey(tt.keyPath, tt.network, tt.password) + + if tt.expectError != "" { + require.ErrorContains(t, err, tt.expectError) + require.Nil(t, relayerKey) + } else { + require.NoError(t, err) + if tt.expectedKey != nil { + require.Equal(t, tt.expectedKey.PrivateKey, relayerKey.PrivateKey) + } + } + }) + } +} + +func Test_ResolveRelayerKeyPath(t *testing.T) { + usr, err := user.Current() + require.NoError(t, err) + + tests := []struct { + name string + relayerKeyPath string + network chains.Network + expectedName string + errMessage string + }{ + { + name: "should resolve relayer key path", + relayerKeyPath: "~/.zetacored/relayer-keys", + network: chains.Network_solana, + expectedName: path.Join(usr.HomeDir, ".zetacored/relayer-keys/solana.json"), + }, + { + name: "should return error if network is invalid", + relayerKeyPath: "~/.zetacored/relayer-keys", + network: chains.Network(999), + errMessage: "failed to get relayer key file name", + }, + { + name: "should return error if network does not support relayer key", + relayerKeyPath: "~/.zetacored/relayer-keys", + network: chains.Network_eth, + errMessage: "does not support relayer key", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + name, err := keys.ResolveRelayerKeyFile(tt.relayerKeyPath, tt.network) + if tt.errMessage != "" { + require.Empty(t, name) + require.ErrorContains(t, err, tt.errMessage) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expectedName, name) + }) + } +} + +func Test_ReadWriteRelayerKeyFile(t *testing.T) { + // sample test key and temp path + solanaPrivKey := sample.SolanaPrivateKey(t) + keyPath := sample.CreateTempDir(t) + fileName := path.Join(keyPath, "solana.json") + + t.Run("should write and read relayer key file", func(t *testing.T) { + // create relayer key file + err := keys.WriteRelayerKeyToFile(fileName, keys.RelayerKey{PrivateKey: solanaPrivKey.String()}) + require.NoError(t, err) + + // read relayer key file + relayerKey, err := keys.ReadRelayerKeyFromFile(fileName) + require.NoError(t, err) + require.Equal(t, solanaPrivKey.String(), relayerKey.PrivateKey) + }) + + t.Run("should return error if relayer key file does not exist", func(t *testing.T) { + noFileName := path.Join(keyPath, "non-existing.json") + _, err := keys.ReadRelayerKeyFromFile(noFileName) + require.ErrorContains(t, err, "unable to read relayer key data") + }) + + t.Run("should return error if unmarsalling fails", func(t *testing.T) { + // create a bad key file + badKeyFile := path.Join(keyPath, "bad.json") + createBadRelayerKeyFile(t, badKeyFile) + + // try reading bad key file + key, err := keys.ReadRelayerKeyFromFile(badKeyFile) + require.ErrorContains(t, err, "unable to unmarshal relayer key") + require.Nil(t, key) + }) +} + +func Test_IsRelayerPrivateKeyValid(t *testing.T) { + tests := []struct { + name string + privKey string + network chains.Network + result bool + }{ + { + name: "valid private key - solana", + privKey: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ", + network: chains.Network(7), // solana + result: true, + }, + { + name: "invalid private key - unsupported network", + privKey: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ", + network: chains.Network(0), // eth + result: false, + }, + { + name: "invalid private key - invalid solana private key", + privKey: "3EMjCcCJg53fMEGVj13UPQpo6p", // too short + network: chains.Network(7), // solana + result: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := keys.IsRelayerPrivateKeyValid(tt.privKey, chains.Network(tt.network)) + require.Equal(t, tt.result, result) + }) + } +} diff --git a/zetaclient/logs/fields.go b/zetaclient/logs/fields.go new file mode 100644 index 0000000000..497690ffa4 --- /dev/null +++ b/zetaclient/logs/fields.go @@ -0,0 +1,18 @@ +package logs + +// A group of predefined field keys and module names for zetaclient logs +const ( + // field keys + FieldModule = "module" + FieldMethod = "method" + FieldChain = "chain" + FieldNonce = "nonce" + FieldTx = "tx" + FieldCctx = "cctx" + + // module names + ModNameInbound = "inbound" + ModNameOutbound = "outbound" + ModNameGasPrice = "gasprice" + ModNameHeaders = "headers" +) diff --git a/zetaclient/metrics/metrics.go b/zetaclient/metrics/metrics.go index df956daa90..a0a7341f94 100644 --- a/zetaclient/metrics/metrics.go +++ b/zetaclient/metrics/metrics.go @@ -50,6 +50,13 @@ var ( Help: "Tss node blame counter per pubkey", }, []string{"pubkey"}) + // RelayerKeyBalance is a gauge that contains the relayer key balance of the chain + RelayerKeyBalance = promauto.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ZetaClientNamespace, + Name: "relayer_key_balance", + Help: "Relayer key balance of the chain", + }, []string{"chain"}) + // HotKeyBurnRate is a gauge that contains the fee burn rate of the hotkey HotKeyBurnRate = promauto.NewGauge(prometheus.GaugeOpts{ Namespace: ZetaClientNamespace, diff --git a/zetaclient/metrics/metrics_test.go b/zetaclient/metrics/metrics_test.go index 6be8bc30c0..239a6391c4 100644 --- a/zetaclient/metrics/metrics_test.go +++ b/zetaclient/metrics/metrics_test.go @@ -10,6 +10,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/zeta-chain/zetacore/pkg/chains" . "gopkg.in/check.v1" ) @@ -39,7 +40,7 @@ func (ms *MetricsSuite) TestCurryWith(c *C) { RPCCount.Reset() } -func (ms *MetricsSuite) TestMetrics(c *C) { +func (ms *MetricsSuite) Test_RPCCount(c *C) { GetFilterLogsPerChain.WithLabelValues("chain1").Inc() GetFilterLogsPerChain.WithLabelValues("chain2").Inc() GetFilterLogsPerChain.WithLabelValues("chain2").Inc() @@ -77,3 +78,11 @@ func (ms *MetricsSuite) TestMetrics(c *C) { rpcCount = testutil.ToFloat64(RPCCount.With(prometheus.Labels{"host": "127.0.0.1:8886", "code": "502"})) c.Assert(rpcCount, Equals, 0.0) } + +func (ms *MetricsSuite) Test_RelayerKeyBalance(c *C) { + RelayerKeyBalance.WithLabelValues(chains.SolanaDevnet.Name).Set(2.1564) + + // assert that relayer key balance is being set correctly + balance := testutil.ToFloat64(RelayerKeyBalance.WithLabelValues(chains.SolanaDevnet.Name)) + c.Assert(balance, Equals, 2.1564) +} diff --git a/zetaclient/orchestrator/bootstap_test.go b/zetaclient/orchestrator/bootstap_test.go index 55b6f47614..bac8507f7c 100644 --- a/zetaclient/orchestrator/bootstap_test.go +++ b/zetaclient/orchestrator/bootstap_test.go @@ -51,7 +51,7 @@ func TestCreateSignerMap(t *testing.T) { cfg.BitcoinConfig = btcConfig // Given AppContext - app := zctx.New(cfg, log) + app := zctx.New(cfg, nil, log) ctx := zctx.WithAppContext(context.Background(), app) // Given chain & chainParams "fetched" from zetacore @@ -230,7 +230,7 @@ func TestCreateChainObserverMap(t *testing.T) { cfg.SolanaConfig = solConfig // Given AppContext - app := zctx.New(cfg, log) + app := zctx.New(cfg, nil, log) ctx := zctx.WithAppContext(context.Background(), app) // Given chain & chainParams "fetched" from zetacore diff --git a/zetaclient/orchestrator/bootstrap.go b/zetaclient/orchestrator/bootstrap.go index ef4920f8b5..238652f931 100644 --- a/zetaclient/orchestrator/bootstrap.go +++ b/zetaclient/orchestrator/bootstrap.go @@ -18,9 +18,9 @@ import ( "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" solbserver "github.com/zeta-chain/zetacore/zetaclient/chains/solana/observer" solanasigner "github.com/zeta-chain/zetacore/zetaclient/chains/solana/signer" - "github.com/zeta-chain/zetacore/zetaclient/config" zctx "github.com/zeta-chain/zetacore/zetaclient/context" "github.com/zeta-chain/zetacore/zetaclient/db" + "github.com/zeta-chain/zetacore/zetaclient/keys" "github.com/zeta-chain/zetacore/zetaclient/metrics" ) @@ -99,13 +99,17 @@ func syncSignerMap( continue } - rawChain := chain.RawChain() + var ( + params = chain.Params() + rawChain = chain.RawChain() + ) switch { case chain.IsEVM(): var ( - mpiAddress = ethcommon.HexToAddress(chain.Params().ConnectorContractAddress) - erc20CustodyAddress = ethcommon.HexToAddress(chain.Params().Erc20CustodyContractAddress) + zetaConnectorAddress = ethcommon.HexToAddress(chain.Params().ConnectorContractAddress) + erc20CustodyAddress = ethcommon.HexToAddress(chain.Params().Erc20CustodyContractAddress) + gatewayAddress = ethcommon.HexToAddress(chain.Params().GatewayAddress) ) cfg, found := app.Config().GetEVMConfig(chainID) @@ -121,10 +125,9 @@ func syncSignerMap( ts, logger, cfg.Endpoint, - config.GetConnectorABI(), - config.GetERC20CustodyABI(), - mpiAddress, + zetaConnectorAddress, erc20CustodyAddress, + gatewayAddress, ) if err != nil { logger.Std.Error().Err(err).Msgf("Unable to construct signer for EVM chain %d", chainID) @@ -161,19 +164,16 @@ func syncSignerMap( continue } - // load the Solana private key - solanaKey, err := app.Config().LoadSolanaPrivateKey() + // try loading Solana relayer key if present + password := chain.RelayerKeyPassword() + relayerKey, err := keys.LoadRelayerKey(app.Config().GetRelayerKeyPath(), rawChain.Network, password) if err != nil { - logger.Std.Error().Err(err).Msg("Unable to get Solana private key") + logger.Std.Error().Err(err).Msg("Unable to load Solana relayer key") + continue } - var ( - chainRaw = chain.RawChain() - paramsRaw = chain.Params() - ) - // create Solana signer - signer, err := solanasigner.NewSigner(*chainRaw, *paramsRaw, rpcClient, tss, solanaKey, ts, logger) + signer, err := solanasigner.NewSigner(*rawChain, *params, rpcClient, tss, relayerKey, ts, logger) if err != nil { logger.Std.Error().Err(err).Msgf("Unable to construct signer for SOL chain %d", chainID) continue diff --git a/zetaclient/orchestrator/orchestrator.go b/zetaclient/orchestrator/orchestrator.go index 884cbdc485..d8a8aeb6a0 100644 --- a/zetaclient/orchestrator/orchestrator.go +++ b/zetaclient/orchestrator/orchestrator.go @@ -15,12 +15,12 @@ import ( "github.com/samber/lo" "github.com/zeta-chain/zetacore/pkg/bg" + "github.com/zeta-chain/zetacore/pkg/constant" zetamath "github.com/zeta-chain/zetacore/pkg/math" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/chains/base" btcobserver "github.com/zeta-chain/zetacore/zetaclient/chains/bitcoin/observer" - "github.com/zeta-chain/zetacore/zetaclient/chains/evm" "github.com/zeta-chain/zetacore/zetaclient/chains/interfaces" solanaobserver "github.com/zeta-chain/zetacore/zetaclient/chains/solana/observer" zctx "github.com/zeta-chain/zetacore/zetaclient/context" @@ -180,7 +180,7 @@ func (oc *Orchestrator) resolveSigner(app *zctx.AppContext, chainID int64) (inte case chain.IsSolana(): params := chain.Params() - // update solana gateway address + // update gateway address if params.GatewayAddress != signer.GetGatewayAddress() { signer.SetGatewayAddress(params.GatewayAddress) oc.logger.Info(). @@ -660,8 +660,13 @@ func (oc *Orchestrator) ScheduleCctxSolana( // runObserverSignerSync runs a blocking ticker that observes chain changes from zetacore // and optionally (de)provisions respective observers and signers. func (oc *Orchestrator) runObserverSignerSync(ctx context.Context) error { - // check every other zeta block - const cadence = 2 * evm.ZetaBlockTime + // sync observers and signers right away to speed up zetaclient startup + if err := oc.syncObserverSigner(ctx); err != nil { + oc.logger.Error().Err(err).Msg("runObserverSignerSync: syncObserverSigner failed for initial sync") + } + + // sync observer and signer every 10 blocks (approx. 1 minute) + const cadence = 10 * constant.ZetaBlockTime ticker := time.NewTicker(cadence) defer ticker.Stop() diff --git a/zetaclient/orchestrator/orchestrator_test.go b/zetaclient/orchestrator/orchestrator_test.go index 3594accc2a..21d3998a84 100644 --- a/zetaclient/orchestrator/orchestrator_test.go +++ b/zetaclient/orchestrator/orchestrator_test.go @@ -535,7 +535,7 @@ func createAppContext(t *testing.T, chainsOrParams ...any) *zctx.AppContext { } // new AppContext - appContext := zctx.New(cfg, zerolog.New(zerolog.NewTestWriter(t))) + appContext := zctx.New(cfg, nil, zerolog.New(zerolog.NewTestWriter(t))) ccFlags := sample.CrosschainFlags() diff --git a/zetaclient/testdata/cctx/chain_1_cctx_inbound_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.go b/zetaclient/testdata/cctx/chain_1_cctx_inbound_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.go index f629b000e9..fefbc9cad9 100644 --- a/zetaclient/testdata/cctx/chain_1_cctx_inbound_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.go +++ b/zetaclient/testdata/cctx/chain_1_cctx_inbound_ERC20_0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da.go @@ -28,7 +28,7 @@ var chain_1_cctx_inbound_ERC20_0x4ea69a0 = &crosschaintypes.CrossChainTx{ Amount: sdkmath.NewUintFromString("9992000000"), ObservedHash: "0x4ea69a0e2ff36f7548ab75791c3b990e076e2a4bffeb616035b239b7d33843da", ObservedExternalHeight: 19320188, - BallotIndex: "0xaf8af6853ead0a6f7c6348ab91b3631e9527aa30da4b22eec199fb8c99060920", + BallotIndex: "0xc3d0dab7b2a34e3bfa2430963ff776ef2357c41f3164a28ab5d6380d7a438938", FinalizedZetaHeight: 1944675, TxFinalizationStatus: crosschaintypes.TxFinalizationStatus_Executed, }, diff --git a/zetaclient/testdata/cctx/chain_1_cctx_inbound_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.go b/zetaclient/testdata/cctx/chain_1_cctx_inbound_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.go index 91fc0beaf8..ef095d996d 100644 --- a/zetaclient/testdata/cctx/chain_1_cctx_inbound_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.go +++ b/zetaclient/testdata/cctx/chain_1_cctx_inbound_Gas_0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532.go @@ -28,7 +28,7 @@ var chain_1_cctx_inbound_Gas_0xeaec67d = &crosschaintypes.CrossChainTx{ Amount: sdkmath.NewUintFromString("4000000000000000"), ObservedHash: "0xeaec67d5dd5d85f27b21bef83e01cbdf59154fd793ea7a22c297f7c3a722c532", ObservedExternalHeight: 19330473, - BallotIndex: "0x639adb850b522874ddd2c4e5eb7ae7ad26d86814fc5b18d8a9e85f638bb94594", + BallotIndex: "0x79f6a3da92d085b2f3c682a2eb1606ef89e53a7db4fdbbb397b3e0f54884cfb0", FinalizedZetaHeight: 1965579, TxFinalizationStatus: crosschaintypes.TxFinalizationStatus_Executed, }, diff --git a/zetaclient/testdata/cctx/chain_1_cctx_inbound_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.go b/zetaclient/testdata/cctx/chain_1_cctx_inbound_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.go index 6e3f5d855d..f655dcc508 100644 --- a/zetaclient/testdata/cctx/chain_1_cctx_inbound_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.go +++ b/zetaclient/testdata/cctx/chain_1_cctx_inbound_Zeta_0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76.go @@ -28,7 +28,7 @@ var chain_1_cctx_inbound_Zeta_0xf393520 = &crosschaintypes.CrossChainTx{ Amount: sdkmath.NewUintFromString("20000000000000000000"), ObservedHash: "0xf3935200c80f98502d5edc7e871ffc40ca898e134525c42c2ae3cbc5725f9d76", ObservedExternalHeight: 19273702, - BallotIndex: "0x9039e8b0493d58ee4db876ffd0785970019d7eb515e2f71b00fe670bb6a8e6ce", + BallotIndex: "0xa4efd2d7293e1c2c557eb527c7ad7f2f8754cce413d3f72929a17e02b537b0af", FinalizedZetaHeight: 1851403, TxFinalizationStatus: crosschaintypes.TxFinalizationStatus_Executed, }, diff --git a/zetaclient/testutils/constant.go b/zetaclient/testutils/constant.go index ad8302577d..3036035db4 100644 --- a/zetaclient/testutils/constant.go +++ b/zetaclient/testutils/constant.go @@ -1,6 +1,10 @@ package testutils -import ethcommon "github.com/ethereum/go-ethereum/common" +import ( + ethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/zeta-chain/zetacore/pkg/chains" +) const ( // TSSAddressEVMMainnet the EVM TSS address for test purposes @@ -29,33 +33,39 @@ const ( EventERC20Withdraw = "Withdrawn" ) +// GatewayAddresses contains constants gateway addresses for testing +var GatewayAddresses = map[int64]string{ + // Gateway address on Solana devnet + chains.SolanaDevnet.ChainId: "94U5AHQMKkV5txNJ17QPXWoh474PheGou6cNP2FEuL1d", +} + // ConnectorAddresses contains constants ERC20 connector addresses for testing var ConnectorAddresses = map[int64]ethcommon.Address{ // Connector address on Ethereum mainnet - 1: ethcommon.HexToAddress("0x000007Cf399229b2f5A4D043F20E90C9C98B7C6a"), + chains.Ethereum.ChainId: ethcommon.HexToAddress("0x000007Cf399229b2f5A4D043F20E90C9C98B7C6a"), // Connector address on Binance Smart Chain mainnet - 56: ethcommon.HexToAddress("0x000063A6e758D9e2f438d430108377564cf4077D"), + chains.BscMainnet.ChainId: ethcommon.HexToAddress("0x000063A6e758D9e2f438d430108377564cf4077D"), // testnet - 5: ethcommon.HexToAddress("0x00005E3125aBA53C5652f9F0CE1a4Cf91D8B15eA"), - 97: ethcommon.HexToAddress("0x0000ecb8cdd25a18F12DAA23f6422e07fBf8B9E1"), - 11155111: ethcommon.HexToAddress("0x3963341dad121c9CD33046089395D66eBF20Fb03"), + chains.Goerli.ChainId: ethcommon.HexToAddress("0x00005E3125aBA53C5652f9F0CE1a4Cf91D8B15eA"), + chains.BscTestnet.ChainId: ethcommon.HexToAddress("0x0000ecb8cdd25a18F12DAA23f6422e07fBf8B9E1"), + chains.Sepolia.ChainId: ethcommon.HexToAddress("0x3963341dad121c9CD33046089395D66eBF20Fb03"), // localnet - 1337: ethcommon.HexToAddress("0xD28D6A0b8189305551a0A8bd247a6ECa9CE781Ca"), + chains.GoerliLocalnet.ChainId: ethcommon.HexToAddress("0xD28D6A0b8189305551a0A8bd247a6ECa9CE781Ca"), } // CustodyAddresses contains constants ERC20 custody addresses for testing var CustodyAddresses = map[int64]ethcommon.Address{ // ERC20 custody address on Ethereum mainnet - 1: ethcommon.HexToAddress("0x0000030Ec64DF25301d8414eE5a29588C4B0dE10"), + chains.Ethereum.ChainId: ethcommon.HexToAddress("0x0000030Ec64DF25301d8414eE5a29588C4B0dE10"), // ERC20 custody address on Binance Smart Chain mainnet - 56: ethcommon.HexToAddress("0x00000fF8fA992424957F97688015814e707A0115"), + chains.BscMainnet.ChainId: ethcommon.HexToAddress("0x00000fF8fA992424957F97688015814e707A0115"), // testnet - 5: ethcommon.HexToAddress("0x000047f11C6E42293F433C82473532E869Ce4Ec5"), - 97: ethcommon.HexToAddress("0x0000a7Db254145767262C6A81a7eE1650684258e"), - 11155111: ethcommon.HexToAddress("0x84725b70a239d3Faa7C6EF0C6C8E8b6c8e28338b"), + chains.Goerli.ChainId: ethcommon.HexToAddress("0x000047f11C6E42293F433C82473532E869Ce4Ec5"), + chains.BscTestnet.ChainId: ethcommon.HexToAddress("0x0000a7Db254145767262C6A81a7eE1650684258e"), + chains.Sepolia.ChainId: ethcommon.HexToAddress("0x84725b70a239d3Faa7C6EF0C6C8E8b6c8e28338b"), } diff --git a/zetaclient/testutils/evm.go b/zetaclient/testutils/evm.go index 3007fa6035..7a0218fecf 100644 --- a/zetaclient/testutils/evm.go +++ b/zetaclient/testutils/evm.go @@ -2,8 +2,8 @@ package testutils import ( ethtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.non-eth.sol" ) // ParseReceiptZetaSent parses a ZetaSent event from a receipt diff --git a/zetaclient/testutils/mocks/chain_params.go b/zetaclient/testutils/mocks/chain_params.go index 19603eda34..a23010372a 100644 --- a/zetaclient/testutils/mocks/chain_params.go +++ b/zetaclient/testutils/mocks/chain_params.go @@ -5,22 +5,21 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/stretchr/testify/require" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/erc20custody.sol" - "github.com/zeta-chain/protocol-contracts/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" + "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.non-eth.sol" + "github.com/zeta-chain/zetacore/pkg/constant" observertypes "github.com/zeta-chain/zetacore/x/observer/types" "github.com/zeta-chain/zetacore/zetaclient/testutils" ) func MockChainParams(chainID int64, confirmation uint64) observertypes.ChainParams { - const zeroAddress = "0x0000000000000000000000000000000000000000" - - connectorAddr := zeroAddress + connectorAddr := constant.EVMZeroAddress if a, ok := testutils.ConnectorAddresses[chainID]; ok { connectorAddr = a.Hex() } - erc20CustodyAddr := zeroAddress + erc20CustodyAddr := constant.EVMZeroAddress if a, ok := testutils.CustodyAddresses[chainID]; ok { erc20CustodyAddr = a.Hex() } @@ -28,7 +27,7 @@ func MockChainParams(chainID int64, confirmation uint64) observertypes.ChainPara return observertypes.ChainParams{ ChainId: chainID, ConfirmationCount: confirmation, - ZetaTokenContractAddress: zeroAddress, + ZetaTokenContractAddress: constant.EVMZeroAddress, ConnectorContractAddress: connectorAddr, Erc20CustodyContractAddress: erc20CustodyAddr, InboundTicker: 12, diff --git a/zetaclient/testutils/mocks/solana_rpc.go b/zetaclient/testutils/mocks/solana_rpc.go index 953bde87e5..fad147037c 100644 --- a/zetaclient/testutils/mocks/solana_rpc.go +++ b/zetaclient/testutils/mocks/solana_rpc.go @@ -47,6 +47,36 @@ func (_m *SolanaRPCClient) GetAccountInfo(ctx context.Context, account solana.Pu return r0, r1 } +// GetBalance provides a mock function with given fields: ctx, account, commitment +func (_m *SolanaRPCClient) GetBalance(ctx context.Context, account solana.PublicKey, commitment rpc.CommitmentType) (*rpc.GetBalanceResult, error) { + ret := _m.Called(ctx, account, commitment) + + if len(ret) == 0 { + panic("no return value specified for GetBalance") + } + + var r0 *rpc.GetBalanceResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, solana.PublicKey, rpc.CommitmentType) (*rpc.GetBalanceResult, error)); ok { + return rf(ctx, account, commitment) + } + if rf, ok := ret.Get(0).(func(context.Context, solana.PublicKey, rpc.CommitmentType) *rpc.GetBalanceResult); ok { + r0 = rf(ctx, account, commitment) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rpc.GetBalanceResult) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, solana.PublicKey, rpc.CommitmentType) error); ok { + r1 = rf(ctx, account, commitment) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // GetConfirmedTransactionWithOpts provides a mock function with given fields: ctx, signature, opts func (_m *SolanaRPCClient) GetConfirmedTransactionWithOpts(ctx context.Context, signature solana.Signature, opts *rpc.GetTransactionOpts) (*rpc.TransactionWithMeta, error) { ret := _m.Called(ctx, signature, opts) diff --git a/zetaclient/zetacore/client_monitor.go b/zetaclient/zetacore/client_monitor.go index b505af90a1..51d4f17168 100644 --- a/zetaclient/zetacore/client_monitor.go +++ b/zetaclient/zetacore/client_monitor.go @@ -150,10 +150,11 @@ func (c *Client) monitorVoteInboundResult( if retryGasLimit > 0 { // new retryGasLimit set to 0 to prevent reentering this function - if _, _, err := c.PostVoteInbound(ctx, retryGasLimit, 0, msg); err != nil { + if resentTxHash, _, err := c.PostVoteInbound(ctx, retryGasLimit, 0, msg); err != nil { c.logger.Error().Err(err).Fields(logFields).Msg("monitorVoteInboundResult: failed to resend tx") } else { - c.logger.Info().Fields(logFields).Msg("monitorVoteInboundResult: successfully resent tx") + logFields["inbound.resent_hash"] = resentTxHash + c.logger.Info().Fields(logFields).Msgf("monitorVoteInboundResult: successfully resent tx") } } diff --git a/zetaclient/zetacore/constant.go b/zetaclient/zetacore/constant.go index a5a4e16829..e7343206ee 100644 --- a/zetaclient/zetacore/constant.go +++ b/zetaclient/zetacore/constant.go @@ -16,7 +16,7 @@ const ( PostVoteInboundGasLimit = 500_000 // PostVoteInboundExecutionGasLimit is the gas limit for voting on observed inbound tx and executing it - PostVoteInboundExecutionGasLimit = 4_000_000 + PostVoteInboundExecutionGasLimit = 6_500_000 // PostVoteInboundMessagePassingExecutionGasLimit is the gas limit for voting on, and executing ,observed inbound tx related to message passing (coin_type == zeta) PostVoteInboundMessagePassingExecutionGasLimit = 4_000_000 @@ -41,7 +41,7 @@ const ( // PostVoteOutboundRevertGasLimit is the gas limit for voting on observed outbound tx for revert (when outbound fails) // The value needs to be higher because reverting implies interacting with the EVM to perform swaps for the gas token - PostVoteOutboundRevertGasLimit = 1_500_000 + PostVoteOutboundRevertGasLimit = 4_000_000 ) // constants for monitoring tx results diff --git a/zetaclient/zetacore/tx.go b/zetaclient/zetacore/tx.go index fb57cd449b..2d63f4a3fd 100644 --- a/zetaclient/zetacore/tx.go +++ b/zetaclient/zetacore/tx.go @@ -50,6 +50,7 @@ func GetInboundVoteMessage( coinType, asset, eventIndex, + types.ProtocolContractVersion_V1, ) return msg } diff --git a/zetaclient/zetacore/tx_test.go b/zetaclient/zetacore/tx_test.go index a01c11be2b..a87ed8404e 100644 --- a/zetaclient/zetacore/tx_test.go +++ b/zetaclient/zetacore/tx_test.go @@ -357,7 +357,7 @@ func TestZetacore_UpdateAppContext(t *testing.T) { t.Run("zetacore update success", func(t *testing.T) { cfg := config.New(false) - appContext := zctx.New(cfg, zerolog.Nop()) + appContext := zctx.New(cfg, nil, zerolog.Nop()) err := client.UpdateAppContext(ctx, appContext, zerolog.New(zerolog.NewTestWriter(t))) require.NoError(t, err) }) @@ -426,7 +426,7 @@ func TestZetacore_PostVoteInbound(t *testing.T) { expectedOutput := observertypes.QueryHasVotedResponse{HasVoted: false} input := observertypes.QueryHasVotedRequest{ - BallotIdentifier: "0x2d10e9b7ce7921fa6b61ada3020d1c797d5ec52424cdcf86ef31cbbbcd45db58", + BallotIdentifier: "0xd204175fc8500bcea563049cce918fa55134bd2d415d3fe137144f55e572b5ff", VoterAddress: address.String(), } method := "/zetachain.zetacore.observer.Query/HasVoted"